Skip to content

Commit

Permalink
feat(evm): added stateful precompile functionnality
Browse files Browse the repository at this point in the history
  • Loading branch information
tremblaythibaultl committed Nov 23, 2022
1 parent 23bee16 commit 19b0337
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 53 deletions.
100 changes: 53 additions & 47 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,66 +42,71 @@ type PrecompiledContract interface {

// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
// contracts used in the Frontier and Homestead releases.
var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
var PrecompiledContractsHomestead = map[common.Address]StatefulPrecompiledContract{
common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
common.BytesToAddress([]byte{19}): &fheAdd{},
}

// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
// contracts used in the Byzantium release.
var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddByzantium{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{},
common.BytesToAddress([]byte{8}): &bn256PairingByzantium{},
var PrecompiledContractsByzantium = map[common.Address]StatefulPrecompiledContract{
common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}),
common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddByzantium{}),
common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulByzantium{}),
common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingByzantium{}),
common.BytesToAddress([]byte{19}): &fheAdd{},
}

// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
// contracts used in the Istanbul release.
var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
var PrecompiledContractsIstanbul = map[common.Address]StatefulPrecompiledContract{
common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}),
common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}),
common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}),
common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}),
common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}),
common.BytesToAddress([]byte{19}): &fheAdd{},
}

// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
// contracts used in the Berlin release.
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
var PrecompiledContractsBerlin = map[common.Address]StatefulPrecompiledContract{
common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}),
common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}),
common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}),
common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}),
common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}),
common.BytesToAddress([]byte{19}): &fheAdd{},
}

// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
// contracts specified in EIP-2537. These are exported for testing purposes.
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{10}): &bls12381G1Add{},
common.BytesToAddress([]byte{11}): &bls12381G1Mul{},
common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
common.BytesToAddress([]byte{13}): &bls12381G2Add{},
common.BytesToAddress([]byte{14}): &bls12381G2Mul{},
common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
common.BytesToAddress([]byte{16}): &bls12381Pairing{},
common.BytesToAddress([]byte{17}): &bls12381MapG1{},
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
var PrecompiledContractsBLS = map[common.Address]StatefulPrecompiledContract{
common.BytesToAddress([]byte{10}): newWrappedPrecompiledContract(&bls12381G1Add{}),
common.BytesToAddress([]byte{11}): newWrappedPrecompiledContract(&bls12381G1Mul{}),
common.BytesToAddress([]byte{12}): newWrappedPrecompiledContract(&bls12381G1MultiExp{}),
common.BytesToAddress([]byte{13}): newWrappedPrecompiledContract(&bls12381G2Add{}),
common.BytesToAddress([]byte{14}): newWrappedPrecompiledContract(&bls12381G2Mul{}),
common.BytesToAddress([]byte{15}): newWrappedPrecompiledContract(&bls12381G2MultiExp{}),
common.BytesToAddress([]byte{16}): newWrappedPrecompiledContract(&bls12381Pairing{}),
common.BytesToAddress([]byte{17}): newWrappedPrecompiledContract(&bls12381MapG1{}),
common.BytesToAddress([]byte{18}): newWrappedPrecompiledContract(&bls12381MapG2{}),
common.BytesToAddress([]byte{19}): &fheAdd{},
}

var (
Expand Down Expand Up @@ -264,9 +269,10 @@ var (
// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198
//
// def mult_complexity(x):
// if x <= 64: return x ** 2
// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
// else: return x ** 2 // 16 + 480 * x - 199680
//
// if x <= 64: return x ** 2
// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
// else: return x ** 2 // 16 + 480 * x - 199680
//
// where is x is max(length_of_MODULUS, length_of_BASE)
func modexpMultComplexity(x *big.Int) *big.Int {
Expand Down
64 changes: 64 additions & 0 deletions core/vm/contracts_stateful.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package vm

import (
"github.com/ethereum/go-ethereum/common"
)

type PrecompileAccessibleState interface {
GetStateDB() StateDB
GetBlockContext() BlockContext
}

// StatefulPrecompiledContract is the interface for executing a precompiled contract
type StatefulPrecompiledContract interface {
// RequiredGas computes the required gas for the given operation
RequiredGas(input []byte) uint64
// Run executes the precompiled contract.
Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error)
}

// wrappedPrecompiledContract implements StatefulPrecompiledContract by wrapping stateless native precompiled contracts
// in Ethereum.
type wrappedPrecompiledContract struct {
p PrecompiledContract
}

// RequiredGas implements the StatefulPrecompiledContract interface
func (w *wrappedPrecompiledContract) RequiredGas(input []byte) uint64 {
return w.p.RequiredGas(input)
}

// Run implements the StatefulPrecompiledContract interface
func (w *wrappedPrecompiledContract) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
return RunPrecompiledContract(w.p, input, suppliedGas)
}

// newWrappedPrecompiledContract returns a wrapped version of [PrecompiledContract] to be executed according to the StatefulPrecompiledContract
// interface.
func newWrappedPrecompiledContract(p PrecompiledContract) StatefulPrecompiledContract {
return &wrappedPrecompiledContract{p: p}
}

func RunStatefulPrecompiledContract(sp StatefulPrecompiledContract, accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
gasCost := sp.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
}
suppliedGas -= gasCost

return sp.Run(accessibleState, caller, addr, input, suppliedGas, readOnly)
}

type fheAdd struct{}

func (e *fheAdd) RequiredGas(input []byte) uint64 {
return 8
}

func (e *fheAdd) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
// state is editable here, e.g.
// accessibleState.GetStateDB().SetNonce(caller, 233)
// will change the caller's (contract that called this precompiled contract) to 233.

return input, suppliedGas, nil
}
22 changes: 16 additions & 6 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ type (
GetHashFunc func(uint64) common.Hash
)

func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
func (evm *EVM) precompile(addr common.Address) (StatefulPrecompiledContract, bool) {
var precompiles map[common.Address]StatefulPrecompiledContract
switch {
case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin
Expand Down Expand Up @@ -161,6 +161,16 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
return evm.interpreter
}

// GetStateDB returns the EVM's StateDB
func (evm *EVM) GetStateDB() StateDB {
return evm.StateDB
}

// GetBlockContext returns the EVM's BlockContext
func (evm *EVM) GetBlockContext() BlockContext {
return evm.Context
}

// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
Expand Down Expand Up @@ -212,7 +222,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
}

if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly)
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
Expand Down Expand Up @@ -275,7 +285,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,

// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly)
} else {
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
Expand Down Expand Up @@ -316,7 +326,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by

// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly)
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
Expand Down Expand Up @@ -365,7 +375,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
}

if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly)
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'
Expand Down

0 comments on commit 19b0337

Please sign in to comment.