Skip to content

Commit

Permalink
test: vm.PrecompileEnvironment.Call() with pre-state tracer
Browse files Browse the repository at this point in the history
  • Loading branch information
ARR4N committed Oct 28, 2024
1 parent 585c1f3 commit e18a1e9
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 8 deletions.
54 changes: 50 additions & 4 deletions core/vm/contracts.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package vm_test

import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"reflect"
Expand All @@ -33,6 +34,8 @@ import (
"github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/core/vm"
"github.com/ava-labs/libevm/crypto"
"github.com/ava-labs/libevm/eth/tracers"
_ "github.com/ava-labs/libevm/eth/tracers/native"
"github.com/ava-labs/libevm/libevm"
"github.com/ava-labs/libevm/libevm/ethtest"
"github.com/ava-labs/libevm/libevm/hookstest"
Expand Down Expand Up @@ -338,7 +341,7 @@ func TestInheritReadOnly(t *testing.T) {
rng := ethtest.NewPseudoRand(42)
contractAddr := rng.Address()
state.CreateAccount(contractAddr)
state.SetCode(contractAddr, convertBytes[vm.OpCode, byte](contract))
state.SetCode(contractAddr, convertBytes[vm.OpCode, byte](contract...))

// (3)

Expand Down Expand Up @@ -404,7 +407,7 @@ func makeReturnProxy(t *testing.T, dest common.Address, call vm.OpCode) []vm.OpC
}

contract = append(contract, vm.PUSH20)
contract = append(contract, convertBytes[byte, vm.OpCode](dest[:])...)
contract = append(contract, convertBytes[byte, vm.OpCode](dest[:]...)...)

contract = append(contract,
p0, // gas
Expand All @@ -417,7 +420,7 @@ func makeReturnProxy(t *testing.T, dest common.Address, call vm.OpCode) []vm.OpC
return contract
}

func convertBytes[From ~byte, To ~byte](buf []From) []To {
func convertBytes[From ~byte, To ~byte](buf ...From) []To {
out := make([]To, len(buf))
for i, b := range buf {
out[i] = To(b)
Expand Down Expand Up @@ -672,7 +675,7 @@ func TestPrecompileMakeCall(t *testing.T) {
evm.Origin = eoa
state.CreateAccount(caller)
proxy := makeReturnProxy(t, sut, tt.incomingCallType)
state.SetCode(caller, convertBytes[vm.OpCode, byte](proxy))
state.SetCode(caller, convertBytes[vm.OpCode, byte](proxy...))

got, _, err := evm.Call(vm.AccountRef(eoa), caller, tt.eoaTxCallData, 1e6, uint256.NewInt(0))
require.NoError(t, err)
Expand All @@ -681,6 +684,49 @@ func TestPrecompileMakeCall(t *testing.T) {
}
}

func TestPrecompileCallWithTracer(t *testing.T) {
// The native pre-state tracer, when logging storage, assumes an invariant
// that is broken by a precompile calling another contract. This is a test
// of the fix, ensuring that an SLOADed value is properly handled by the
// tracer.

rng := ethtest.NewPseudoRand(42 * 142857)
precompile := rng.Address()
contract := rng.Address()

hooks := &hookstest.Stub{
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
precompile: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
return env.Call(contract, nil, suppliedGas, uint256.NewInt(0))
}),
},
}
hooks.Register(t)

state, evm := ethtest.NewZeroEVM(t)
evm.GasPrice = big.NewInt(1)

state.CreateAccount(contract)
var zeroHash common.Hash
value := rng.Hash()
state.SetState(contract, zeroHash, value)
state.SetCode(contract, convertBytes[vm.OpCode, byte](vm.PC, vm.SLOAD))

const tracerName = "prestateTracer"
tracer, err := tracers.DefaultDirectory.New(tracerName, nil, nil)
require.NoErrorf(t, err, "tracers.DefaultDirectory.New(%q)", tracerName)
evm.Config.Tracer = tracer

_, _, err = evm.Call(vm.AccountRef(rng.Address()), precompile, []byte{}, 1e6, uint256.NewInt(0))
require.NoError(t, err, "evm.Call([precompile that calls regular contract])")

gotJSON, err := tracer.GetResult()
require.NoErrorf(t, err, "%T.GetResult()", tracer)
var got map[common.Address]struct{ Storage map[common.Hash]common.Hash }
require.NoErrorf(t, json.Unmarshal(gotJSON, &got), "json.Unmarshal(%T.GetResult(), %T)", tracer, &got)
require.Equal(t, value, got[contract].Storage[zeroHash], "value loaded with SLOAD")
}

//nolint:testableexamples // Including output would only make the example more complicated and hide the true intent
func ExamplePrecompileEnvironment() {
// To determine the actual caller of a precompile, as against the effective
Expand Down
9 changes: 5 additions & 4 deletions eth/tracers/native/prestate_libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ import (
)

// CaptureEnter implements the [vm.EVMLogger] hook for entering a new scope (via
// call, create or selfdestruct).
// CALL*, CREATE or SELFDESTRUCT).
func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Although [prestateTracer.lookupStorage] expects
// [prestateTracer.lookupAccount] to have been called, the invariant is
// maintained by [prestateTracer.CaptureState] when encountering an opcode
// maintained by [prestateTracer.CaptureState] when it encounters an OpCode
// corresponding to scope entry. This, however, doesn't work when using a
// call method exposed by [vm.PrecompileEnvironment], so we expose it here
// as it is idempotent.
// call method exposed by [vm.PrecompileEnvironment], and is restored by a
// call to this CaptureEnter implementation. Note that lookupAccount(x) is
// idempotent.
t.lookupAccount(to)
}

0 comments on commit e18a1e9

Please sign in to comment.