Skip to content

chore: squash arr4n/libevm into libevm #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
61df9b3
feat: pseudo-generic extra payloads in `params.ChainConfig` and `para…
ARR4N Aug 22, 2024
5cbdefb
feat: `params.ExtraPayloadGetter` for end-user type safety
ARR4N Aug 22, 2024
4548d73
refactor: payloads only available through `params.ExtraPayloadGetter`
ARR4N Aug 22, 2024
d209bb6
chore: make `libevm/examples/extraparams` a `params` testable example
ARR4N Aug 22, 2024
3944da5
doc: `libevm/pseudo` package comments and improved readability
ARR4N Aug 23, 2024
6d3dec1
doc: `params.*Extra*` comments and improved readability
ARR4N Aug 23, 2024
e196474
doc: `params.ExtraPayloadGetter` comments and improved readability
ARR4N Aug 23, 2024
25e6704
doc: `params/config.libevm_test.go` comments and improved readability
ARR4N Aug 23, 2024
6d75c12
refactor: simplify `params.ChainConfig.UnmarshalJSON()`
ARR4N Aug 26, 2024
a6742fd
refactor: abstract new/nil-pointer creation into `pseudo.Constructor`s
ARR4N Aug 26, 2024
fc28b9c
feat: precompile override via `params.Extras` hooks
ARR4N Sep 2, 2024
fb916fe
doc: flesh out `PrecompileOverride()` in example
ARR4N Sep 2, 2024
7d1d206
doc: complete commentary and improve readability
ARR4N Sep 2, 2024
9dee6b2
refactor: `ChainConfig.Hooks()` + `Rules` equivalent
ARR4N Sep 3, 2024
639aa1c
chore: rename precompiles test file in keeping with geth equivalent
ARR4N Sep 3, 2024
b642795
feat: stateful precompiles + allowlist hooks
ARR4N Sep 3, 2024
2867af1
fix: `StateTransition.canExecuteTransaction()` used `msg.From` instea…
ARR4N Sep 3, 2024
5d63223
test: `params.RulesHooks.CanCreateContract` integration
ARR4N Sep 4, 2024
85a3258
test: `params.RulesHooks.CanExecuteTransaction` integration
ARR4N Sep 5, 2024
76fcc7e
test: `vm.NewStatefulPrecompile()` integration
ARR4N Sep 5, 2024
4891c50
refactor: simplify test of `CanCreateContract`
ARR4N Sep 5, 2024
9162508
refactor: abstract generation of random `Address`/`Hash` values
ARR4N Sep 5, 2024
6f7cb2e
doc: full documentation + readability refactoring/renaming
ARR4N Sep 5, 2024
e6840aa
fix: remove circular dependency in tests
ARR4N Sep 6, 2024
9c2c214
Merge pull request #1 from ava-labs/arr4n/libevm-params-extras
ARR4N Sep 10, 2024
a33baa3
Merge pull request #2 from ava-labs/arr4n/libevm-precompile-overrides
ARR4N Sep 10, 2024
95774a4
Merge pull request #3 from ava-labs/arr4n/libevm-stateful-precompiles
ARR4N Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down
9 changes: 9 additions & 0 deletions core/state_transition.libevm.go
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)
}
40 changes: 40 additions & 0 deletions core/state_transition.libevm_test.go
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())
}
4 changes: 2 additions & 2 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
83 changes: 83 additions & 0 deletions core/vm/contracts.libevm.go
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,
}
)
178 changes: 178 additions & 0 deletions core/vm/contracts.libevm_test.go
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())
})
}
}
Loading