diff --git a/CHANGELOG.md b/CHANGELOG.md index 627acd1ed18c..5f7fc3fbab7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* [#7](https://github.com/evmos/go-ethereum/pull/7) Implement custom active precompiles for the EVM. * [#6](https://github.com/evmos/go-ethereum/pull/6) Refactor `Stack` implementation * [#3](https://github.com/evmos/go-ethereum/pull/3) Move the `JumpTable` defaults to a separate function. * [#2](https://github.com/evmos/go-ethereum/pull/2) Define `Interpreter` interface for the EVM. diff --git a/core/state_transition.go b/core/state_transition.go index 0946c0372e2f..068a4fb21f29 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -42,8 +42,10 @@ The state transitioning model does all the necessary work to work out a valid ne 3) Create a new state object if the recipient is \0*32 4) Value transfer == If contract creation == - 4a) Attempt to run transaction data - 4b) If valid, use result as code for the new state object + + 4a) Attempt to run transaction data + 4b) If valid, use result as code for the new state object + == end == 5) Run Script section 6) Derive new state root @@ -262,13 +264,13 @@ func (st *StateTransition) preCheck() error { // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // -// - used gas: -// total gas used (including gas being refunded) -// - returndata: -// the returned data from evm -// - concrete execution error: -// various **EVM** error which aborts the execution, -// e.g. ErrOutOfGas, ErrExecutionReverted +// - used gas: +// total gas used (including gas being refunded) +// - returndata: +// the returned data from evm +// - concrete execution error: +// various **EVM** error which aborts the execution, +// e.g. ErrOutOfGas, ErrExecutionReverted // // However if any consensus issue encountered, return the error directly with // nil evm execution result. @@ -319,8 +321,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // Set up the initial access list. if rules.IsBerlin { - st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) + st.state.PrepareAccessList(msg.From(), msg.To(), st.evm.ActivePrecompiles(rules), msg.AccessList()) } + var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 1b832b638695..4a09ba8a6c9c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -17,9 +17,11 @@ package vm import ( + "bytes" "crypto/sha256" "encoding/binary" "errors" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -126,8 +128,8 @@ func init() { } } -// ActivePrecompiles returns the precompiles enabled with the current configuration. -func ActivePrecompiles(rules params.Rules) []common.Address { +// DefaultActivePrecompiles returns the set of precompiles enabled with the default configuration. +func DefaultActivePrecompiles(rules params.Rules) []common.Address { switch { case rules.IsBerlin: return PrecompiledAddressesBerlin @@ -140,6 +142,87 @@ func ActivePrecompiles(rules params.Rules) []common.Address { } } +// DefaultPrecompiles define the mapping of address and precompiles from the default configuration +func DefaultPrecompiles(rules params.Rules) (precompiles map[common.Address]PrecompiledContract) { + switch { + case rules.IsBerlin: + precompiles = PrecompiledContractsBerlin + case rules.IsIstanbul: + precompiles = PrecompiledContractsIstanbul + case rules.IsByzantium: + precompiles = PrecompiledContractsByzantium + default: + precompiles = PrecompiledContractsHomestead + } + + return precompiles +} + +// ActivePrecompiles returns the precompiles enabled with the current configuration. +// +// NOTE: The rules argument is ignored as the active precompiles can be set via the WithPrecompiles +// method according to the chain rules from the current block context. +func (evm *EVM) ActivePrecompiles(_ params.Rules) []common.Address { + return evm.activePrecompiles +} + +// Precompile returns a precompiled contract for the given address. This +// function returns false if the address is not a registered precompile. +func (evm *EVM) Precompile(addr common.Address) (PrecompiledContract, bool) { + p, ok := evm.precompiles[addr] + return p, ok +} + +// WithPrecompiles sets the precompiled contracts and the slice of actives precompiles. +// IMPORTANT: This function does NOT validate the precompiles provided to the EVM. The caller should +// use the ValidatePrecompiles function for this purpose prior to calling WithPrecompiles. +func (evm *EVM) WithPrecompiles( + precompiles map[common.Address]PrecompiledContract, + activePrecompiles []common.Address, +) { + evm.precompiles = precompiles + evm.activePrecompiles = activePrecompiles +} + +// ValidatePrecompiles validates the precompile map against the active +// precompile slice. +// It returns an error if the precompiled contract map has a different length +// than the slice of active contract addresses. This function also checks for +// duplicates, invalid addresses and empty precompile contract instances. +func ValidatePrecompiles( + precompiles map[common.Address]PrecompiledContract, + activePrecompiles []common.Address, +) error { + if len(precompiles) != len(activePrecompiles) { + return fmt.Errorf("precompiles length mismatch (expected %d, got %d)", len(precompiles), len(activePrecompiles)) + } + + dupActivePrecompiles := make(map[common.Address]bool) + + for _, addr := range activePrecompiles { + if dupActivePrecompiles[addr] { + return fmt.Errorf("duplicate active precompile: %s", addr) + } + + precompile, ok := precompiles[addr] + if !ok { + return fmt.Errorf("active precompile address doesn't exist in precompiles map: %s", addr) + } + + if precompile == nil { + return fmt.Errorf("precompile contract cannot be nil: %s", addr) + } + + if bytes.Equal(addr.Bytes(), common.Address{}.Bytes()) { + return fmt.Errorf("precompile cannot be the zero address: %s", addr) + } + + dupActivePrecompiles[addr] = true + } + + return nil +} + // RunPrecompiledContract runs and evaluates the output of a precompiled contract. // It returns // - the returned bytes, @@ -203,6 +286,7 @@ type sha256hash struct{} func (c *sha256hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas } + func (c *sha256hash) Run(input []byte) ([]byte, error) { h := sha256.Sum256(input) return h[:], nil @@ -218,6 +302,7 @@ type ripemd160hash struct{} func (c *ripemd160hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas } + func (c *ripemd160hash) Run(input []byte) ([]byte, error) { ripemd := ripemd160.New() ripemd.Write(input) @@ -234,6 +319,7 @@ type dataCopy struct{} func (c *dataCopy) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas } + func (c *dataCopy) Run(in []byte) ([]byte, error) { return in, nil } @@ -264,9 +350,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 { diff --git a/core/vm/evm.go b/core/vm/evm.go index 4fc6310b3bac..ff0878e85be1 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -41,22 +41,6 @@ type ( GetHashFunc func(uint64) common.Hash ) -func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { - var precompiles map[common.Address]PrecompiledContract - switch { - case evm.chainRules.IsBerlin: - precompiles = PrecompiledContractsBerlin - case evm.chainRules.IsIstanbul: - precompiles = PrecompiledContractsIstanbul - case evm.chainRules.IsByzantium: - precompiles = PrecompiledContractsByzantium - default: - precompiles = PrecompiledContractsHomestead - } - p, ok := precompiles[addr] - return p, ok -} - // BlockContext provides the EVM with auxiliary information. Once provided // it shouldn't be modified. type BlockContext struct { @@ -121,6 +105,10 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 + // precompiles defines the precompiled contracts used by the EVM + precompiles map[common.Address]PrecompiledContract + // activePrecompiles defines the precompiles that are currently active + activePrecompiles []common.Address } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -134,8 +122,11 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil), } - + // set the default precompiles + evm.activePrecompiles = DefaultActivePrecompiles(evm.chainRules) + evm.precompiles = DefaultPrecompiles(evm.chainRules) evm.interpreter = NewEVMInterpreter(evm, config) + return evm } @@ -181,7 +172,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas return nil, gas, ErrInsufficientBalance } snapshot := evm.StateDB.Snapshot() - p, isPrecompile := evm.precompile(addr) + p, isPrecompile := evm.Precompile(addr) if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { @@ -280,7 +271,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 { + if p, isPrecompile := evm.Precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr @@ -321,7 +312,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 { + if p, isPrecompile := evm.Precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr @@ -370,7 +361,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte }(gas) } - if p, isPrecompile := evm.precompile(addr); isPrecompile { + if p, isPrecompile := evm.Precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { // At this point, we use a copy of address. If we don't, the go compiler will diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 7861fb92dba3..959134a1fb24 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -119,8 +119,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { sender = vm.AccountRef(cfg.Origin) ) if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin { - cfg.State.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) + cfg.State.PrepareAccessList(cfg.Origin, &address, vm.DefaultActivePrecompiles(rules), nil) } + cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -151,8 +152,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { sender = vm.AccountRef(cfg.Origin) ) if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin { - cfg.State.PrepareAccessList(cfg.Origin, nil, vm.ActivePrecompiles(rules), nil) + cfg.State.PrepareAccessList(cfg.Origin, nil, vm.DefaultActivePrecompiles(rules), nil) } + // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, @@ -177,7 +179,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er statedb := cfg.State if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin { - statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) + statedb.PrepareAccessList(cfg.Origin, &address, vm.DefaultActivePrecompiles(rules), nil) } // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 3cec9baf4519..73aa20d20a09 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -49,9 +49,11 @@ func init() { // hex strings into big ints. var bigIntProgram = goja.MustCompile("bigInt", bigIntegerJS, false) -type toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error) -type toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error) -type fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error) +type ( + toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error) + toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error) + fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error) +) func toBuf(vm *goja.Runtime, bufType goja.Value, val []byte) (goja.Value, error) { // bufType is usually Uint8Array. This is equivalent to `new Uint8Array(val)` in JS. @@ -238,7 +240,7 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64()) // Update list of precompiles based on current block rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = env.ActivePrecompiles(rules) t.ctx["intrinsicGas"] = t.vm.ToValue(t.gasLimit - gas) } diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 34e608bfd60d..d938f019c219 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -37,14 +37,15 @@ func init() { // a reversed signature can be matched against the size of the data. // // Example: -// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"}) -// { -// 0x27dc297e-128: 1, -// 0x38cc4831-0: 2, -// 0x524f3889-96: 1, -// 0xadf59f99-288: 1, -// 0xc281d19e-0: 1 -// } +// +// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"}) +// { +// 0x27dc297e-128: 1, +// 0x38cc4831-0: 2, +// 0x524f3889-96: 1, +// 0xadf59f99-288: 1, +// 0xc281d19e-0: 1 +// } type fourByteTracer struct { env *vm.EVM ids map[string]int // ids aggregates the 4byte ids found @@ -84,7 +85,7 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo // Update list of precompiles based on current block rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = env.ActivePrecompiles(rules) // Save the outer calldata also if len(input) >= 4 { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e6740942d859..913faf563000 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -227,7 +227,7 @@ func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string { pending, queue := s.b.TxPoolContent() // Define a formatter to flatten a transaction into a string - var format = func(tx *types.Transaction) string { + format := func(tx *types.Transaction) string { if to := tx.To(); to != nil { return fmt.Sprintf("%s: %v wei + %v gas × %v wei", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice()) } @@ -731,10 +731,10 @@ func (s *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) m } // GetBlockByNumber returns the requested canonical block. -// * When blockNr is -1 the chain head is returned. -// * When blockNr is -2 the pending chain head is returned. -// * When fullTx is true all transactions in the block are returned, otherwise -// only the transaction hash is returned. +// - When blockNr is -1 the chain head is returned. +// - When blockNr is -2 the pending chain head is returned. +// - When fullTx is true all transactions in the block are returned, otherwise +// only the transaction hash is returned. func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { @@ -1111,7 +1111,6 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr for lo+1 < hi { mid := (hi + lo) / 2 failed, _, err := executable(mid) - // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is // assigned. Return the error directly, don't struggle any more. @@ -1410,7 +1409,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } isPostMerge := header.Difficulty.Cmp(common.Big0) == 0 // Retrieve the precompiles since they don't need to be added to the access list - precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge)) + precompiles := vm.DefaultActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge)) // Create an initial tracer prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles) @@ -1837,11 +1836,11 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g matchTx := sendArgs.toTransaction() // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. - var price = matchTx.GasPrice() + price := matchTx.GasPrice() if gasPrice != nil { price = gasPrice.ToInt() } - var gas = matchTx.Gas() + gas := matchTx.Gas() if gasLimit != nil { gas = uint64(*gasLimit) } diff --git a/tests/state_test.go b/tests/state_test.go index d33ebc4b00db..27756d3e49e2 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -20,18 +20,17 @@ import ( "bufio" "bytes" "fmt" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" "math/big" "os" "path/filepath" "reflect" "strings" "testing" - - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers/logger" ) func TestState(t *testing.T) { @@ -192,6 +191,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.Error(err) return } + vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock() _, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, false)