Skip to content

Commit fbe5e53

Browse files
committed
Merge branch 'libevm' into arr4n/chainconfig-check-hooks
2 parents 0fc930a + ab357e0 commit fbe5e53

File tree

10 files changed

+187
-66
lines changed

10 files changed

+187
-66
lines changed

core/evm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
7373
BlobBaseFee: blobBaseFee,
7474
GasLimit: header.GasLimit,
7575
Random: random,
76+
Header: header,
7677
}
7778
}
7879

core/vm/contracts.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,7 @@ func (args *evmCallArgs) RunPrecompiledContract(p PrecompiledContract, input []b
174174
return nil, 0, ErrOutOfGas
175175
}
176176
suppliedGas -= gasCost
177-
output, err := args.run(p, input)
178-
return output, suppliedGas, err
177+
return args.run(p, input, suppliedGas)
179178
}
180179

181180
// ECRECOVER implemented as a native contract.

core/vm/contracts.libevm.go

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package vm
22

33
import (
44
"fmt"
5+
"math/big"
56

67
"github.com/holiman/uint256"
78

89
"github.com/ethereum/go-ethereum/common"
10+
"github.com/ethereum/go-ethereum/core/types"
911
"github.com/ethereum/go-ethereum/libevm"
1012
"github.com/ethereum/go-ethereum/params"
1113
)
@@ -50,42 +52,43 @@ const (
5052

5153
// run runs the [PrecompiledContract], differentiating between stateful and
5254
// regular types.
53-
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
55+
func (args *evmCallArgs) run(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
5456
if p, ok := p.(statefulPrecompile); ok {
55-
return p.run(args, input)
57+
return p(args, input, suppliedGas)
5658
}
57-
return p.Run(input)
59+
// Gas consumption for regular precompiles was already handled by the native
60+
// RunPrecompiledContract(), which called this method.
61+
ret, err = p.Run(input)
62+
return ret, suppliedGas, err
5863
}
5964

60-
// PrecompiledStatefulRun is the stateful equivalent of the Run() method of a
65+
// PrecompiledStatefulContract is the stateful equivalent of a
6166
// [PrecompiledContract].
62-
type PrecompiledStatefulRun func(env PrecompileEnvironment, input []byte) ([]byte, error)
67+
type PrecompiledStatefulContract func(env PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error)
6368

6469
// NewStatefulPrecompile constructs a new PrecompiledContract that can be used
6570
// via an [EVM] instance but MUST NOT be called directly; a direct call to Run()
6671
// reserves the right to panic. See other requirements defined in the comments
6772
// on [PrecompiledContract].
68-
func NewStatefulPrecompile(run PrecompiledStatefulRun, requiredGas func([]byte) uint64) PrecompiledContract {
69-
return statefulPrecompile{
70-
gas: requiredGas,
71-
run: run,
72-
}
73+
func NewStatefulPrecompile(run PrecompiledStatefulContract) PrecompiledContract {
74+
return statefulPrecompile(run)
7375
}
7476

75-
type statefulPrecompile struct {
76-
gas func([]byte) uint64
77-
run PrecompiledStatefulRun
78-
}
77+
// statefulPrecompile implements the [PrecompiledContract] interface to allow a
78+
// [PrecompiledStatefulContract] to be carried with regular geth plumbing. The
79+
// methods are defined on this unexported type instead of directly on
80+
// [PrecompiledStatefulContract] to hide implementation details.
81+
type statefulPrecompile PrecompiledStatefulContract
7982

80-
func (p statefulPrecompile) RequiredGas(input []byte) uint64 {
81-
return p.gas(input)
82-
}
83+
// RequiredGas always returns zero as this gas is consumed by native geth code
84+
// before the contract is run.
85+
func (statefulPrecompile) RequiredGas([]byte) uint64 { return 0 }
8386

8487
func (p statefulPrecompile) Run([]byte) ([]byte, error) {
8588
// https://google.github.io/styleguide/go/best-practices.html#when-to-panic
8689
// This would indicate an API misuse and would occur in tests, not in
8790
// production.
88-
panic(fmt.Sprintf("BUG: call to %T.Run(); MUST call %T", p, p.run))
91+
panic(fmt.Sprintf("BUG: call to %T.Run(); MUST call %T itself", p, p))
8992
}
9093

9194
// A PrecompileEnvironment provides information about the context in which a
@@ -98,6 +101,10 @@ type PrecompileEnvironment interface {
98101
// ReadOnlyState will always be non-nil.
99102
ReadOnlyState() libevm.StateReader
100103
Addresses() *libevm.AddressContext
104+
105+
BlockHeader() (types.Header, error)
106+
BlockNumber() *big.Int
107+
BlockTime() uint64
101108
}
102109

103110
//
@@ -151,6 +158,24 @@ func (args *evmCallArgs) Addresses() *libevm.AddressContext {
151158
}
152159
}
153160

161+
func (args *evmCallArgs) BlockHeader() (types.Header, error) {
162+
hdr := args.evm.Context.Header
163+
if hdr == nil {
164+
// Although [core.NewEVMBlockContext] sets the field and is in the
165+
// typical hot path (e.g. miner), there are other ways to create a
166+
// [vm.BlockContext] (e.g. directly in tests) that may result in no
167+
// available header.
168+
return types.Header{}, fmt.Errorf("nil %T in current %T", hdr, args.evm.Context)
169+
}
170+
return *hdr, nil
171+
}
172+
173+
func (args *evmCallArgs) BlockNumber() *big.Int {
174+
return new(big.Int).Set(args.evm.Context.BlockNumber)
175+
}
176+
177+
func (args *evmCallArgs) BlockTime() uint64 { return args.evm.Context.Time }
178+
154179
var (
155180
// These lock in the assumptions made when implementing [evmCallArgs]. If
156181
// these break then the struct fields SHOULD be changed to match these

core/vm/contracts.libevm_test.go

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package vm_test
33
import (
44
"fmt"
55
"math/big"
6+
"reflect"
7+
"strings"
68
"testing"
79

810
"github.com/holiman/uint256"
@@ -11,6 +13,8 @@ import (
1113
"golang.org/x/exp/rand"
1214

1315
"github.com/ethereum/go-ethereum/common"
16+
"github.com/ethereum/go-ethereum/core"
17+
"github.com/ethereum/go-ethereum/core/types"
1418
"github.com/ethereum/go-ethereum/core/vm"
1519
"github.com/ethereum/go-ethereum/crypto"
1620
"github.com/ethereum/go-ethereum/libevm"
@@ -79,6 +83,31 @@ func TestPrecompileOverride(t *testing.T) {
7983
}
8084
}
8185

86+
type statefulPrecompileOutput struct {
87+
Caller, Self common.Address
88+
StateValue common.Hash
89+
ReadOnly bool
90+
BlockNumber, Difficulty *big.Int
91+
BlockTime uint64
92+
Input []byte
93+
}
94+
95+
func (o statefulPrecompileOutput) String() string {
96+
var lines []string
97+
out := reflect.ValueOf(o)
98+
for i, n := 0, out.NumField(); i < n; i++ {
99+
name := out.Type().Field(i).Name
100+
fld := out.Field(i).Interface()
101+
102+
verb := "%v"
103+
if _, ok := fld.([]byte); ok {
104+
verb = "%#x"
105+
}
106+
lines = append(lines, fmt.Sprintf("%s: "+verb, name, fld))
107+
}
108+
return strings.Join(lines, "\n")
109+
}
110+
82111
func TestNewStatefulPrecompile(t *testing.T) {
83112
rng := ethtest.NewPseudoRand(314159)
84113
precompile := rng.Address()
@@ -87,38 +116,47 @@ func TestNewStatefulPrecompile(t *testing.T) {
87116
const gasLimit = 1e6
88117
gasCost := rng.Uint64n(gasLimit)
89118

90-
makeOutput := func(caller, self common.Address, input []byte, stateVal common.Hash, readOnly bool) []byte {
91-
return []byte(fmt.Sprintf(
92-
"Caller: %v Precompile: %v State: %v Read-only: %t, Input: %#x",
93-
caller, self, stateVal, readOnly, input,
94-
))
95-
}
96-
run := func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
119+
run := func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
97120
if got, want := env.StateDB() != nil, !env.ReadOnly(); got != want {
98-
return nil, fmt.Errorf("PrecompileEnvironment().StateDB() must be non-nil i.f.f. not read-only; got non-nil? %t; want %t", got, want)
121+
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)
122+
}
123+
hdr, err := env.BlockHeader()
124+
if err != nil {
125+
return nil, 0, err
99126
}
100127

101128
addrs := env.Addresses()
102-
val := env.ReadOnlyState().GetState(precompile, slot)
103-
return makeOutput(addrs.Caller, addrs.Self, input, val, env.ReadOnly()), nil
129+
out := &statefulPrecompileOutput{
130+
Caller: addrs.Caller,
131+
Self: addrs.Self,
132+
StateValue: env.ReadOnlyState().GetState(precompile, slot),
133+
ReadOnly: env.ReadOnly(),
134+
BlockNumber: env.BlockNumber(),
135+
BlockTime: env.BlockTime(),
136+
Difficulty: hdr.Difficulty,
137+
Input: input,
138+
}
139+
return []byte(out.String()), suppliedGas - gasCost, nil
104140
}
105141
hooks := &hookstest.Stub{
106142
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
107-
precompile: vm.NewStatefulPrecompile(
108-
run,
109-
func(b []byte) uint64 {
110-
return gasCost
111-
},
112-
),
143+
precompile: vm.NewStatefulPrecompile(run),
113144
},
114145
}
115146
hooks.Register(t)
116147

148+
header := &types.Header{
149+
Number: rng.BigUint64(),
150+
Time: rng.Uint64(),
151+
Difficulty: rng.BigUint64(),
152+
}
117153
caller := rng.Address()
118154
input := rng.Bytes(8)
119155
value := rng.Hash()
120156

121-
state, evm := ethtest.NewZeroEVM(t)
157+
state, evm := ethtest.NewZeroEVM(t, ethtest.WithBlockContext(
158+
core.NewEVMBlockContext(header, nil, rng.AddressPtr()),
159+
))
122160
state.SetState(precompile, slot, value)
123161

124162
tests := []struct {
@@ -160,12 +198,21 @@ func TestNewStatefulPrecompile(t *testing.T) {
160198

161199
for _, tt := range tests {
162200
t.Run(tt.name, func(t *testing.T) {
163-
wantReturnData := makeOutput(caller, precompile, input, value, tt.wantReadOnly)
201+
wantReturnData := statefulPrecompileOutput{
202+
Caller: caller,
203+
Self: precompile,
204+
StateValue: value,
205+
ReadOnly: tt.wantReadOnly,
206+
BlockNumber: header.Number,
207+
BlockTime: header.Time,
208+
Difficulty: header.Difficulty,
209+
Input: input,
210+
}.String()
164211
wantGasLeft := gasLimit - gasCost
165212

166213
gotReturnData, gotGasLeft, err := tt.call()
167214
require.NoError(t, err)
168-
assert.Equal(t, string(wantReturnData), string(gotReturnData))
215+
assert.Equal(t, wantReturnData, string(gotReturnData))
169216
assert.Equal(t, wantGasLeft, gotGasLeft)
170217
})
171218
}
@@ -204,13 +251,12 @@ func TestInheritReadOnly(t *testing.T) {
204251
hooks := &hookstest.Stub{
205252
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
206253
precompile: vm.NewStatefulPrecompile(
207-
func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
254+
func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
208255
if env.ReadOnly() {
209-
return []byte{ifReadOnly}, nil
256+
return []byte{ifReadOnly}, suppliedGas, nil
210257
}
211-
return []byte{ifNotReadOnly}, nil
258+
return []byte{ifNotReadOnly}, suppliedGas, nil
212259
},
213-
func([]byte) uint64 { return 0 },
214260
),
215261
},
216262
}
@@ -296,20 +342,23 @@ func TestCanCreateContract(t *testing.T) {
296342
account := rng.Address()
297343
slot := rng.Hash()
298344

345+
const gasLimit uint64 = 1e6
346+
gasUsage := rng.Uint64n(gasLimit)
347+
299348
makeErr := func(cc *libevm.AddressContext, stateVal common.Hash) error {
300349
return fmt.Errorf("Origin: %v Caller: %v Contract: %v State: %v", cc.Origin, cc.Caller, cc.Self, stateVal)
301350
}
302351
hooks := &hookstest.Stub{
303-
CanCreateContractFn: func(cc *libevm.AddressContext, s libevm.StateReader) error {
304-
return makeErr(cc, s.GetState(account, slot))
352+
CanCreateContractFn: func(cc *libevm.AddressContext, gas uint64, s libevm.StateReader) (uint64, error) {
353+
return gas - gasUsage, makeErr(cc, s.GetState(account, slot))
305354
},
306355
}
307356
hooks.Register(t)
308357

309358
origin := rng.Address()
310359
caller := rng.Address()
311360
value := rng.Hash()
312-
code := rng.Bytes(8)
361+
code := []byte{byte(vm.STOP)}
313362
salt := rng.Hash()
314363

315364
create := crypto.CreateAddress(caller, 0)
@@ -323,14 +372,14 @@ func TestCanCreateContract(t *testing.T) {
323372
{
324373
name: "Create",
325374
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
326-
return evm.Create(vm.AccountRef(caller), code, 1e6, uint256.NewInt(0))
375+
return evm.Create(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0))
327376
},
328377
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create}, value),
329378
},
330379
{
331380
name: "Create2",
332381
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
333-
return evm.Create2(vm.AccountRef(caller), code, 1e6, uint256.NewInt(0), new(uint256.Int).SetBytes(salt[:]))
382+
return evm.Create2(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0), new(uint256.Int).SetBytes(salt[:]))
334383
},
335384
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create2}, value),
336385
},
@@ -342,8 +391,10 @@ func TestCanCreateContract(t *testing.T) {
342391
state.SetState(account, slot, value)
343392
evm.TxContext.Origin = origin
344393

345-
_, _, _, err := tt.create(evm)
394+
_, _, gasRemaining, err := tt.create(evm)
346395
require.EqualError(t, err, tt.wantErr.Error())
396+
// require prints uint64s in hex
397+
require.Equal(t, int(gasLimit-gasUsage), int(gasRemaining), "gas remaining") //nolint:gosec // G115 won't overflow as <= 1e6
347398
})
348399
}
349400
}

core/vm/evm.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ type BlockContext struct {
7979
BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price)
8080
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
8181
Random *common.Hash // Provides information for PREVRANDAO
82+
83+
Header *types.Header // libevm addition; not guaranteed to be set
8284
}
8385

8486
// TxContext provides the EVM with information about a transaction.
@@ -428,10 +430,6 @@ func (c *codeAndHash) Hash() common.Hash {
428430

429431
// create creates a new contract using code as deployment code.
430432
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
431-
cc := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: address}
432-
if err := evm.chainRules.Hooks().CanCreateContract(cc, evm.StateDB); err != nil {
433-
return nil, common.Address{}, gas, err
434-
}
435433
// Depth check execution. Fail if we're trying to execute above the
436434
// limit.
437435
if evm.depth > int(params.CallCreateDepth) {
@@ -455,6 +453,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
455453
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
456454
return nil, common.Address{}, 0, ErrContractAddressCollision
457455
}
456+
457+
//libevm:start
458+
//
459+
// This check MUST be placed after the caller's nonce is incremented but
460+
// before all other state-modifying behaviour, even if changes may be
461+
// reverted to the snapshot.
462+
addrs := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: address}
463+
gas, err := evm.chainRules.Hooks().CanCreateContract(addrs, gas, evm.StateDB)
464+
if err != nil {
465+
return nil, common.Address{}, gas, err
466+
}
467+
//libevm:end
468+
458469
// Create a new account on the state
459470
snapshot := evm.StateDB.Snapshot()
460471
evm.StateDB.CreateAccount(address)

0 commit comments

Comments
 (0)