Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: gas consumption for stateful precompiles #26

Merged
merged 3 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ func (args *evmCallArgs) RunPrecompiledContract(p PrecompiledContract, input []b
return nil, 0, ErrOutOfGas
}
suppliedGas -= gasCost
output, err := args.run(p, input)
return output, suppliedGas, err
return args.run(p, input, suppliedGas)
}

// ECRECOVER implemented as a native contract.
Expand Down
37 changes: 19 additions & 18 deletions core/vm/contracts.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,42 +50,43 @@ const (

// run runs the [PrecompiledContract], differentiating between stateful and
// regular types.
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
func (args *evmCallArgs) run(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
if p, ok := p.(statefulPrecompile); ok {
return p.run(args, input)
return p(args, input, suppliedGas)
}
return p.Run(input)
// Gas consumption for regular precompiles was already handled by the native
// RunPrecompiledContract(), which called this method.
ret, err = p.Run(input)
return ret, suppliedGas, err
}

// PrecompiledStatefulRun is the stateful equivalent of the Run() method of a
// PrecompiledStatefulContract is the stateful equivalent of a
// [PrecompiledContract].
type PrecompiledStatefulRun func(env PrecompileEnvironment, input []byte) ([]byte, error)
type PrecompiledStatefulContract func(env PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error)

// NewStatefulPrecompile constructs a new PrecompiledContract that can be used
// via an [EVM] instance but MUST NOT be called directly; a direct call to Run()
// reserves the right to panic. See other requirements defined in the comments
// on [PrecompiledContract].
func NewStatefulPrecompile(run PrecompiledStatefulRun, requiredGas func([]byte) uint64) PrecompiledContract {
return statefulPrecompile{
gas: requiredGas,
run: run,
}
func NewStatefulPrecompile(run PrecompiledStatefulContract) PrecompiledContract {
return statefulPrecompile(run)
}

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

func (p statefulPrecompile) RequiredGas(input []byte) uint64 {
return p.gas(input)
}
// RequiredGas always returns zero as this gas is consumed by native geth code
// before the contract is run.
func (statefulPrecompile) RequiredGas([]byte) uint64 { return 0 }

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

// A PrecompileEnvironment provides information about the context in which a
Expand Down
20 changes: 7 additions & 13 deletions core/vm/contracts.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,18 @@ func TestNewStatefulPrecompile(t *testing.T) {
caller, self, stateVal, readOnly, input,
))
}
run := func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
run := func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
if got, want := env.StateDB() != nil, !env.ReadOnly(); got != want {
return nil, 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)
}

addrs := env.Addresses()
val := env.ReadOnlyState().GetState(precompile, slot)
return makeOutput(addrs.Caller, addrs.Self, input, val, env.ReadOnly()), nil
return makeOutput(addrs.Caller, addrs.Self, input, val, env.ReadOnly()), suppliedGas - gasCost, nil
}
hooks := &hookstest.Stub{
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
precompile: vm.NewStatefulPrecompile(
run,
func(b []byte) uint64 {
return gasCost
},
),
precompile: vm.NewStatefulPrecompile(run),
},
}
hooks.Register(t)
Expand Down Expand Up @@ -204,13 +199,12 @@ func TestInheritReadOnly(t *testing.T) {
hooks := &hookstest.Stub{
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
precompile: vm.NewStatefulPrecompile(
func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
if env.ReadOnly() {
return []byte{ifReadOnly}, nil
return []byte{ifReadOnly}, suppliedGas, nil
}
return []byte{ifNotReadOnly}, nil
return []byte{ifNotReadOnly}, suppliedGas, nil
},
func([]byte) uint64 { return 0 },
),
},
}
Expand Down