diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 00df55eee6b8..cc7c56d0e4fc 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -148,7 +148,10 @@ func init() { } // ActivePrecompiles returns the precompiles enabled with the current configuration. -func ActivePrecompiles(rules params.Rules) []common.Address { +func ActivePrecompiles(rules params.Rules) (active []common.Address) { + defer func() { + active = rules.Hooks().ActivePrecompiles(append([]common.Address{}, active...)) + }() switch { case rules.IsCancun: return PrecompiledAddressesCancun diff --git a/core/vm/contracts.libevm_test.go b/core/vm/contracts.libevm_test.go index d40513f89c48..b28174b38230 100644 --- a/core/vm/contracts.libevm_test.go +++ b/core/vm/contracts.libevm_test.go @@ -423,3 +423,25 @@ func TestCanCreateContract(t *testing.T) { }) } } + +func TestActivePrecompilesOverride(t *testing.T) { + newRules := func() params.Rules { + return new(params.ChainConfig).Rules(big.NewInt(0), false, 0) + } + defaultActive := vm.ActivePrecompiles(newRules()) + + rng := ethtest.NewPseudoRand(0xDecafC0ffeeBad) + precompiles := make([]common.Address, rng.Intn(10)+5) + for i := range precompiles { + precompiles[i] = rng.Address() + } + hooks := &hookstest.Stub{ + ActivePrecompilesFn: func(active []common.Address) []common.Address { + assert.Equal(t, defaultActive, active, "ActivePrecompiles() hook receives default addresses") + return precompiles + }, + } + hooks.Register(t) + + require.Equal(t, precompiles, vm.ActivePrecompiles(newRules()), "vm.ActivePrecompiles() returns overridden addresses") +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 872a24077cff..310ba33217c8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -159,7 +159,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig // 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) { - evm.TxContext, evm.StateDB = overrideEVMResetArgs(txCtx, statedb) + evm.TxContext, evm.StateDB = evm.overrideEVMResetArgs(txCtx, statedb) } // Cancel cancels any running EVM operation. This may be called concurrently and diff --git a/core/vm/evm.libevm_test.go b/core/vm/evm.libevm_test.go index 0a6f6f6209ab..9668b7d1f743 100644 --- a/core/vm/evm.libevm_test.go +++ b/core/vm/evm.libevm_test.go @@ -19,6 +19,7 @@ import ( "math/big" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/params" @@ -27,23 +28,25 @@ import ( type evmArgOverrider struct { newEVMchainID int64 - resetTxCtx TxContext - resetStateDB StateDB + gotResetChainID *big.Int + resetTxContextTo TxContext + resetStateDBTo StateDB } -func (o evmArgOverrider) OverrideNewEVMArgs(args *NewEVMArgs) *NewEVMArgs { +func (o *evmArgOverrider) OverrideNewEVMArgs(args *NewEVMArgs) *NewEVMArgs { args.ChainConfig = ¶ms.ChainConfig{ChainID: big.NewInt(o.newEVMchainID)} return args } -func (o evmArgOverrider) OverrideEVMResetArgs(*EVMResetArgs) *EVMResetArgs { +func (o *evmArgOverrider) OverrideEVMResetArgs(r params.Rules, _ *EVMResetArgs) *EVMResetArgs { + o.gotResetChainID = r.ChainID return &EVMResetArgs{ - TxContext: o.resetTxCtx, - StateDB: o.resetStateDB, + TxContext: o.resetTxContextTo, + StateDB: o.resetStateDBTo, } } -func (o evmArgOverrider) register(t *testing.T) { +func (o *evmArgOverrider) register(t *testing.T) { t.Helper() libevmHooks = nil RegisterHooks(o) @@ -71,9 +74,13 @@ func TestOverrideEVMResetArgs(t *testing.T) { // Equivalent to rationale for TestOverrideNewEVMArgs above. var _ func(TxContext, StateDB) = (*EVM)(nil).Reset - const gasPrice = 1357924680 - hooks := evmArgOverrider{ - resetTxCtx: TxContext{ + const ( + chainID = 0xc0ffee + gasPrice = 1357924680 + ) + hooks := &evmArgOverrider{ + newEVMchainID: chainID, + resetTxContextTo: TxContext{ GasPrice: big.NewInt(gasPrice), }, } @@ -81,5 +88,6 @@ func TestOverrideEVMResetArgs(t *testing.T) { evm := NewEVM(BlockContext{}, TxContext{}, nil, nil, Config{}) evm.Reset(TxContext{}, nil) - require.Equalf(t, big.NewInt(gasPrice), evm.GasPrice, "%T.GasPrice set by Reset() hook", evm) + assert.Equalf(t, big.NewInt(chainID), hooks.gotResetChainID, "%T.ChainID passed to Reset() hook", params.Rules{}) + assert.Equalf(t, big.NewInt(gasPrice), evm.GasPrice, "%T.GasPrice set by Reset() hook", evm) } diff --git a/core/vm/hooks.libevm.go b/core/vm/hooks.libevm.go index 71362355b4c1..461f72f78af8 100644 --- a/core/vm/hooks.libevm.go +++ b/core/vm/hooks.libevm.go @@ -33,7 +33,7 @@ var libevmHooks Hooks // See [RegisterHooks]. type Hooks interface { OverrideNewEVMArgs(*NewEVMArgs) *NewEVMArgs - OverrideEVMResetArgs(*EVMResetArgs) *EVMResetArgs + OverrideEVMResetArgs(params.Rules, *EVMResetArgs) *EVMResetArgs } // NewEVMArgs are the arguments received by [NewEVM], available for override @@ -67,10 +67,10 @@ func overrideNewEVMArgs( return args.BlockContext, args.TxContext, args.StateDB, args.ChainConfig, args.Config } -func overrideEVMResetArgs(txCtx TxContext, statedb StateDB) (TxContext, StateDB) { +func (evm *EVM) overrideEVMResetArgs(txCtx TxContext, statedb StateDB) (TxContext, StateDB) { if libevmHooks == nil { return txCtx, statedb } - args := libevmHooks.OverrideEVMResetArgs(&EVMResetArgs{txCtx, statedb}) + args := libevmHooks.OverrideEVMResetArgs(evm.chainRules, &EVMResetArgs{txCtx, statedb}) return args.TxContext, args.StateDB } diff --git a/libevm/hookstest/stub.go b/libevm/hookstest/stub.go index 78fec96f0e75..53df887f8288 100644 --- a/libevm/hookstest/stub.go +++ b/libevm/hookstest/stub.go @@ -45,6 +45,7 @@ type Stub struct { CheckConfigCompatibleFn func(*params.ChainConfig, *big.Int, uint64) *params.ConfigCompatError DescriptionSuffix string PrecompileOverrides map[common.Address]libevm.PrecompiledContract + ActivePrecompilesFn func([]common.Address) []common.Address CanExecuteTransactionFn func(common.Address, *common.Address, libevm.StateReader) error CanCreateContractFn func(*libevm.AddressContext, uint64, libevm.StateReader) (uint64, error) } @@ -71,6 +72,15 @@ func (s Stub) PrecompileOverride(a common.Address) (libevm.PrecompiledContract, return p, ok } +// ActivePrecompiles proxies arguments to the s.ActivePrecompilesFn function if +// non-nil, otherwise it acts as a noop. +func (s Stub) ActivePrecompiles(active []common.Address) []common.Address { + if f := s.ActivePrecompilesFn; f != nil { + return f(active) + } + return active +} + // CheckConfigForkOrder proxies arguments to the s.CheckConfigForkOrderFn // function if non-nil, otherwise it acts as a noop. func (s Stub) CheckConfigForkOrder() error { diff --git a/params/hooks.libevm.go b/params/hooks.libevm.go index 57e40d4f31a9..23e4e27490bf 100644 --- a/params/hooks.libevm.go +++ b/params/hooks.libevm.go @@ -48,6 +48,12 @@ type RulesHooks interface { // [PrecompiledContract] is non-nil. If it returns `false` then the default // precompile behaviour is honoured. PrecompileOverride(common.Address) (_ libevm.PrecompiledContract, override bool) + // ActivePrecompiles receives the addresses that would usually be returned + // by a call to [vm.ActivePrecompiles] and MUST return the value to be + // returned by said function, which will be propagated. It MAY alter the + // received slice. The value it returns MUST be consistent with the + // behaviour of the PrecompileOverride hook. + ActivePrecompiles([]common.Address) []common.Address } // RulesAllowlistHooks are a subset of [RulesHooks] that gate actions, signalled @@ -120,3 +126,8 @@ func (NOOPHooks) CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libev func (NOOPHooks) PrecompileOverride(common.Address) (libevm.PrecompiledContract, bool) { return nil, false } + +// ActivePrecompiles echoes the active addresses unchanged. +func (NOOPHooks) ActivePrecompiles(active []common.Address) []common.Address { + return active +}