Skip to content

Commit 38eaaab

Browse files
authored
feat!: RulesHooks.CanCreateContract() accepts and returns gas (#28)
* refactor!: `RulesHooks.CanCreateContract()` via `defer` `TestCanCreateContract` is unchanged and merely moved to the appropriate file. * feat!: `RulesHooks.CanCreateContract()` accepts and returns gas * chore: move `TestCanCreateContract` to original file Simplifies code review * chore: pacify linter * refactor!: revert to non-deferred call (not at start)
1 parent 58f3883 commit 38eaaab

File tree

5 files changed

+39
-21
lines changed

5 files changed

+39
-21
lines changed

core/vm/contracts.libevm_test.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,20 +296,23 @@ func TestCanCreateContract(t *testing.T) {
296296
account := rng.Address()
297297
slot := rng.Hash()
298298

299+
const gasLimit uint64 = 1e6
300+
gasUsage := rng.Uint64n(gasLimit)
301+
299302
makeErr := func(cc *libevm.AddressContext, stateVal common.Hash) error {
300303
return fmt.Errorf("Origin: %v Caller: %v Contract: %v State: %v", cc.Origin, cc.Caller, cc.Self, stateVal)
301304
}
302305
hooks := &hookstest.Stub{
303-
CanCreateContractFn: func(cc *libevm.AddressContext, s libevm.StateReader) error {
304-
return makeErr(cc, s.GetState(account, slot))
306+
CanCreateContractFn: func(cc *libevm.AddressContext, gas uint64, s libevm.StateReader) (uint64, error) {
307+
return gas - gasUsage, makeErr(cc, s.GetState(account, slot))
305308
},
306309
}
307310
hooks.Register(t)
308311

309312
origin := rng.Address()
310313
caller := rng.Address()
311314
value := rng.Hash()
312-
code := rng.Bytes(8)
315+
code := []byte{byte(vm.STOP)}
313316
salt := rng.Hash()
314317

315318
create := crypto.CreateAddress(caller, 0)
@@ -323,14 +326,14 @@ func TestCanCreateContract(t *testing.T) {
323326
{
324327
name: "Create",
325328
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
326-
return evm.Create(vm.AccountRef(caller), code, 1e6, uint256.NewInt(0))
329+
return evm.Create(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0))
327330
},
328331
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create}, value),
329332
},
330333
{
331334
name: "Create2",
332335
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[:]))
336+
return evm.Create2(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0), new(uint256.Int).SetBytes(salt[:]))
334337
},
335338
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create2}, value),
336339
},
@@ -342,8 +345,10 @@ func TestCanCreateContract(t *testing.T) {
342345
state.SetState(account, slot, value)
343346
evm.TxContext.Origin = origin
344347

345-
_, _, _, err := tt.create(evm)
348+
_, _, gasRemaining, err := tt.create(evm)
346349
require.EqualError(t, err, tt.wantErr.Error())
350+
// require prints uint64s in hex
351+
require.Equal(t, int(gasLimit-gasUsage), int(gasRemaining), "gas remaining") //nolint:gosec // G115 won't overflow as <= 1e6
347352
})
348353
}
349354
}

core/vm/evm.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,6 @@ func (c *codeAndHash) Hash() common.Hash {
428428

429429
// create creates a new contract using code as deployment code.
430430
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-
}
435431
// Depth check execution. Fail if we're trying to execute above the
436432
// limit.
437433
if evm.depth > int(params.CallCreateDepth) {
@@ -455,6 +451,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
455451
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
456452
return nil, common.Address{}, 0, ErrContractAddressCollision
457453
}
454+
455+
//libevm:start
456+
//
457+
// This check MUST be placed after the caller's nonce is incremented but
458+
// before all other state-modifying behaviour, even if changes may be
459+
// reverted to the snapshot.
460+
addrs := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: address}
461+
gas, err := evm.chainRules.Hooks().CanCreateContract(addrs, gas, evm.StateDB)
462+
if err != nil {
463+
return nil, common.Address{}, gas, err
464+
}
465+
//libevm:end
466+
458467
// Create a new account on the state
459468
snapshot := evm.StateDB.Snapshot()
460469
evm.StateDB.CreateAccount(address)

libevm/hookstest/stub.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func Register[C params.ChainConfigHooks, R params.RulesHooks](tb testing.TB, ext
2727
type Stub struct {
2828
PrecompileOverrides map[common.Address]libevm.PrecompiledContract
2929
CanExecuteTransactionFn func(common.Address, *common.Address, libevm.StateReader) error
30-
CanCreateContractFn func(*libevm.AddressContext, libevm.StateReader) error
30+
CanCreateContractFn func(*libevm.AddressContext, uint64, libevm.StateReader) (uint64, error)
3131
}
3232

3333
// Register is a convenience wrapper for registering s as both the
@@ -63,11 +63,11 @@ func (s Stub) CanExecuteTransaction(from common.Address, to *common.Address, sr
6363

6464
// CanCreateContract proxies arguments to the s.CanCreateContractFn function if
6565
// non-nil, otherwise it acts as a noop.
66-
func (s Stub) CanCreateContract(cc *libevm.AddressContext, sr libevm.StateReader) error {
66+
func (s Stub) CanCreateContract(cc *libevm.AddressContext, gas uint64, sr libevm.StateReader) (uint64, error) {
6767
if f := s.CanCreateContractFn; f != nil {
68-
return f(cc, sr)
68+
return f(cc, gas, sr)
6969
}
70-
return nil
70+
return gas, nil
7171
}
7272

7373
var _ interface {

params/example.libevm_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,12 @@ func (r RulesExtra) PrecompileOverride(addr common.Address) (_ libevm.Precompile
105105
// CanCreateContract implements the required [params.RuleHooks] method. Access
106106
// to state allows it to be configured on-chain however this is an optional
107107
// implementation detail.
108-
func (r RulesExtra) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error {
108+
func (r RulesExtra) CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libevm.StateReader) (uint64, error) {
109109
if time.Unix(int64(r.timestamp), 0).UTC().Day() != int(time.Tuesday) { //nolint:gosec // G115 timestamp won't overflow int64 for millions of years so this is someone else's problem
110-
return errors.New("uh oh")
110+
// Consumes all remaining gas.
111+
return 0, errors.New("uh oh")
111112
}
112-
return nil
113+
return gas, nil
113114
}
114115

115116
// This example demonstrates how the rest of this file would be used from a

params/hooks.libevm.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ type RulesHooks interface {
3232
// RulesAllowlistHooks are a subset of [RulesHooks] that gate actions, signalled
3333
// by returning a nil (allowed) or non-nil (blocked) error.
3434
type RulesAllowlistHooks interface {
35-
CanCreateContract(*libevm.AddressContext, libevm.StateReader) error
35+
// CanCreateContract is called after the deployer's nonce is incremented but
36+
// before all other state-modifying actions.
37+
CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libevm.StateReader) (gasRemaining uint64, _ error)
3638
CanExecuteTransaction(from common.Address, to *common.Address, _ libevm.StateReader) error
3739
}
3840

@@ -71,9 +73,10 @@ func (NOOPHooks) CanExecuteTransaction(_ common.Address, _ *common.Address, _ li
7173
return nil
7274
}
7375

74-
// CanCreateContract allows all (otherwise valid) contract deployment.
75-
func (NOOPHooks) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error {
76-
return nil
76+
// CanCreateContract allows all (otherwise valid) contract deployment, not
77+
// consuming any more gas.
78+
func (NOOPHooks) CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libevm.StateReader) (uint64, error) {
79+
return gas, nil
7780
}
7881

7982
// PrecompileOverride instructs the EVM interpreter to use the default

0 commit comments

Comments
 (0)