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)
This commit is contained in:
Arran Schlosberg 2024-09-17 12:14:17 -04:00 committed by GitHub
parent 58f38836c0
commit 38eaaab96c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 39 additions and 21 deletions

View file

@ -296,12 +296,15 @@ func TestCanCreateContract(t *testing.T) {
account := rng.Address() account := rng.Address()
slot := rng.Hash() slot := rng.Hash()
const gasLimit uint64 = 1e6
gasUsage := rng.Uint64n(gasLimit)
makeErr := func(cc *libevm.AddressContext, stateVal common.Hash) error { 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) return fmt.Errorf("Origin: %v Caller: %v Contract: %v State: %v", cc.Origin, cc.Caller, cc.Self, stateVal)
} }
hooks := &hookstest.Stub{ hooks := &hookstest.Stub{
CanCreateContractFn: func(cc *libevm.AddressContext, s libevm.StateReader) error { CanCreateContractFn: func(cc *libevm.AddressContext, gas uint64, s libevm.StateReader) (uint64, error) {
return makeErr(cc, s.GetState(account, slot)) return gas - gasUsage, makeErr(cc, s.GetState(account, slot))
}, },
} }
hooks.Register(t) hooks.Register(t)
@ -309,7 +312,7 @@ func TestCanCreateContract(t *testing.T) {
origin := rng.Address() origin := rng.Address()
caller := rng.Address() caller := rng.Address()
value := rng.Hash() value := rng.Hash()
code := rng.Bytes(8) code := []byte{byte(vm.STOP)}
salt := rng.Hash() salt := rng.Hash()
create := crypto.CreateAddress(caller, 0) create := crypto.CreateAddress(caller, 0)
@ -323,14 +326,14 @@ func TestCanCreateContract(t *testing.T) {
{ {
name: "Create", name: "Create",
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) { create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
return evm.Create(vm.AccountRef(caller), code, 1e6, uint256.NewInt(0)) return evm.Create(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0))
}, },
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create}, value), wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create}, value),
}, },
{ {
name: "Create2", name: "Create2",
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) { 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[:])) return evm.Create2(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0), new(uint256.Int).SetBytes(salt[:]))
}, },
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create2}, value), wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create2}, value),
}, },
@ -342,8 +345,10 @@ func TestCanCreateContract(t *testing.T) {
state.SetState(account, slot, value) state.SetState(account, slot, value)
evm.TxContext.Origin = origin evm.TxContext.Origin = origin
_, _, _, err := tt.create(evm) _, _, gasRemaining, err := tt.create(evm)
require.EqualError(t, err, tt.wantErr.Error()) require.EqualError(t, err, tt.wantErr.Error())
// require prints uint64s in hex
require.Equal(t, int(gasLimit-gasUsage), int(gasRemaining), "gas remaining") //nolint:gosec // G115 won't overflow as <= 1e6
}) })
} }
} }

View file

@ -428,10 +428,6 @@ func (c *codeAndHash) Hash() common.Hash {
// create creates a new contract using code as deployment code. // create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
cc := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: address}
if err := evm.chainRules.Hooks().CanCreateContract(cc, evm.StateDB); err != nil {
return nil, common.Address{}, gas, err
}
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
@ -455,6 +451,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
return nil, common.Address{}, 0, ErrContractAddressCollision return nil, common.Address{}, 0, ErrContractAddressCollision
} }
//libevm:start
//
// This check MUST be placed after the caller's nonce is incremented but
// before all other state-modifying behaviour, even if changes may be
// reverted to the snapshot.
addrs := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: address}
gas, err := evm.chainRules.Hooks().CanCreateContract(addrs, gas, evm.StateDB)
if err != nil {
return nil, common.Address{}, gas, err
}
//libevm:end
// Create a new account on the state // Create a new account on the state
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
evm.StateDB.CreateAccount(address) evm.StateDB.CreateAccount(address)

View file

@ -27,7 +27,7 @@ func Register[C params.ChainConfigHooks, R params.RulesHooks](tb testing.TB, ext
type Stub struct { type Stub struct {
PrecompileOverrides map[common.Address]libevm.PrecompiledContract PrecompileOverrides map[common.Address]libevm.PrecompiledContract
CanExecuteTransactionFn func(common.Address, *common.Address, libevm.StateReader) error CanExecuteTransactionFn func(common.Address, *common.Address, libevm.StateReader) error
CanCreateContractFn func(*libevm.AddressContext, libevm.StateReader) error CanCreateContractFn func(*libevm.AddressContext, uint64, libevm.StateReader) (uint64, error)
} }
// Register is a convenience wrapper for registering s as both the // 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
// CanCreateContract proxies arguments to the s.CanCreateContractFn function if // CanCreateContract proxies arguments to the s.CanCreateContractFn function if
// non-nil, otherwise it acts as a noop. // non-nil, otherwise it acts as a noop.
func (s Stub) CanCreateContract(cc *libevm.AddressContext, sr libevm.StateReader) error { func (s Stub) CanCreateContract(cc *libevm.AddressContext, gas uint64, sr libevm.StateReader) (uint64, error) {
if f := s.CanCreateContractFn; f != nil { if f := s.CanCreateContractFn; f != nil {
return f(cc, sr) return f(cc, gas, sr)
} }
return nil return gas, nil
} }
var _ interface { var _ interface {

View file

@ -105,11 +105,12 @@ func (r RulesExtra) PrecompileOverride(addr common.Address) (_ libevm.Precompile
// CanCreateContract implements the required [params.RuleHooks] method. Access // CanCreateContract implements the required [params.RuleHooks] method. Access
// to state allows it to be configured on-chain however this is an optional // to state allows it to be configured on-chain however this is an optional
// implementation detail. // implementation detail.
func (r RulesExtra) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error { func (r RulesExtra) CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libevm.StateReader) (uint64, error) {
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 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
return errors.New("uh oh") // Consumes all remaining gas.
return 0, errors.New("uh oh")
} }
return nil return gas, nil
} }
// This example demonstrates how the rest of this file would be used from a // This example demonstrates how the rest of this file would be used from a

View file

@ -32,7 +32,9 @@ type RulesHooks interface {
// RulesAllowlistHooks are a subset of [RulesHooks] that gate actions, signalled // RulesAllowlistHooks are a subset of [RulesHooks] that gate actions, signalled
// by returning a nil (allowed) or non-nil (blocked) error. // by returning a nil (allowed) or non-nil (blocked) error.
type RulesAllowlistHooks interface { type RulesAllowlistHooks interface {
CanCreateContract(*libevm.AddressContext, libevm.StateReader) error // CanCreateContract is called after the deployer's nonce is incremented but
// before all other state-modifying actions.
CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libevm.StateReader) (gasRemaining uint64, _ error)
CanExecuteTransaction(from common.Address, to *common.Address, _ libevm.StateReader) error CanExecuteTransaction(from common.Address, to *common.Address, _ libevm.StateReader) error
} }
@ -71,9 +73,10 @@ func (NOOPHooks) CanExecuteTransaction(_ common.Address, _ *common.Address, _ li
return nil return nil
} }
// CanCreateContract allows all (otherwise valid) contract deployment. // CanCreateContract allows all (otherwise valid) contract deployment, not
func (NOOPHooks) CanCreateContract(*libevm.AddressContext, libevm.StateReader) error { // consuming any more gas.
return nil func (NOOPHooks) CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libevm.StateReader) (uint64, error) {
return gas, nil
} }
// PrecompileOverride instructs the EVM interpreter to use the default // PrecompileOverride instructs the EVM interpreter to use the default