mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 02:40:45 +00:00
feat: vm.PrecompileEnvironment access to block info (#27)
This commit is contained in:
parent
c5da3ca99e
commit
ab357e0279
6 changed files with 122 additions and 13 deletions
|
|
@ -73,6 +73,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
||||||
BlobBaseFee: blobBaseFee,
|
BlobBaseFee: blobBaseFee,
|
||||||
GasLimit: header.GasLimit,
|
GasLimit: header.GasLimit,
|
||||||
Random: random,
|
Random: random,
|
||||||
|
Header: header,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/libevm"
|
"github.com/ethereum/go-ethereum/libevm"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
@ -99,6 +101,10 @@ type PrecompileEnvironment interface {
|
||||||
// ReadOnlyState will always be non-nil.
|
// ReadOnlyState will always be non-nil.
|
||||||
ReadOnlyState() libevm.StateReader
|
ReadOnlyState() libevm.StateReader
|
||||||
Addresses() *libevm.AddressContext
|
Addresses() *libevm.AddressContext
|
||||||
|
|
||||||
|
BlockHeader() (types.Header, error)
|
||||||
|
BlockNumber() *big.Int
|
||||||
|
BlockTime() uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
@ -152,6 +158,24 @@ func (args *evmCallArgs) Addresses() *libevm.AddressContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (args *evmCallArgs) BlockHeader() (types.Header, error) {
|
||||||
|
hdr := args.evm.Context.Header
|
||||||
|
if hdr == nil {
|
||||||
|
// Although [core.NewEVMBlockContext] sets the field and is in the
|
||||||
|
// typical hot path (e.g. miner), there are other ways to create a
|
||||||
|
// [vm.BlockContext] (e.g. directly in tests) that may result in no
|
||||||
|
// available header.
|
||||||
|
return types.Header{}, fmt.Errorf("nil %T in current %T", hdr, args.evm.Context)
|
||||||
|
}
|
||||||
|
return *hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (args *evmCallArgs) BlockNumber() *big.Int {
|
||||||
|
return new(big.Int).Set(args.evm.Context.BlockNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (args *evmCallArgs) BlockTime() uint64 { return args.evm.Context.Time }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// These lock in the assumptions made when implementing [evmCallArgs]. If
|
// These lock in the assumptions made when implementing [evmCallArgs]. If
|
||||||
// these break then the struct fields SHOULD be changed to match these
|
// these break then the struct fields SHOULD be changed to match these
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package vm_test
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
@ -11,6 +13,8 @@ import (
|
||||||
"golang.org/x/exp/rand"
|
"golang.org/x/exp/rand"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/libevm"
|
"github.com/ethereum/go-ethereum/libevm"
|
||||||
|
|
@ -79,6 +83,31 @@ func TestPrecompileOverride(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type statefulPrecompileOutput struct {
|
||||||
|
Caller, Self common.Address
|
||||||
|
StateValue common.Hash
|
||||||
|
ReadOnly bool
|
||||||
|
BlockNumber, Difficulty *big.Int
|
||||||
|
BlockTime uint64
|
||||||
|
Input []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o statefulPrecompileOutput) String() string {
|
||||||
|
var lines []string
|
||||||
|
out := reflect.ValueOf(o)
|
||||||
|
for i, n := 0, out.NumField(); i < n; i++ {
|
||||||
|
name := out.Type().Field(i).Name
|
||||||
|
fld := out.Field(i).Interface()
|
||||||
|
|
||||||
|
verb := "%v"
|
||||||
|
if _, ok := fld.([]byte); ok {
|
||||||
|
verb = "%#x"
|
||||||
|
}
|
||||||
|
lines = append(lines, fmt.Sprintf("%s: "+verb, name, fld))
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewStatefulPrecompile(t *testing.T) {
|
func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
rng := ethtest.NewPseudoRand(314159)
|
rng := ethtest.NewPseudoRand(314159)
|
||||||
precompile := rng.Address()
|
precompile := rng.Address()
|
||||||
|
|
@ -87,20 +116,27 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
const gasLimit = 1e6
|
const gasLimit = 1e6
|
||||||
gasCost := rng.Uint64n(gasLimit)
|
gasCost := rng.Uint64n(gasLimit)
|
||||||
|
|
||||||
makeOutput := func(caller, self common.Address, input []byte, stateVal common.Hash, readOnly bool) []byte {
|
|
||||||
return []byte(fmt.Sprintf(
|
|
||||||
"Caller: %v Precompile: %v State: %v Read-only: %t, Input: %#x",
|
|
||||||
caller, self, stateVal, readOnly, input,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
run := func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
|
run := func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
|
||||||
if got, want := env.StateDB() != nil, !env.ReadOnly(); got != want {
|
if got, want := env.StateDB() != nil, !env.ReadOnly(); got != want {
|
||||||
return nil, 0, fmt.Errorf("PrecompileEnvironment().StateDB() must be non-nil i.f.f. not read-only; got non-nil? %t; want %t", got, want)
|
return nil, 0, fmt.Errorf("PrecompileEnvironment().StateDB() must be non-nil i.f.f. not read-only; got non-nil? %t; want %t", got, want)
|
||||||
}
|
}
|
||||||
|
hdr, err := env.BlockHeader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
addrs := env.Addresses()
|
addrs := env.Addresses()
|
||||||
val := env.ReadOnlyState().GetState(precompile, slot)
|
out := &statefulPrecompileOutput{
|
||||||
return makeOutput(addrs.Caller, addrs.Self, input, val, env.ReadOnly()), suppliedGas - gasCost, nil
|
Caller: addrs.Caller,
|
||||||
|
Self: addrs.Self,
|
||||||
|
StateValue: env.ReadOnlyState().GetState(precompile, slot),
|
||||||
|
ReadOnly: env.ReadOnly(),
|
||||||
|
BlockNumber: env.BlockNumber(),
|
||||||
|
BlockTime: env.BlockTime(),
|
||||||
|
Difficulty: hdr.Difficulty,
|
||||||
|
Input: input,
|
||||||
|
}
|
||||||
|
return []byte(out.String()), suppliedGas - gasCost, nil
|
||||||
}
|
}
|
||||||
hooks := &hookstest.Stub{
|
hooks := &hookstest.Stub{
|
||||||
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
||||||
|
|
@ -109,11 +145,18 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
}
|
}
|
||||||
hooks.Register(t)
|
hooks.Register(t)
|
||||||
|
|
||||||
|
header := &types.Header{
|
||||||
|
Number: rng.BigUint64(),
|
||||||
|
Time: rng.Uint64(),
|
||||||
|
Difficulty: rng.BigUint64(),
|
||||||
|
}
|
||||||
caller := rng.Address()
|
caller := rng.Address()
|
||||||
input := rng.Bytes(8)
|
input := rng.Bytes(8)
|
||||||
value := rng.Hash()
|
value := rng.Hash()
|
||||||
|
|
||||||
state, evm := ethtest.NewZeroEVM(t)
|
state, evm := ethtest.NewZeroEVM(t, ethtest.WithBlockContext(
|
||||||
|
core.NewEVMBlockContext(header, nil, rng.AddressPtr()),
|
||||||
|
))
|
||||||
state.SetState(precompile, slot, value)
|
state.SetState(precompile, slot, value)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -155,12 +198,21 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
wantReturnData := makeOutput(caller, precompile, input, value, tt.wantReadOnly)
|
wantReturnData := statefulPrecompileOutput{
|
||||||
|
Caller: caller,
|
||||||
|
Self: precompile,
|
||||||
|
StateValue: value,
|
||||||
|
ReadOnly: tt.wantReadOnly,
|
||||||
|
BlockNumber: header.Number,
|
||||||
|
BlockTime: header.Time,
|
||||||
|
Difficulty: header.Difficulty,
|
||||||
|
Input: input,
|
||||||
|
}.String()
|
||||||
wantGasLeft := gasLimit - gasCost
|
wantGasLeft := gasLimit - gasCost
|
||||||
|
|
||||||
gotReturnData, gotGasLeft, err := tt.call()
|
gotReturnData, gotGasLeft, err := tt.call()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, string(wantReturnData), string(gotReturnData))
|
assert.Equal(t, wantReturnData, string(gotReturnData))
|
||||||
assert.Equal(t, wantGasLeft, gotGasLeft)
|
assert.Equal(t, wantGasLeft, gotGasLeft)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ type BlockContext struct {
|
||||||
BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price)
|
BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price)
|
||||||
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
|
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
|
||||||
Random *common.Hash // Provides information for PREVRANDAO
|
Random *common.Hash // Provides information for PREVRANDAO
|
||||||
|
|
||||||
|
Header *types.Header // libevm addition; not guaranteed to be set
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxContext provides the EVM with information about a transaction.
|
// TxContext provides the EVM with information about a transaction.
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@ import (
|
||||||
// arguments to [vm.NewEVM] are the zero values of their respective types,
|
// 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
|
// except for the use of [core.CanTransfer] and [core.Transfer] instead of nil
|
||||||
// functions.
|
// functions.
|
||||||
func NewZeroEVM(tb testing.TB) (*state.StateDB, *vm.EVM) {
|
func NewZeroEVM(tb testing.TB, opts ...EVMOption) (*state.StateDB, *vm.EVM) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
sdb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
sdb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||||
require.NoError(tb, err, "state.New()")
|
require.NoError(tb, err, "state.New()")
|
||||||
|
|
||||||
return sdb, vm.NewEVM(
|
vm := vm.NewEVM(
|
||||||
vm.BlockContext{
|
vm.BlockContext{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
|
|
@ -35,4 +35,27 @@ func NewZeroEVM(tb testing.TB) (*state.StateDB, *vm.EVM) {
|
||||||
¶ms.ChainConfig{},
|
¶ms.ChainConfig{},
|
||||||
vm.Config{},
|
vm.Config{},
|
||||||
)
|
)
|
||||||
|
for _, o := range opts {
|
||||||
|
o.apply(vm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdb, vm
|
||||||
|
}
|
||||||
|
|
||||||
|
// An EVMOption configures the EVM returned by [NewZeroEVM].
|
||||||
|
type EVMOption interface {
|
||||||
|
apply(*vm.EVM)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcOption func(*vm.EVM)
|
||||||
|
|
||||||
|
var _ EVMOption = funcOption(nil)
|
||||||
|
|
||||||
|
func (f funcOption) apply(vm *vm.EVM) { f(vm) }
|
||||||
|
|
||||||
|
// WithBlockContext overrides the default context.
|
||||||
|
func WithBlockContext(c vm.BlockContext) EVMOption {
|
||||||
|
return funcOption(func(vm *vm.EVM) {
|
||||||
|
vm.Context = c
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package ethtest
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"golang.org/x/exp/rand"
|
"golang.org/x/exp/rand"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -40,3 +42,8 @@ func (r *PseudoRand) Bytes(n uint) []byte {
|
||||||
r.Read(b) //nolint:gosec,errcheck // Guaranteed nil error
|
r.Read(b) //nolint:gosec,errcheck // Guaranteed nil error
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Big returns [rand.Rand.Uint64] as a [big.Int].
|
||||||
|
func (r *PseudoRand) BigUint64() *big.Int {
|
||||||
|
return new(big.Int).SetUint64(r.Uint64())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue