diff --git a/core/vm/contracts.go b/core/vm/contracts.go index f6b16b2b44..cb6f5de93c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -138,7 +138,21 @@ func (p PrecompiledContracts) Copy() PrecompiledContracts { return c } -// ActivePrecompiles returns the precompiles enabled with the current configuration. +// ActivePrecompiledContracts returns precompiled contracts enabled with the current configuration. +func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { + switch { + case rules.IsBerlin: + return PrecompiledContractsBerlin + case rules.IsIstanbul: + return PrecompiledContractsIstanbul + case rules.IsByzantium: + return PrecompiledContractsByzantium + default: + return PrecompiledContractsHomestead + } +} + +// ActivePrecompiles returns the precompile addresses enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { case rules.IsBerlin: diff --git a/core/vm/evm.go b/core/vm/evm.go index 59e65ef715..a72822fcec 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -123,24 +123,18 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } - switch { - case evm.chainRules.IsBerlin: - evm.precompiles = PrecompiledContractsBerlin.Copy() - case evm.chainRules.IsIstanbul: - evm.precompiles = PrecompiledContractsIstanbul.Copy() - case evm.chainRules.IsByzantium: - evm.precompiles = PrecompiledContractsByzantium.Copy() - default: - evm.precompiles = PrecompiledContractsHomestead.Copy() - } - // ECRecoverCode can be set only through RPC calls - if config.DisableECRecover { - delete(evm.precompiles, common.BytesToAddress([]byte{1})) - } + evm.precompiles = ActivePrecompiledContracts(evm.chainRules) evm.interpreter = NewEVMInterpreter(evm) return evm } +// SetPrecompiles sets the precompiled contracts for the EVM. +// This method is only used through RPC calls. +// It is not thread-safe. +func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) { + evm.precompiles = precompiles +} + // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2f5c9a8cb9..13c56b57d4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -873,6 +873,7 @@ type OverrideAccount struct { Balance **hexutil.Big `json:"balance"` State *map[common.Hash]common.Hash `json:"state"` StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` + MoveTo *common.Address `json:"moveTo"` } // StateOverride is the collection of overridden accounts. @@ -917,6 +918,80 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { return nil } +// ApplyMulticall overrides the fields of specified accounts into the given state. +func (diff *StateOverride) ApplyMulticall(state *state.StateDB, precompiles vm.PrecompiledContracts) error { + if diff == nil { + return nil + } + for addr, account := range *diff { + p, isPrecompile := precompiles[addr] + // The MoveTo feature makes it possible to replace precompiles and EVM + // contracts in all the following configurations: + // 1. Precompile -> Precompile + // 2. Precompile -> EVM contract + // 3. EVM contract -> Precompile + // 4. EVM contract -> EVM contract + if account.MoveTo != nil { + if isPrecompile { + // Clear destination account which may be an EVM contract. + if !state.Empty(*account.MoveTo) { + state.SetCode(*account.MoveTo, nil) + state.SetNonce(*account.MoveTo, 0) + state.SetBalance(*account.MoveTo, big.NewInt(0)) + state.SetStorage(*account.MoveTo, map[common.Hash]common.Hash{}) + } + // If destination is a precompile, it will be simply replaced. + precompiles[*account.MoveTo] = p + } else { + state.SetBalance(*account.MoveTo, state.GetBalance(addr)) + state.SetNonce(*account.MoveTo, state.GetNonce(addr)) + state.SetCode(*account.MoveTo, state.GetCode(addr)) + // TODO: copy storage over + //state.SetState(*account.MoveTo, state.GetState(addr)) + // Clear source storage + state.SetStorage(addr, map[common.Hash]common.Hash{}) + if precompiles[*account.MoveTo] != nil { + delete(precompiles, *account.MoveTo) + } + } + } + if isPrecompile { + // Now that the contract is moved it can be deleted. + delete(precompiles, addr) + } + // Override account nonce. + if account.Nonce != nil { + state.SetNonce(addr, uint64(*account.Nonce)) + } + // Override account(contract) code. + if account.Code != nil { + state.SetCode(addr, *account.Code) + } + // Override account balance. + if account.Balance != nil { + state.SetBalance(addr, (*big.Int)(*account.Balance)) + } + if account.State != nil && account.StateDiff != nil { + return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) + } + // Replace entire state if caller requires. + if account.State != nil { + state.SetStorage(addr, *account.State) + } + // Apply state diff into specified accounts. + if account.StateDiff != nil { + for key, value := range *account.StateDiff { + state.SetState(addr, key, value) + } + } + } + // Now finalize the changes. Finalize is normally performed between transactions. + // By using finalize, the overrides are semantically behaving as + // if they were created in a transaction just before the tracing occur. + state.Finalise(false) + return nil +} + // BlockOverrides is a set of header fields to override. type BlockOverrides struct { Number *hexutil.Big @@ -1013,16 +1088,19 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}) + return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, nil) } -func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext, vmConfig *vm.Config) (*core.ExecutionResult, error) { +func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext, vmConfig *vm.Config, precompiles vm.PrecompiledContracts) (*core.ExecutionResult, error) { // Get a new instance of the EVM. msg, err := args.ToMessage(gp.Gas(), header.BaseFee) if err != nil { return nil, err } evm, vmError := b.GetEVM(ctx, msg, state, header, vmConfig, blockContext) + if precompiles != nil { + evm.SetPrecompiles(precompiles) + } // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1170,9 +1248,12 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo globalGasCap = s.b.RPCGasCap() gp = new(core.GasPool).AddGas(globalGasCap) prevNumber = header.Number.Uint64() + blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + rules = s.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) + precompiles = vm.ActivePrecompiledContracts(rules).Copy() ) for bi, block := range blocks { - blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) if block.BlockOverrides != nil { block.BlockOverrides.Apply(&blockContext) } @@ -1182,7 +1263,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } prevNumber = blockContext.BlockNumber.Uint64() // State overrides are applied prior to execution of a block - if err := block.StateOverrides.Apply(state); err != nil { + if err := block.StateOverrides.ApplyMulticall(state, precompiles); err != nil { return nil, err } // ECRecover replacement code will be fetched from statedb and executed as a normal EVM bytecode. @@ -1211,7 +1292,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if opts.TraceTransfers { vmConfig.Tracer = newTracer() } - result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig) + result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles) if err != nil { results[bi].Calls[i] = callResult{Error: err.Error(), Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 187fd2b6d6..6b85d0f358 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1018,17 +1018,19 @@ func TestMulticallV1(t *testing.T) { // } Code: hex2Bytes("6040516000815260006020820152600060408201526000606082015260208160808360015afa60008103603157600080fd5b601482f3"), }, + common.BytesToAddress([]byte{0x01}): OverrideAccount{ + // Yul code that returns the address of the caller. + // object "Test" { + // code { + // let c := caller() + // mstore(0, c) + // return(0xc, 0x14) + // } + // } + Code: hex2Bytes("33806000526014600cf3"), + }, }, BlockOverrides: &BlockOverrides{}, - // Yul code that returns the address of the caller. - // object "Test" { - // code { - // let c := caller() - // mstore(0, c) - // return(0xc, 0x14) - // } - // } - ECRecoverOverride: hex2Bytes("33806000526014600cf3"), Calls: []TransactionArgs{{ From: &randomAccounts[0].addr, To: &randomAccounts[2].addr,