forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
- Loading branch information
Showing
22 changed files
with
1,516 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) | ||
}) | ||
} | ||
} |
Oops, something went wrong.