mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 05:41:35 +00:00
chore: squash arr4n/libevm into libevm (#7)
* feat: pseudo-generic extra payloads in `params.ChainConfig` and `params.Rules` * feat: `params.ExtraPayloadGetter` for end-user type safety * refactor: payloads only available through `params.ExtraPayloadGetter` * chore: make `libevm/examples/extraparams` a `params` testable example * doc: `libevm/pseudo` package comments and improved readability * doc: `params.*Extra*` comments and improved readability * doc: `params.ExtraPayloadGetter` comments and improved readability * doc: `params/config.libevm_test.go` comments and improved readability * refactor: simplify `params.ChainConfig.UnmarshalJSON()` * refactor: abstract new/nil-pointer creation into `pseudo.Constructor`s * feat: precompile override via `params.Extras` hooks * doc: flesh out `PrecompileOverride()` in example * doc: complete commentary and improve readability * refactor: `ChainConfig.Hooks()` + `Rules` equivalent * chore: rename precompiles test file in keeping with geth equivalent * feat: stateful precompiles + allowlist hooks The allowlist hooks are included in this commit because they allow for the same functionality as stateful precompiles in `ava-labs/coreth` and `ava-labs/subnet-evm`. * fix: `StateTransition.canExecuteTransaction()` used `msg.From` instead of `To` * test: `params.RulesHooks.CanCreateContract` integration * test: `params.RulesHooks.CanExecuteTransaction` integration * test: `vm.NewStatefulPrecompile()` integration * refactor: simplify test of `CanCreateContract` * refactor: abstract generation of random `Address`/`Hash` values * doc: full documentation + readability refactoring/renaming * fix: remove circular dependency in tests
This commit is contained in:
parent
2bd6bd01d2
commit
5429fd87c8
22 changed files with 1516 additions and 7 deletions
|
|
@ -365,6 +365,9 @@ func (st *StateTransition) preCheck() error {
|
|||
// However if any consensus issue encountered, return the error directly with
|
||||
// nil evm execution result.
|
||||
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||
if err := st.canExecuteTransaction(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// First check this message satisfies all consensus rules before
|
||||
// applying the message. The rules include these clauses
|
||||
//
|
||||
|
|
|
|||
9
core/state_transition.libevm.go
Normal file
9
core/state_transition.libevm.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package core
|
||||
|
||||
// canExecuteTransaction is a convenience wrapper for calling the
|
||||
// [params.RulesHooks.CanExecuteTransaction] hook.
|
||||
func (st *StateTransition) canExecuteTransaction() error {
|
||||
bCtx := st.evm.Context
|
||||
rules := st.evm.ChainConfig().Rules(bCtx.BlockNumber, bCtx.Random != nil, bCtx.Time)
|
||||
return rules.Hooks().CanExecuteTransaction(st.msg.From, st.msg.To, st.state)
|
||||
}
|
||||
40
core/state_transition.libevm_test.go
Normal file
40
core/state_transition.libevm_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package core_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/libevm"
|
||||
"github.com/ethereum/go-ethereum/libevm/ethtest"
|
||||
"github.com/ethereum/go-ethereum/libevm/hookstest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCanExecuteTransaction(t *testing.T) {
|
||||
rng := ethtest.NewPseudoRand(42)
|
||||
account := rng.Address()
|
||||
slot := rng.Hash()
|
||||
|
||||
makeErr := func(from common.Address, to *common.Address, val common.Hash) error {
|
||||
return fmt.Errorf("From: %v To: %v State: %v", from, to, val)
|
||||
}
|
||||
hooks := &hookstest.Stub{
|
||||
CanExecuteTransactionFn: func(from common.Address, to *common.Address, s libevm.StateReader) error {
|
||||
return makeErr(from, to, s.GetState(account, slot))
|
||||
},
|
||||
}
|
||||
hooks.RegisterForRules(t)
|
||||
|
||||
value := rng.Hash()
|
||||
|
||||
state, evm := ethtest.NewZeroEVM(t)
|
||||
state.SetState(account, slot, value)
|
||||
msg := &core.Message{
|
||||
From: rng.Address(),
|
||||
To: rng.AddressPtr(),
|
||||
}
|
||||
_, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(30e6))
|
||||
require.EqualError(t, err, makeErr(msg.From, msg.To, value).Error())
|
||||
}
|
||||
|
|
@ -168,13 +168,13 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
|
|||
// - the returned bytes,
|
||||
// - the _remaining_ gas,
|
||||
// - any error that occurred
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||
func (args *evmCallArgs) RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||
gasCost := p.RequiredGas(input)
|
||||
if suppliedGas < gasCost {
|
||||
return nil, 0, ErrOutOfGas
|
||||
}
|
||||
suppliedGas -= gasCost
|
||||
output, err := p.Run(input)
|
||||
output, err := args.run(p, input)
|
||||
return output, suppliedGas, err
|
||||
}
|
||||
|
||||
|
|
|
|||
83
core/vm/contracts.libevm.go
Normal file
83
core/vm/contracts.libevm.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// evmCallArgs mirrors the parameters of the [EVM] methods Call(), CallCode(),
|
||||
// DelegateCall() and StaticCall(). Its fields are identical to those of the
|
||||
// parameters, prepended with the receiver name. As {Delegate,Static}Call don't
|
||||
// accept a value, they MUST set the respective field to nil.
|
||||
//
|
||||
// Instantiation can be achieved by merely copying the parameter names, in
|
||||
// order, which is trivially achieved with AST manipulation:
|
||||
//
|
||||
// func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) ... {
|
||||
// ...
|
||||
// args := &evmCallArgs{evm, caller, addr, input, gas, value}
|
||||
type evmCallArgs struct {
|
||||
evm *EVM
|
||||
caller ContractRef
|
||||
addr common.Address
|
||||
input []byte
|
||||
gas uint64
|
||||
value *uint256.Int
|
||||
}
|
||||
|
||||
// run runs the [PrecompiledContract], differentiating between stateful and
|
||||
// regular types.
|
||||
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
|
||||
if p, ok := p.(statefulPrecompile); ok {
|
||||
return p.run(args.evm.StateDB, &args.evm.chainRules, args.caller.Address(), args.addr, input)
|
||||
}
|
||||
return p.Run(input)
|
||||
}
|
||||
|
||||
// PrecompiledStatefulRun is the stateful equivalent of the Run() method of a
|
||||
// [PrecompiledContract].
|
||||
type PrecompiledStatefulRun func(_ StateDB, _ *params.Rules, caller, self common.Address, input []byte) ([]byte, error)
|
||||
|
||||
// NewStatefulPrecompile constructs a new PrecompiledContract that can be used
|
||||
// via an [EVM] instance but MUST NOT be called directly; a direct call to Run()
|
||||
// reserves the right to panic. See other requirements defined in the comments
|
||||
// on [PrecompiledContract].
|
||||
func NewStatefulPrecompile(run PrecompiledStatefulRun, requiredGas func([]byte) uint64) PrecompiledContract {
|
||||
return statefulPrecompile{
|
||||
gas: requiredGas,
|
||||
run: run,
|
||||
}
|
||||
}
|
||||
|
||||
type statefulPrecompile struct {
|
||||
gas func([]byte) uint64
|
||||
run PrecompiledStatefulRun
|
||||
}
|
||||
|
||||
func (p statefulPrecompile) RequiredGas(input []byte) uint64 {
|
||||
return p.gas(input)
|
||||
}
|
||||
|
||||
func (p statefulPrecompile) Run([]byte) ([]byte, error) {
|
||||
// https://google.github.io/styleguide/go/best-practices.html#when-to-panic
|
||||
// This would indicate an API misuse and would occur in tests, not in
|
||||
// production.
|
||||
panic(fmt.Sprintf("BUG: call to %T.Run(); MUST call %T", p, p.run))
|
||||
}
|
||||
|
||||
var (
|
||||
// These lock in the assumptions made when implementing [evmCallArgs]. If
|
||||
// these break then the struct fields SHOULD be changed to match these
|
||||
// signatures.
|
||||
_ = [](func(ContractRef, common.Address, []byte, uint64, *uint256.Int) ([]byte, uint64, error)){
|
||||
(*EVM)(nil).Call,
|
||||
(*EVM)(nil).CallCode,
|
||||
}
|
||||
_ = [](func(ContractRef, common.Address, []byte, uint64) ([]byte, uint64, error)){
|
||||
(*EVM)(nil).DelegateCall,
|
||||
(*EVM)(nil).StaticCall,
|
||||
}
|
||||
)
|
||||
178
core/vm/contracts.libevm_test.go
Normal file
178
core/vm/contracts.libevm_test.go
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
package vm_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/libevm"
|
||||
"github.com/ethereum/go-ethereum/libevm/ethtest"
|
||||
"github.com/ethereum/go-ethereum/libevm/hookstest"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
type precompileStub struct {
|
||||
requiredGas uint64
|
||||
returnData []byte
|
||||
}
|
||||
|
||||
func (s *precompileStub) RequiredGas([]byte) uint64 { return s.requiredGas }
|
||||
func (s *precompileStub) Run([]byte) ([]byte, error) { return s.returnData, nil }
|
||||
|
||||
func TestPrecompileOverride(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
addr common.Address
|
||||
requiredGas uint64
|
||||
stubData []byte
|
||||
}
|
||||
|
||||
const gasLimit = uint64(1e7)
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "arbitrary values",
|
||||
addr: common.Address{'p', 'r', 'e', 'c', 'o', 'm', 'p', 'i', 'l', 'e'},
|
||||
requiredGas: 314159,
|
||||
stubData: []byte("the return data"),
|
||||
},
|
||||
}
|
||||
|
||||
rng := rand.New(rand.NewSource(42))
|
||||
for _, addr := range vm.PrecompiledAddressesCancun {
|
||||
tests = append(tests, test{
|
||||
name: fmt.Sprintf("existing precompile %v", addr),
|
||||
addr: addr,
|
||||
requiredGas: rng.Uint64n(gasLimit),
|
||||
stubData: addr[:],
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
hooks := &hookstest.Stub{
|
||||
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
||||
tt.addr: &precompileStub{
|
||||
requiredGas: tt.requiredGas,
|
||||
returnData: tt.stubData,
|
||||
},
|
||||
},
|
||||
}
|
||||
hooks.RegisterForRules(t)
|
||||
|
||||
t.Run(fmt.Sprintf("%T.Call([overridden precompile address = %v])", &vm.EVM{}, tt.addr), func(t *testing.T) {
|
||||
_, evm := ethtest.NewZeroEVM(t)
|
||||
gotData, gotGasLeft, err := evm.Call(vm.AccountRef{}, tt.addr, nil, gasLimit, uint256.NewInt(0))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.stubData, gotData, "contract's return data")
|
||||
assert.Equal(t, gasLimit-tt.requiredGas, gotGasLeft, "gas left")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStatefulPrecompile(t *testing.T) {
|
||||
rng := ethtest.NewPseudoRand(314159)
|
||||
precompile := rng.Address()
|
||||
slot := rng.Hash()
|
||||
|
||||
const gasLimit = 1e6
|
||||
gasCost := rng.Uint64n(gasLimit)
|
||||
|
||||
makeOutput := func(caller, self common.Address, input []byte, stateVal common.Hash) []byte {
|
||||
return []byte(fmt.Sprintf(
|
||||
"Caller: %v Precompile: %v State: %v Input: %#x",
|
||||
caller, self, stateVal, input,
|
||||
))
|
||||
}
|
||||
hooks := &hookstest.Stub{
|
||||
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
||||
precompile: vm.NewStatefulPrecompile(
|
||||
func(state vm.StateDB, _ *params.Rules, caller, self common.Address, input []byte) ([]byte, error) {
|
||||
return makeOutput(caller, self, input, state.GetState(precompile, slot)), nil
|
||||
},
|
||||
func(b []byte) uint64 {
|
||||
return gasCost
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
hooks.RegisterForRules(t)
|
||||
|
||||
caller := rng.Address()
|
||||
input := rng.Bytes(8)
|
||||
value := rng.Hash()
|
||||
|
||||
state, evm := ethtest.NewZeroEVM(t)
|
||||
state.SetState(precompile, slot, value)
|
||||
wantReturnData := makeOutput(caller, precompile, input, value)
|
||||
wantGasLeft := gasLimit - gasCost
|
||||
|
||||
gotReturnData, gotGasLeft, err := evm.Call(vm.AccountRef(caller), precompile, input, gasLimit, uint256.NewInt(0))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, wantReturnData, gotReturnData)
|
||||
assert.Equal(t, wantGasLeft, gotGasLeft)
|
||||
}
|
||||
|
||||
func TestCanCreateContract(t *testing.T) {
|
||||
rng := ethtest.NewPseudoRand(142857)
|
||||
account := rng.Address()
|
||||
slot := rng.Hash()
|
||||
|
||||
makeErr := func(cc *libevm.AddressContext, stateVal common.Hash) error {
|
||||
return fmt.Errorf("Origin: %v Caller: %v Contract: %v State: %v", cc.Origin, cc.Caller, cc.Self, stateVal)
|
||||
}
|
||||
hooks := &hookstest.Stub{
|
||||
CanCreateContractFn: func(cc *libevm.AddressContext, s libevm.StateReader) error {
|
||||
return makeErr(cc, s.GetState(account, slot))
|
||||
},
|
||||
}
|
||||
hooks.RegisterForRules(t)
|
||||
|
||||
origin := rng.Address()
|
||||
caller := rng.Address()
|
||||
value := rng.Hash()
|
||||
code := rng.Bytes(8)
|
||||
salt := rng.Hash()
|
||||
|
||||
create := crypto.CreateAddress(caller, 0)
|
||||
create2 := crypto.CreateAddress2(caller, salt, crypto.Keccak256(code))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
create func(*vm.EVM) ([]byte, common.Address, uint64, error)
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "Create",
|
||||
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
|
||||
return evm.Create(vm.AccountRef(caller), code, 1e6, uint256.NewInt(0))
|
||||
},
|
||||
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create}, value),
|
||||
},
|
||||
{
|
||||
name: "Create2",
|
||||
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
|
||||
return evm.Create2(vm.AccountRef(caller), code, 1e6, uint256.NewInt(0), new(uint256.Int).SetBytes(salt[:]))
|
||||
},
|
||||
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create2}, value),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state, evm := ethtest.NewZeroEVM(t)
|
||||
state.SetState(account, slot, value)
|
||||
evm.TxContext.Origin = origin
|
||||
|
||||
_, _, _, err := tt.create(evm)
|
||||
require.EqualError(t, err, tt.wantErr.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/libevm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -38,6 +39,9 @@ type (
|
|||
)
|
||||
|
||||
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
||||
if p, override := evm.chainRules.Hooks().PrecompileOverride(addr); override {
|
||||
return p, p != nil
|
||||
}
|
||||
var precompiles map[common.Address]PrecompiledContract
|
||||
switch {
|
||||
case evm.chainRules.IsCancun:
|
||||
|
|
@ -224,7 +228,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
}
|
||||
|
||||
if isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
args := &evmCallArgs{evm, caller, addr, input, gas, value}
|
||||
ret, gas, err = args.RunPrecompiledContract(p, input, gas)
|
||||
} 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.
|
||||
|
|
@ -287,7 +292,8 @@ 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)
|
||||
args := &evmCallArgs{evm, caller, addr, input, gas, value}
|
||||
ret, gas, err = args.RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
|
|
@ -332,7 +338,8 @@ 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)
|
||||
args := &evmCallArgs{evm, caller, addr, input, gas, nil}
|
||||
ret, gas, err = args.RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
|
|
@ -381,7 +388,8 @@ 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)
|
||||
args := &evmCallArgs{evm, caller, addr, input, gas, nil}
|
||||
ret, gas, err = args.RunPrecompiledContract(p, input, gas)
|
||||
} 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'
|
||||
|
|
@ -420,6 +428,10 @@ func (c *codeAndHash) Hash() common.Hash {
|
|||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
|
||||
cc := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: address}
|
||||
if err := evm.chainRules.Hooks().CanCreateContract(cc, evm.StateDB); err != nil {
|
||||
return nil, common.Address{}, gas, err
|
||||
}
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
|
|
|
|||
7
core/vm/libevm_test.go
Normal file
7
core/vm/libevm_test.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package vm
|
||||
|
||||
// The original RunPrecompiledContract was migrated to being a method on
|
||||
// [evmCallArgs]. We need to replace it for use by regular geth tests.
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||
return (*evmCallArgs)(nil).RunPrecompiledContract(p, input, suppliedGas)
|
||||
}
|
||||
37
libevm/ethtest/evm.go
Normal file
37
libevm/ethtest/evm.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Package ethtest provides utility functions for use in testing
|
||||
// Ethereum-related functionality.
|
||||
package ethtest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// NewZeroEVM returns a new EVM backed by a [rawdb.NewMemoryDatabase]; all other
|
||||
// arguments to [vm.NewEVM] are the zero values of their respective types,
|
||||
// except for the use of [core.CanTransfer] and [core.Transfer] instead of nil
|
||||
// functions.
|
||||
func NewZeroEVM(tb testing.TB) (*state.StateDB, *vm.EVM) {
|
||||
tb.Helper()
|
||||
|
||||
sdb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
require.NoError(tb, err, "state.New()")
|
||||
|
||||
return sdb, vm.NewEVM(
|
||||
vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
},
|
||||
vm.TxContext{},
|
||||
sdb,
|
||||
¶ms.ChainConfig{},
|
||||
vm.Config{},
|
||||
)
|
||||
}
|
||||
41
libevm/ethtest/rand.go
Normal file
41
libevm/ethtest/rand.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package ethtest
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
// PseudoRand extends [rand.Rand] (*not* crypto/rand).
|
||||
type PseudoRand struct {
|
||||
*rand.Rand
|
||||
}
|
||||
|
||||
// NewPseudoRand returns a new PseudoRand with the given seed.
|
||||
func NewPseudoRand(seed uint64) *PseudoRand {
|
||||
return &PseudoRand{rand.New(rand.NewSource(seed))}
|
||||
}
|
||||
|
||||
// Address returns a pseudorandom address.
|
||||
func (r *PseudoRand) Address() (a common.Address) {
|
||||
r.Read(a[:])
|
||||
return a
|
||||
}
|
||||
|
||||
// AddressPtr returns a pointer to a pseudorandom address.
|
||||
func (r *PseudoRand) AddressPtr() *common.Address {
|
||||
a := r.Address()
|
||||
return &a
|
||||
}
|
||||
|
||||
// Hash returns a pseudorandom hash.
|
||||
func (r *PseudoRand) Hash() (h common.Hash) {
|
||||
r.Read(h[:])
|
||||
return h
|
||||
}
|
||||
|
||||
// Bytes returns `n` pseudorandom bytes.
|
||||
func (r *PseudoRand) Bytes(n uint) []byte {
|
||||
b := make([]byte, n)
|
||||
r.Read(b)
|
||||
return b
|
||||
}
|
||||
60
libevm/hookstest/stub.go
Normal file
60
libevm/hookstest/stub.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Package hookstest provides test doubles for testing subsets of libevm hooks.
|
||||
package hookstest
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/libevm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// A Stub is a test double for [params.ChainConfigHooks] and
|
||||
// [params.RulesHooks]. Each of the fields, if non-nil, back their respective
|
||||
// hook methods, which otherwise fall back to the default behaviour.
|
||||
type Stub struct {
|
||||
PrecompileOverrides map[common.Address]libevm.PrecompiledContract
|
||||
CanExecuteTransactionFn func(common.Address, *common.Address, libevm.StateReader) error
|
||||
CanCreateContractFn func(*libevm.AddressContext, libevm.StateReader) error
|
||||
}
|
||||
|
||||
// RegisterForRules clears any registered [params.Extras] and then registers s
|
||||
// as [params.RulesHooks], which are themselves cleared by the
|
||||
// [testing.TB.Cleanup] routine.
|
||||
func (s *Stub) RegisterForRules(tb testing.TB) {
|
||||
params.TestOnlyClearRegisteredExtras()
|
||||
params.RegisterExtras(params.Extras[params.NOOPHooks, Stub]{
|
||||
NewRules: func(_ *params.ChainConfig, _ *params.Rules, _ *params.NOOPHooks, blockNum *big.Int, isMerge bool, timestamp uint64) *Stub {
|
||||
return s
|
||||
},
|
||||
})
|
||||
tb.Cleanup(params.TestOnlyClearRegisteredExtras)
|
||||
}
|
||||
|
||||
func (s Stub) PrecompileOverride(a common.Address) (libevm.PrecompiledContract, bool) {
|
||||
if len(s.PrecompileOverrides) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
p, ok := s.PrecompileOverrides[a]
|
||||
return p, ok
|
||||
}
|
||||
|
||||
func (s Stub) CanExecuteTransaction(from common.Address, to *common.Address, sr libevm.StateReader) error {
|
||||
if f := s.CanExecuteTransactionFn; f != nil {
|
||||
return f(from, to, sr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Stub) CanCreateContract(cc *libevm.AddressContext, sr libevm.StateReader) error {
|
||||
if f := s.CanCreateContractFn; f != nil {
|
||||
return f(cc, sr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
params.ChainConfigHooks
|
||||
params.RulesHooks
|
||||
} = Stub{}
|
||||
20
libevm/interfaces_test.go
Normal file
20
libevm/interfaces_test.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package libevm_test
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/libevm"
|
||||
)
|
||||
|
||||
// IMPORTANT: if any of these break then the libevm copy MUST be updated.
|
||||
|
||||
// These two interfaces MUST be identical.
|
||||
var (
|
||||
// Each assignment demonstrates that the methods of the LHS interface are a
|
||||
// (non-strict) subset of the RHS interface's; both being possible
|
||||
// proves that they are identical.
|
||||
_ vm.PrecompiledContract = (libevm.PrecompiledContract)(nil)
|
||||
_ libevm.PrecompiledContract = (vm.PrecompiledContract)(nil)
|
||||
)
|
||||
|
||||
// StateReader MUST be a subset vm.StateDB.
|
||||
var _ libevm.StateReader = (vm.StateDB)(nil)
|
||||
52
libevm/libevm.go
Normal file
52
libevm/libevm.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package libevm
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// PrecompiledContract is an exact copy of vm.PrecompiledContract, mirrored here
|
||||
// for instances where importing that package would result in a circular
|
||||
// dependency.
|
||||
type PrecompiledContract interface {
|
||||
RequiredGas(input []byte) uint64
|
||||
Run(input []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// StateReader is a subset of vm.StateDB, exposing only methods that read from
|
||||
// but do not modify state. See method comments in vm.StateDB, which aren't
|
||||
// copied here as they risk becoming outdated.
|
||||
type StateReader interface {
|
||||
GetBalance(common.Address) *uint256.Int
|
||||
GetNonce(common.Address) uint64
|
||||
|
||||
GetCodeHash(common.Address) common.Hash
|
||||
GetCode(common.Address) []byte
|
||||
GetCodeSize(common.Address) int
|
||||
|
||||
GetRefund() uint64
|
||||
|
||||
GetCommittedState(common.Address, common.Hash) common.Hash
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
|
||||
GetTransientState(addr common.Address, key common.Hash) common.Hash
|
||||
|
||||
HasSelfDestructed(common.Address) bool
|
||||
|
||||
Exist(common.Address) bool
|
||||
Empty(common.Address) bool
|
||||
|
||||
AddressInAccessList(addr common.Address) bool
|
||||
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||
}
|
||||
|
||||
// AddressContext carries addresses available to contexts such as calls and
|
||||
// contract creation.
|
||||
//
|
||||
// With respect to contract creation, the Self address MAY be the predicted
|
||||
// address of the contract about to be deployed, which may not exist yet.
|
||||
type AddressContext struct {
|
||||
Origin common.Address // equivalent to vm.ORIGIN op code
|
||||
Caller common.Address // equivalent to vm.CALLER op code
|
||||
Self common.Address // equivalent to vm.ADDRESS op code
|
||||
}
|
||||
24
libevm/pseudo/constructor.go
Normal file
24
libevm/pseudo/constructor.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package pseudo
|
||||
|
||||
// A Constructor returns newly constructed [Type] instances for a pre-registered
|
||||
// concrete type.
|
||||
type Constructor interface {
|
||||
Zero() *Type
|
||||
NewPointer() *Type
|
||||
NilPointer() *Type
|
||||
}
|
||||
|
||||
// NewConstructor returns a [Constructor] that builds `T` [Type] instances.
|
||||
func NewConstructor[T any]() Constructor {
|
||||
return ctor[T]{}
|
||||
}
|
||||
|
||||
type ctor[T any] struct{}
|
||||
|
||||
func (ctor[T]) Zero() *Type { return Zero[T]().Type }
|
||||
func (ctor[T]) NilPointer() *Type { return Zero[*T]().Type }
|
||||
|
||||
func (ctor[T]) NewPointer() *Type {
|
||||
var x T
|
||||
return From(&x).Type
|
||||
}
|
||||
45
libevm/pseudo/constructor_test.go
Normal file
45
libevm/pseudo/constructor_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package pseudo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConstructor(t *testing.T) {
|
||||
testConstructor[uint](t)
|
||||
testConstructor[string](t)
|
||||
testConstructor[struct{ x string }](t)
|
||||
}
|
||||
|
||||
func testConstructor[T any](t *testing.T) {
|
||||
var zero T
|
||||
t.Run(fmt.Sprintf("%T", zero), func(t *testing.T) {
|
||||
ctor := NewConstructor[T]()
|
||||
|
||||
t.Run("NilPointer()", func(t *testing.T) {
|
||||
got := get[*T](t, ctor.NilPointer())
|
||||
assert.Nil(t, got)
|
||||
})
|
||||
|
||||
t.Run("NewPointer()", func(t *testing.T) {
|
||||
got := get[*T](t, ctor.NewPointer())
|
||||
require.NotNil(t, got)
|
||||
assert.Equal(t, zero, *got)
|
||||
})
|
||||
|
||||
t.Run("Zero()", func(t *testing.T) {
|
||||
got := get[T](t, ctor.Zero())
|
||||
assert.Equal(t, zero, got)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func get[T any](t *testing.T, typ *Type) (x T) {
|
||||
t.Helper()
|
||||
val, err := NewValue[T](typ)
|
||||
require.NoError(t, err, "NewValue[%T]()", x)
|
||||
return val.Get()
|
||||
}
|
||||
175
libevm/pseudo/type.go
Normal file
175
libevm/pseudo/type.go
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
// Package pseudo provides a bridge between generic and non-generic code via
|
||||
// pseudo-types and pseudo-values. With careful usage, there is minimal
|
||||
// reduction in type safety.
|
||||
//
|
||||
// Adding generic type parameters to anything (e.g. struct, function, etc)
|
||||
// "pollutes" all code that uses the generic type. Refactoring all uses isn't
|
||||
// always feasible, and a [Type] acts as an intermediate fix. Although their
|
||||
// constructors are generic, they are not, and they are instead coupled with a
|
||||
// generic [Value] that SHOULD be used for access.
|
||||
//
|
||||
// Packages typically SHOULD NOT expose a [Type] and SHOULD instead provide
|
||||
// users with a type-safe [Value].
|
||||
package pseudo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A Type wraps a strongly-typed value without exposing information about its
|
||||
// type. It can be used in lieu of a generic field / parameter.
|
||||
type Type struct {
|
||||
val value
|
||||
}
|
||||
|
||||
// A Value provides strongly-typed access to the payload carried by a [Type].
|
||||
type Value[T any] struct {
|
||||
t *Type
|
||||
}
|
||||
|
||||
// A Pseudo type couples a [Type] and a [Value]. If returned by a constructor
|
||||
// from this package, both wrap the same payload.
|
||||
type Pseudo[T any] struct {
|
||||
Type *Type
|
||||
Value *Value[T]
|
||||
}
|
||||
|
||||
// TypeAndValue is a convenience function for splitting the contents of `p`,
|
||||
// typically at construction.
|
||||
func (p *Pseudo[T]) TypeAndValue() (*Type, *Value[T]) {
|
||||
return p.Type, p.Value
|
||||
}
|
||||
|
||||
// From returns a Pseudo[T] constructed from `v`.
|
||||
func From[T any](v T) *Pseudo[T] {
|
||||
t := &Type{
|
||||
val: &concrete[T]{
|
||||
val: v,
|
||||
},
|
||||
}
|
||||
return &Pseudo[T]{t, MustNewValue[T](t)}
|
||||
}
|
||||
|
||||
// Zero is equivalent to [From] called with the [zero value] of type `T`. Note
|
||||
// that pointers, slices, maps, etc. will therefore be nil.
|
||||
//
|
||||
// [zero value]: https://go.dev/tour/basics/12
|
||||
func Zero[T any]() *Pseudo[T] {
|
||||
var x T
|
||||
return From[T](x)
|
||||
}
|
||||
|
||||
// Interface returns the wrapped value as an `any`, equivalent to
|
||||
// [reflect.Value.Interface]. Prefer [Value.Get].
|
||||
func (t *Type) Interface() any { return t.val.get() }
|
||||
|
||||
// NewValue constructs a [Value] from a [Type], first confirming that `t` wraps
|
||||
// a payload of type `T`.
|
||||
func NewValue[T any](t *Type) (*Value[T], error) {
|
||||
var x T
|
||||
if !t.val.canSetTo(x) {
|
||||
return nil, fmt.Errorf("cannot create *Value[%T] with *Type carrying %T", x, t.val.get())
|
||||
}
|
||||
return &Value[T]{t}, nil
|
||||
}
|
||||
|
||||
// MustNewValue is equivalent to [NewValue] except that it panics instead of
|
||||
// returning an error.
|
||||
func MustNewValue[T any](t *Type) *Value[T] {
|
||||
v, err := NewValue[T](t)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Get returns the value.
|
||||
func (a *Value[T]) Get() T { return a.t.val.get().(T) }
|
||||
|
||||
// Set sets the value.
|
||||
func (a *Value[T]) Set(v T) { a.t.val.mustSet(v) }
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (t *Type) MarshalJSON() ([]byte, error) { return t.val.MarshalJSON() }
|
||||
|
||||
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||
func (t *Type) UnmarshalJSON(b []byte) error { return t.val.UnmarshalJSON(b) }
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (v *Value[T]) MarshalJSON() ([]byte, error) { return v.t.MarshalJSON() }
|
||||
|
||||
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||
func (v *Value[T]) UnmarshalJSON(b []byte) error { return v.t.UnmarshalJSON(b) }
|
||||
|
||||
var _ = []interface {
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
}{
|
||||
(*Type)(nil),
|
||||
(*Value[struct{}])(nil),
|
||||
(*concrete[struct{}])(nil),
|
||||
}
|
||||
|
||||
// A value is a non-generic wrapper around a [concrete] struct.
|
||||
type value interface {
|
||||
get() any
|
||||
canSetTo(any) bool
|
||||
set(any) error
|
||||
mustSet(any)
|
||||
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
}
|
||||
|
||||
type concrete[T any] struct {
|
||||
val T
|
||||
}
|
||||
|
||||
func (c *concrete[T]) get() any { return c.val }
|
||||
|
||||
func (c *concrete[T]) canSetTo(v any) bool {
|
||||
_, ok := v.(T)
|
||||
return ok
|
||||
}
|
||||
|
||||
// An invalidTypeError is returned by [conrete.set] if the value is incompatible
|
||||
// with its type. This should never leave this package and exists only to
|
||||
// provide precise testing of unhappy paths.
|
||||
type invalidTypeError[T any] struct {
|
||||
SetTo any
|
||||
}
|
||||
|
||||
func (e *invalidTypeError[T]) Error() string {
|
||||
var t T
|
||||
return fmt.Sprintf("cannot set %T to %T", t, e.SetTo)
|
||||
}
|
||||
|
||||
func (c *concrete[T]) set(v any) error {
|
||||
vv, ok := v.(T)
|
||||
if !ok {
|
||||
// Other invariants in this implementation (aim to) guarantee that this
|
||||
// will never happen.
|
||||
return &invalidTypeError[T]{SetTo: v}
|
||||
}
|
||||
c.val = vv
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *concrete[T]) mustSet(v any) {
|
||||
if err := c.set(v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = 0 // for happy-path coverage inspection
|
||||
}
|
||||
|
||||
func (c *concrete[T]) MarshalJSON() ([]byte, error) { return json.Marshal(c.val) }
|
||||
|
||||
func (c *concrete[T]) UnmarshalJSON(b []byte) error {
|
||||
var v T
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
c.val = v
|
||||
return nil
|
||||
}
|
||||
79
libevm/pseudo/type_test.go
Normal file
79
libevm/pseudo/type_test.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package pseudo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestType(t *testing.T) {
|
||||
testType(t, "Zero[int]", Zero[int], 0, 42, "I'm not an int")
|
||||
testType(t, "Zero[string]", Zero[string], "", "hello, world", 99)
|
||||
|
||||
testType(
|
||||
t, "From[uint](314159)",
|
||||
func() *Pseudo[uint] {
|
||||
return From[uint](314159)
|
||||
},
|
||||
314159, 0, struct{}{},
|
||||
)
|
||||
|
||||
testType(t, "nil pointer", Zero[*float64], (*float64)(nil), new(float64), 0)
|
||||
}
|
||||
|
||||
func testType[T any](t *testing.T, name string, ctor func() *Pseudo[T], init T, setTo T, invalid any) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
typ, val := ctor().TypeAndValue()
|
||||
assert.Equal(t, init, val.Get())
|
||||
val.Set(setTo)
|
||||
assert.Equal(t, setTo, val.Get())
|
||||
|
||||
t.Run("set to invalid type", func(t *testing.T) {
|
||||
wantErr := &invalidTypeError[T]{SetTo: invalid}
|
||||
|
||||
assertError := func(t *testing.T, err any) {
|
||||
t.Helper()
|
||||
switch err := err.(type) {
|
||||
case *invalidTypeError[T]:
|
||||
assert.Equal(t, wantErr, err)
|
||||
default:
|
||||
t.Errorf("got error %v; want %v", err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("Set(%T{%v})", invalid, invalid), func(t *testing.T) {
|
||||
assertError(t, typ.val.set(invalid))
|
||||
})
|
||||
|
||||
t.Run(fmt.Sprintf("MustSet(%T{%v})", invalid, invalid), func(t *testing.T) {
|
||||
defer func() {
|
||||
assertError(t, recover())
|
||||
}()
|
||||
typ.val.mustSet(invalid)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("JSON round trip", func(t *testing.T) {
|
||||
buf, err := json.Marshal(typ)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, gotVal := Zero[T]().TypeAndValue()
|
||||
require.NoError(t, json.Unmarshal(buf, &got))
|
||||
assert.Equal(t, val.Get(), gotVal.Get())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func ExamplePseudo_TypeAndValue() {
|
||||
typ, val := From("hello").TypeAndValue()
|
||||
|
||||
// But, if only one is needed:
|
||||
typ = From("world").Type
|
||||
val = From("this isn't coupled to the Type").Value
|
||||
|
||||
_ = typ
|
||||
_ = val
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/libevm/pseudo"
|
||||
"github.com/ethereum/go-ethereum/params/forks"
|
||||
)
|
||||
|
||||
|
|
@ -365,6 +366,8 @@ type ChainConfig struct {
|
|||
// Various consensus engines
|
||||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||
Clique *CliqueConfig `json:"clique,omitempty"`
|
||||
|
||||
extra *pseudo.Type // See RegisterExtras()
|
||||
}
|
||||
|
||||
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
|
||||
|
|
@ -902,6 +905,8 @@ type Rules struct {
|
|||
IsBerlin, IsLondon bool
|
||||
IsMerge, IsShanghai, IsCancun, IsPrague bool
|
||||
IsVerkle bool
|
||||
|
||||
extra *pseudo.Type // See RegisterExtras()
|
||||
}
|
||||
|
||||
// Rules ensures c's ChainID is not nil.
|
||||
|
|
@ -912,7 +917,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
|||
}
|
||||
// disallow setting Merge out of order
|
||||
isMerge = isMerge && c.IsLondon(num)
|
||||
return Rules{
|
||||
r := Rules{
|
||||
ChainID: new(big.Int).Set(chainID),
|
||||
IsHomestead: c.IsHomestead(num),
|
||||
IsEIP150: c.IsEIP150(num),
|
||||
|
|
@ -930,4 +935,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
|||
IsPrague: isMerge && c.IsPrague(num, timestamp),
|
||||
IsVerkle: isMerge && c.IsVerkle(num, timestamp),
|
||||
}
|
||||
c.addRulesExtra(&r, num, isMerge, timestamp)
|
||||
return r
|
||||
}
|
||||
|
|
|
|||
232
params/config.libevm.go
Normal file
232
params/config.libevm.go
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/libevm/pseudo"
|
||||
)
|
||||
|
||||
// Extras are arbitrary payloads to be added as extra fields in [ChainConfig]
|
||||
// and [Rules] structs. See [RegisterExtras].
|
||||
type Extras[C ChainConfigHooks, R RulesHooks] struct {
|
||||
// NewRules, if non-nil is called at the end of [ChainConfig.Rules] with the
|
||||
// newly created [Rules] and other context from the method call. Its
|
||||
// returned value will be the extra payload of the [Rules]. If NewRules is
|
||||
// nil then so too will the [Rules] extra payload be a nil `*R`.
|
||||
//
|
||||
// NewRules MAY modify the [Rules] but MUST NOT modify the [ChainConfig].
|
||||
NewRules func(_ *ChainConfig, _ *Rules, _ *C, blockNum *big.Int, isMerge bool, timestamp uint64) *R
|
||||
}
|
||||
|
||||
// RegisterExtras registers the types `C` and `R` such that they are carried as
|
||||
// extra payloads in [ChainConfig] and [Rules] structs, respectively. It is
|
||||
// expected to be called in an `init()` function and MUST NOT be called more
|
||||
// than once. Both `C` and `R` MUST be structs.
|
||||
//
|
||||
// After registration, JSON unmarshalling of a [ChainConfig] will create a new
|
||||
// `*C` and unmarshal the JSON key "extra" into it. Conversely, JSON marshalling
|
||||
// will populate the "extra" key with the contents of the `*C`. Both the
|
||||
// [json.Marshaler] and [json.Unmarshaler] interfaces are honoured if
|
||||
// implemented by `C` and/or `R.`
|
||||
//
|
||||
// Calls to [ChainConfig.Rules] will call the `NewRules` function of the
|
||||
// registered [Extras] to create a new `*R`.
|
||||
//
|
||||
// The payloads can be accessed via the [ExtraPayloadGetter.FromChainConfig] and
|
||||
// [ExtraPayloadGetter.FromRules] methods of the getter returned by
|
||||
// RegisterExtras. Where stated in the interface definitions, they will also be
|
||||
// used as hooks to alter Ethereum behaviour; if this isn't desired then they
|
||||
// can embed [NOOPHooks] to satisfy either interface.
|
||||
func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPayloadGetter[C, R] {
|
||||
if registeredExtras != nil {
|
||||
panic("re-registration of Extras")
|
||||
}
|
||||
mustBeStruct[C]()
|
||||
mustBeStruct[R]()
|
||||
|
||||
getter := e.getter()
|
||||
registeredExtras = &extraConstructors{
|
||||
chainConfig: pseudo.NewConstructor[C](),
|
||||
rules: pseudo.NewConstructor[R](),
|
||||
newForRules: e.newForRules,
|
||||
getter: getter,
|
||||
}
|
||||
return getter
|
||||
}
|
||||
|
||||
// TestOnlyClearRegisteredExtras clears the [Extras] previously passed to
|
||||
// [RegisterExtras]. It panics if called from a non-testing call stack.
|
||||
//
|
||||
// In tests it SHOULD be called before every call to [RegisterExtras] and then
|
||||
// defer-called afterwards, either directly or via testing.TB.Cleanup(). This is
|
||||
// a workaround for the single-call limitation on [RegisterExtras].
|
||||
func TestOnlyClearRegisteredExtras() {
|
||||
pc := make([]uintptr, 10)
|
||||
runtime.Callers(0, pc)
|
||||
frames := runtime.CallersFrames(pc)
|
||||
for {
|
||||
f, more := frames.Next()
|
||||
if strings.Contains(f.File, "/testing/") || strings.HasSuffix(f.File, "_test.go") {
|
||||
registeredExtras = nil
|
||||
return
|
||||
}
|
||||
if !more {
|
||||
panic("no _test.go file in call stack")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// registeredExtras holds non-generic constructors for the [Extras] types
|
||||
// registered via [RegisterExtras].
|
||||
var registeredExtras *extraConstructors
|
||||
|
||||
type extraConstructors struct {
|
||||
chainConfig, rules pseudo.Constructor
|
||||
newForRules func(_ *ChainConfig, _ *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type
|
||||
// use top-level hooksFrom<X>() functions instead of these as they handle
|
||||
// instances where no [Extras] were registered.
|
||||
getter interface {
|
||||
hooksFromChainConfig(*ChainConfig) ChainConfigHooks
|
||||
hooksFromRules(*Rules) RulesHooks
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Extras[C, R]) newForRules(c *ChainConfig, r *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type {
|
||||
if e.NewRules == nil {
|
||||
return registeredExtras.rules.NilPointer()
|
||||
}
|
||||
rExtra := e.NewRules(c, r, e.getter().FromChainConfig(c), blockNum, isMerge, timestamp)
|
||||
return pseudo.From(rExtra).Type
|
||||
}
|
||||
|
||||
func (*Extras[C, R]) getter() (g ExtraPayloadGetter[C, R]) { return }
|
||||
|
||||
// mustBeStruct panics if `T` isn't a struct.
|
||||
func mustBeStruct[T any]() {
|
||||
if k := reflect.TypeFor[T]().Kind(); k != reflect.Struct {
|
||||
panic(notStructMessage[T]())
|
||||
}
|
||||
}
|
||||
|
||||
// notStructMessage returns the message with which [mustBeStruct] might panic.
|
||||
// It exists to avoid change-detector tests should the message contents change.
|
||||
func notStructMessage[T any]() string {
|
||||
var x T
|
||||
return fmt.Sprintf("%T is not a struct", x)
|
||||
}
|
||||
|
||||
// An ExtraPayloadGettter provides strongly typed access to the extra payloads
|
||||
// carried by [ChainConfig] and [Rules] structs. The only valid way to construct
|
||||
// a getter is by a call to [RegisterExtras].
|
||||
type ExtraPayloadGetter[C ChainConfigHooks, R RulesHooks] struct {
|
||||
_ struct{} // make godoc show unexported fields so nobody tries to make their own getter ;)
|
||||
}
|
||||
|
||||
// FromChainConfig returns the ChainConfig's extra payload.
|
||||
func (ExtraPayloadGetter[C, R]) FromChainConfig(c *ChainConfig) *C {
|
||||
return pseudo.MustNewValue[*C](c.extraPayload()).Get()
|
||||
}
|
||||
|
||||
// hooksFromChainConfig is equivalent to FromChainConfig(), but returns an
|
||||
// interface instead of the concrete type implementing it; this allows it to be
|
||||
// used in non-generic code. If the concrete-type value is nil (typically
|
||||
// because no [Extras] were registered) a [noopHooks] is returned so it can be
|
||||
// used without nil checks.
|
||||
func (e ExtraPayloadGetter[C, R]) hooksFromChainConfig(c *ChainConfig) ChainConfigHooks {
|
||||
if h := e.FromChainConfig(c); h != nil {
|
||||
return *h
|
||||
}
|
||||
return NOOPHooks{}
|
||||
}
|
||||
|
||||
// FromRules returns the Rules' extra payload.
|
||||
func (ExtraPayloadGetter[C, R]) FromRules(r *Rules) *R {
|
||||
return pseudo.MustNewValue[*R](r.extraPayload()).Get()
|
||||
}
|
||||
|
||||
// hooksFromRules is the [RulesHooks] equivalent of hooksFromChainConfig().
|
||||
func (e ExtraPayloadGetter[C, R]) hooksFromRules(r *Rules) RulesHooks {
|
||||
if h := e.FromRules(r); h != nil {
|
||||
return *h
|
||||
}
|
||||
return NOOPHooks{}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||
func (c *ChainConfig) UnmarshalJSON(data []byte) error {
|
||||
type raw ChainConfig // doesn't inherit methods so avoids recursing back here (infinitely)
|
||||
cc := &struct {
|
||||
*raw
|
||||
Extra *pseudo.Type `json:"extra"`
|
||||
}{
|
||||
raw: (*raw)(c), // embedded to achieve regular JSON unmarshalling
|
||||
}
|
||||
if e := registeredExtras; e != nil {
|
||||
cc.Extra = e.chainConfig.NilPointer() // `c.extra` is otherwise unexported
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, cc); err != nil {
|
||||
return err
|
||||
}
|
||||
c.extra = cc.Extra
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (c *ChainConfig) MarshalJSON() ([]byte, error) {
|
||||
// See UnmarshalJSON() for rationale.
|
||||
type raw ChainConfig
|
||||
cc := &struct {
|
||||
*raw
|
||||
Extra *pseudo.Type `json:"extra"`
|
||||
}{raw: (*raw)(c), Extra: c.extra}
|
||||
return json.Marshal(cc)
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
} = (*ChainConfig)(nil)
|
||||
|
||||
// addRulesExtra is called at the end of [ChainConfig.Rules]; it exists to
|
||||
// abstract the libevm-specific behaviour outside of original geth code.
|
||||
func (c *ChainConfig) addRulesExtra(r *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) {
|
||||
r.extra = nil
|
||||
if registeredExtras != nil {
|
||||
r.extra = registeredExtras.newForRules(c, r, blockNum, isMerge, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
// extraPayload returns the ChainConfig's extra payload iff [RegisterExtras] has
|
||||
// already been called. If the payload hasn't been populated (typically via
|
||||
// unmarshalling of JSON), a nil value is constructed and returned.
|
||||
func (c *ChainConfig) extraPayload() *pseudo.Type {
|
||||
if registeredExtras == nil {
|
||||
// This will only happen if someone constructs an [ExtraPayloadGetter]
|
||||
// directly, without a call to [RegisterExtras].
|
||||
//
|
||||
// See https://google.github.io/styleguide/go/best-practices#when-to-panic
|
||||
panic(fmt.Sprintf("%T.ExtraPayload() called before RegisterExtras()", c))
|
||||
}
|
||||
if c.extra == nil {
|
||||
c.extra = registeredExtras.chainConfig.NilPointer()
|
||||
}
|
||||
return c.extra
|
||||
}
|
||||
|
||||
// extraPayload is equivalent to [ChainConfig.extraPayload].
|
||||
func (r *Rules) extraPayload() *pseudo.Type {
|
||||
if registeredExtras == nil {
|
||||
// See ChainConfig.extraPayload() equivalent.
|
||||
panic(fmt.Sprintf("%T.ExtraPayload() called before RegisterExtras()", r))
|
||||
}
|
||||
if r.extra == nil {
|
||||
r.extra = registeredExtras.rules.NilPointer()
|
||||
}
|
||||
return r.extra
|
||||
}
|
||||
164
params/config.libevm_test.go
Normal file
164
params/config.libevm_test.go
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/libevm/pseudo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type rawJSON struct {
|
||||
json.RawMessage
|
||||
NOOPHooks
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
} = (*rawJSON)(nil)
|
||||
|
||||
func TestRegisterExtras(t *testing.T) {
|
||||
type (
|
||||
ccExtraA struct {
|
||||
A string `json:"a"`
|
||||
ChainConfigHooks
|
||||
}
|
||||
rulesExtraA struct {
|
||||
A string
|
||||
RulesHooks
|
||||
}
|
||||
ccExtraB struct {
|
||||
B string `json:"b"`
|
||||
ChainConfigHooks
|
||||
}
|
||||
rulesExtraB struct {
|
||||
B string
|
||||
RulesHooks
|
||||
}
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
register func()
|
||||
ccExtra *pseudo.Type
|
||||
wantRulesExtra any
|
||||
}{
|
||||
{
|
||||
name: "Rules payload copied from ChainConfig payload",
|
||||
register: func() {
|
||||
RegisterExtras(Extras[ccExtraA, rulesExtraA]{
|
||||
NewRules: func(cc *ChainConfig, r *Rules, ex *ccExtraA, _ *big.Int, _ bool, _ uint64) *rulesExtraA {
|
||||
return &rulesExtraA{
|
||||
A: ex.A,
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
ccExtra: pseudo.From(&ccExtraA{
|
||||
A: "hello",
|
||||
}).Type,
|
||||
wantRulesExtra: &rulesExtraA{
|
||||
A: "hello",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no NewForRules() function results in typed but nil pointer",
|
||||
register: func() {
|
||||
RegisterExtras(Extras[ccExtraB, rulesExtraB]{})
|
||||
},
|
||||
ccExtra: pseudo.From(&ccExtraB{
|
||||
B: "world",
|
||||
}).Type,
|
||||
wantRulesExtra: (*rulesExtraB)(nil),
|
||||
},
|
||||
{
|
||||
name: "custom JSON handling honoured",
|
||||
register: func() {
|
||||
RegisterExtras(Extras[rawJSON, struct{ RulesHooks }]{})
|
||||
},
|
||||
ccExtra: pseudo.From(&rawJSON{
|
||||
RawMessage: []byte(`"hello, world"`),
|
||||
}).Type,
|
||||
wantRulesExtra: (*struct{ RulesHooks })(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
TestOnlyClearRegisteredExtras()
|
||||
tt.register()
|
||||
defer TestOnlyClearRegisteredExtras()
|
||||
|
||||
in := &ChainConfig{
|
||||
ChainID: big.NewInt(142857),
|
||||
extra: tt.ccExtra,
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(in)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := new(ChainConfig)
|
||||
require.NoError(t, json.Unmarshal(buf, got))
|
||||
assert.Equal(t, tt.ccExtra.Interface(), got.extraPayload().Interface())
|
||||
assert.Equal(t, in, got)
|
||||
// TODO: do we need an explicit test of the JSON output, or is a
|
||||
// Marshal-Unmarshal round trip sufficient?
|
||||
|
||||
gotRules := got.Rules(nil, false, 0)
|
||||
assert.Equal(t, tt.wantRulesExtra, gotRules.extraPayload().Interface())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtrasPanic(t *testing.T) {
|
||||
TestOnlyClearRegisteredExtras()
|
||||
defer TestOnlyClearRegisteredExtras()
|
||||
|
||||
assertPanics(
|
||||
t, func() {
|
||||
new(ChainConfig).extraPayload()
|
||||
},
|
||||
"before RegisterExtras",
|
||||
)
|
||||
|
||||
assertPanics(
|
||||
t, func() {
|
||||
new(Rules).extraPayload()
|
||||
},
|
||||
"before RegisterExtras",
|
||||
)
|
||||
|
||||
assertPanics(
|
||||
t, func() {
|
||||
mustBeStruct[int]()
|
||||
},
|
||||
notStructMessage[int](),
|
||||
)
|
||||
|
||||
RegisterExtras(Extras[struct{ ChainConfigHooks }, struct{ RulesHooks }]{})
|
||||
|
||||
assertPanics(
|
||||
t, func() {
|
||||
RegisterExtras(Extras[struct{ ChainConfigHooks }, struct{ RulesHooks }]{})
|
||||
},
|
||||
"re-registration",
|
||||
)
|
||||
}
|
||||
|
||||
func assertPanics(t *testing.T, fn func(), wantContains string) {
|
||||
t.Helper()
|
||||
defer func() {
|
||||
switch r := recover().(type) {
|
||||
case nil:
|
||||
t.Error("function did not panic as expected")
|
||||
case string:
|
||||
assert.Contains(t, r, wantContains)
|
||||
default:
|
||||
t.Fatalf("BAD TEST SETUP: recover() got unsupported type %T", r)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
158
params/example.libevm_test.go
Normal file
158
params/example.libevm_test.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
// In practice, everything in this file except for the Example() function SHOULD
|
||||
// be a standalone package, typically called `extraparams`. As long as this new
|
||||
// package is imported anywhere, its init() function will register the "extra"
|
||||
// types, which can be accessed via [extraparams.FromChainConfig] and/or
|
||||
// [extraparams.FromRules]. In all other respects, the [params.ChainConfig] and
|
||||
// [params.Rules] types will act as expected.
|
||||
//
|
||||
// The Example() function demonstrates how the `extraparams` package might be
|
||||
// used from elsewhere.
|
||||
package params_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/libevm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// In practice this would be a regular init() function but nuances around the
|
||||
// testing of this package require it to be called in the Example().
|
||||
func initFn() {
|
||||
params.TestOnlyClearRegisteredExtras() // not necessary outside of the example
|
||||
// This registration makes *all* [params.ChainConfig] and [params.Rules]
|
||||
// instances respect the payload types. They do not need to be modified to
|
||||
// know about `extraparams`.
|
||||
getter = params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{
|
||||
NewRules: constructRulesExtra,
|
||||
})
|
||||
}
|
||||
|
||||
var getter params.ExtraPayloadGetter[ChainConfigExtra, RulesExtra]
|
||||
|
||||
// constructRulesExtra acts as an adjunct to the [params.ChainConfig.Rules]
|
||||
// method. Its primary purpose is to construct the extra payload for the
|
||||
// [params.Rules] but it MAY also modify the [params.Rules].
|
||||
func constructRulesExtra(c *params.ChainConfig, r *params.Rules, cEx *ChainConfigExtra, blockNum *big.Int, isMerge bool, timestamp uint64) *RulesExtra {
|
||||
return &RulesExtra{
|
||||
IsMyFork: cEx.MyForkTime != nil && *cEx.MyForkTime <= timestamp,
|
||||
timestamp: timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
// ChainConfigExtra can be any struct. Here it just mirrors a common pattern in
|
||||
// the standard [params.ChainConfig] struct.
|
||||
type ChainConfigExtra struct {
|
||||
MyForkTime *uint64 `json:"myForkTime"`
|
||||
}
|
||||
|
||||
// RulesExtra can be any struct. It too mirrors a common pattern in
|
||||
// [params.Rules].
|
||||
type RulesExtra struct {
|
||||
IsMyFork bool
|
||||
timestamp uint64
|
||||
|
||||
// (Optional) If not all hooks are desirable then embedding a [NOOPHooks]
|
||||
// allows the type to satisfy the [RulesHooks] interface, resulting in
|
||||
// default Ethereum behaviour.
|
||||
params.NOOPHooks
|
||||
}
|
||||
|
||||
// FromChainConfig returns the extra payload carried by the ChainConfig.
|
||||
func FromChainConfig(c *params.ChainConfig) *ChainConfigExtra {
|
||||
return getter.FromChainConfig(c)
|
||||
}
|
||||
|
||||
// FromRules returns the extra payload carried by the Rules.
|
||||
func FromRules(r *params.Rules) *RulesExtra {
|
||||
return getter.FromRules(r)
|
||||
}
|
||||
|
||||
// myForkPrecompiledContracts is analogous to the vm.PrecompiledContracts<Fork>
|
||||
// maps. Note [RulesExtra.PrecompileOverride] treatment of nil values here.
|
||||
var myForkPrecompiledContracts = map[common.Address]vm.PrecompiledContract{
|
||||
//...
|
||||
common.BytesToAddress([]byte{0x2}): nil, // i.e disabled
|
||||
//...
|
||||
}
|
||||
|
||||
// PrecompileOverride implements the required [params.RuleHooks] method.
|
||||
func (r RulesExtra) PrecompileOverride(addr common.Address) (_ libevm.PrecompiledContract, override bool) {
|
||||
if !r.IsMyFork {
|
||||
return nil, false
|
||||
}
|
||||
p, ok := myForkPrecompiledContracts[addr]
|
||||
// The returned boolean indicates whether or not [vm.EVMInterpreter] MUST
|
||||
// override the address, not what it returns as its own `isPrecompile`
|
||||
// boolean.
|
||||
//
|
||||
// Therefore returning `nil, true` here indicates that the precompile will
|
||||
// be disabled. Returning `false` here indicates that the default precompile
|
||||
// behaviour will be exhibited.
|
||||
//
|
||||
// The same pattern can alternatively be implemented with an explicit
|
||||
// `disabledPrecompiles` set to make the behaviour clearer.
|
||||
return p, ok
|
||||
}
|
||||
|
||||
// CanCreateContract implements the required [params.RuleHooks] method. Access
|
||||
// to state allows it to be configured on-chain however this is an optional
|
||||
// implementation detail.
|
||||
func (r RulesExtra) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error {
|
||||
if time.Unix(int64(r.timestamp), 0).UTC().Day() != int(time.Tuesday) {
|
||||
return errors.New("uh oh!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This example demonstrates how the rest of this file would be used from a
|
||||
// *different* package.
|
||||
func ExampleExtraPayloadGetter() {
|
||||
initFn() // Outside of an example this is unnecessary as the function will be a regular init().
|
||||
|
||||
const forkTime = 530003640
|
||||
jsonData := fmt.Sprintf(`{
|
||||
"chainId": 1234,
|
||||
"extra": {
|
||||
"myForkTime": %d
|
||||
}
|
||||
}`, forkTime)
|
||||
|
||||
// Because [params.RegisterExtras] has been called, unmarshalling a JSON
|
||||
// field of "extra" into a [params.ChainConfig] will populate a new value of
|
||||
// the registered type. This can be accessed with the [FromChainConfig]
|
||||
// function.
|
||||
config := new(params.ChainConfig)
|
||||
if err := json.Unmarshal([]byte(jsonData), config); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Chain ID", config.ChainID) // original geth fields work as expected
|
||||
|
||||
ccExtra := FromChainConfig(config) // extraparams.FromChainConfig() in practice
|
||||
if ccExtra != nil && ccExtra.MyForkTime != nil {
|
||||
fmt.Println("Fork time", *ccExtra.MyForkTime)
|
||||
}
|
||||
|
||||
for _, time := range []uint64{forkTime - 1, forkTime, forkTime + 1} {
|
||||
rules := config.Rules(nil, false, time)
|
||||
rExtra := FromRules(&rules) // extraparams.FromRules() in practice
|
||||
if rExtra != nil {
|
||||
fmt.Printf("IsMyFork at %v: %t\n", rExtra.timestamp, rExtra.IsMyFork)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Chain ID 1234
|
||||
// Fork time 530003640
|
||||
// IsMyFork at 530003639: false
|
||||
// IsMyFork at 530003640: true
|
||||
// IsMyFork at 530003641: true
|
||||
}
|
||||
83
params/hooks.libevm.go
Normal file
83
params/hooks.libevm.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/libevm"
|
||||
)
|
||||
|
||||
// ChainConfigHooks are required for all types registered as [Extras] for
|
||||
// [ChainConfig] payloads.
|
||||
type ChainConfigHooks interface{}
|
||||
|
||||
// TODO(arr4n): given the choice of whether a hook should be defined on a
|
||||
// ChainConfig or on the Rules, what are the guiding principles? A ChainConfig
|
||||
// carries the most general information while Rules benefit from "knowing" the
|
||||
// block number and timestamp. I am leaning towards the default choice being
|
||||
// on Rules (as it's trivial to copy information from ChainConfig to Rules in
|
||||
// [Extras.NewRules]) unless the call site only has access to a ChainConfig.
|
||||
|
||||
// RulesHooks are required for all types registered as [Extras] for [Rules]
|
||||
// payloads.
|
||||
type RulesHooks interface {
|
||||
RulesAllowlistHooks
|
||||
// PrecompileOverride signals whether or not the EVM interpreter MUST
|
||||
// override its treatment of the address when deciding if it is a
|
||||
// precompiled contract. If PrecompileOverride returns `true` then the
|
||||
// interpreter will treat the address as a precompile i.f.f the
|
||||
// [PrecompiledContract] is non-nil. If it returns `false` then the default
|
||||
// precompile behaviour is honoured.
|
||||
PrecompileOverride(common.Address) (_ libevm.PrecompiledContract, override bool)
|
||||
}
|
||||
|
||||
// RulesAllowlistHooks are a subset of [RulesHooks] that gate actions, signalled
|
||||
// by returning a nil (allowed) or non-nil (blocked) error.
|
||||
type RulesAllowlistHooks interface {
|
||||
CanCreateContract(*libevm.AddressContext, libevm.StateReader) error
|
||||
CanExecuteTransaction(from common.Address, to *common.Address, _ libevm.StateReader) error
|
||||
}
|
||||
|
||||
// Hooks returns the hooks registered with [RegisterExtras], or [NOOPHooks] if
|
||||
// none were registered.
|
||||
func (c *ChainConfig) Hooks() ChainConfigHooks {
|
||||
if e := registeredExtras; e != nil {
|
||||
return e.getter.hooksFromChainConfig(c)
|
||||
}
|
||||
return NOOPHooks{}
|
||||
}
|
||||
|
||||
// Hooks returns the hooks registered with [RegisterExtras], or [NOOPHooks] if
|
||||
// none were registered.
|
||||
func (r *Rules) Hooks() RulesHooks {
|
||||
if e := registeredExtras; e != nil {
|
||||
return e.getter.hooksFromRules(r)
|
||||
}
|
||||
return NOOPHooks{}
|
||||
}
|
||||
|
||||
// NOOPHooks implements both [ChainConfigHooks] and [RulesHooks] such that every
|
||||
// hook is a no-op. This allows it to be returned instead of a nil interface,
|
||||
// which would otherwise require every usage site to perform a nil check. It can
|
||||
// also be embedded in structs that only wish to implement a sub-set of hooks.
|
||||
// Use of a NOOPHooks is equivalent to default Ethereum behaviour.
|
||||
type NOOPHooks struct{}
|
||||
|
||||
var _ interface {
|
||||
ChainConfigHooks
|
||||
RulesHooks
|
||||
} = NOOPHooks{}
|
||||
|
||||
// CanExecuteTransaction allows all (otherwise valid) transactions.
|
||||
func (NOOPHooks) CanExecuteTransaction(_ common.Address, _ *common.Address, _ libevm.StateReader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanCreateContract allows all (otherwise valid) contract deployment.
|
||||
func (NOOPHooks) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrecompileOverride instructs the EVM interpreter to use the default
|
||||
// precompile behaviour.
|
||||
func (NOOPHooks) PrecompileOverride(common.Address) (libevm.PrecompiledContract, bool) {
|
||||
return nil, false
|
||||
}
|
||||
Loading…
Reference in a new issue