mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 10:50:44 +00:00
refactor: PrecompileEnvironment.{,Use,Refund}Gas() in lieu of args (#73)
## Why this should be merged
Aligns precompiled contracts with the pattern used in
`vm.EVMInterpreter` for regular contracts and simplifies gas accounting
by using existing mechanisms. The most notable simplification occurs
when there are multiple error paths that return early and have to
account for consumed gas (any local solution would likely just mirror
this one).
This forms part of a broader, long-term direction of feature parity
between precompiled and bytecode contracts, exposed via
`vm.PrecompileEnvironment`.
The `vm.Contract.Value()` method is also exposed as a natural
accompaniment to this change.
## How this works
The original signature for `vm.PrecompiledStatefulContract` received the
amount of gas available and then returned the amount remaining; this has
been removed in lieu of exposing the existing `vm.Contract.UseGas()`
method via the `vm.PrecompileEnvironment`. A new `legacy` package wraps
the old signature and converts it to a new one so `ava-labs/coreth`
doesn't need to be refactored.
```go
func oldPrecompile(env vm.PrecompileEnvironment, input []byte, gas uint64) ([]byte, uint64, error) {
// ...
if err != nil {
return nil, gas - gasCost, err // pattern susceptible to bugs; should it be `nil, gas, err` ?
}
// ...
return output, gas - gasCost, nil
}
func newPrecompile(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
// The original `gas` argument is still available as `env.Gas()`
// ...
if !env.UseGas(gasCost) { // an explicit point at which gas is consumed
return nil, vm.ErrOutOfGas
}
// ...
if err != nil {
return nil, err
}
// ...
return output, nil
}
```
## How this was tested
Existing unit test modified to use the `legacy` adaptor.
---------
Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com>
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
This commit is contained in:
parent
43878f4fab
commit
44f23c8869
6 changed files with 141 additions and 52 deletions
|
|
@ -177,7 +177,9 @@ func (args *evmCallArgs) RunPrecompiledContract(p PrecompiledContract, input []b
|
||||||
return nil, 0, ErrOutOfGas
|
return nil, 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
suppliedGas -= gasCost
|
suppliedGas -= gasCost
|
||||||
return args.run(p, input, suppliedGas)
|
args.gasRemaining = suppliedGas
|
||||||
|
output, err := args.run(p, input)
|
||||||
|
return output, args.gasRemaining, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ECRECOVER implemented as a native contract.
|
// ECRECOVER implemented as a native contract.
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ type evmCallArgs struct {
|
||||||
caller ContractRef
|
caller ContractRef
|
||||||
addr common.Address
|
addr common.Address
|
||||||
input []byte
|
input []byte
|
||||||
gas uint64
|
gasRemaining uint64
|
||||||
value *uint256.Int
|
value *uint256.Int
|
||||||
// args:end
|
// args:end
|
||||||
}
|
}
|
||||||
|
|
@ -89,20 +89,26 @@ func (t CallType) OpCode() OpCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
// run runs the [PrecompiledContract], differentiating between stateful and
|
// run runs the [PrecompiledContract], differentiating between stateful and
|
||||||
// regular types.
|
// regular types, updating `args.gasRemaining` in the stateful case.
|
||||||
func (args *evmCallArgs) run(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
|
||||||
if p, ok := p.(statefulPrecompile); ok {
|
switch p := p.(type) {
|
||||||
return p(args.env(), input, suppliedGas)
|
default:
|
||||||
|
return p.Run(input)
|
||||||
|
case statefulPrecompile:
|
||||||
|
env := args.env()
|
||||||
|
ret, err := p(env, input)
|
||||||
|
args.gasRemaining = env.Gas()
|
||||||
|
return ret, err
|
||||||
}
|
}
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrecompiledStatefulContract is the stateful equivalent of a
|
// PrecompiledStatefulContract is the stateful equivalent of a
|
||||||
// [PrecompiledContract].
|
// [PrecompiledContract].
|
||||||
type PrecompiledStatefulContract func(env PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error)
|
//
|
||||||
|
// Instead of receiving and returning gas arguments, stateful precompiles use
|
||||||
|
// the respective methods on [PrecompileEnvironment]. If a call to UseGas()
|
||||||
|
// returns false, a stateful precompile SHOULD return [ErrOutOfGas].
|
||||||
|
type PrecompiledStatefulContract func(env PrecompileEnvironment, input []byte) (ret []byte, err error)
|
||||||
|
|
||||||
// NewStatefulPrecompile constructs a new PrecompiledContract that can be used
|
// NewStatefulPrecompile constructs a new PrecompiledContract that can be used
|
||||||
// via an [EVM] instance but MUST NOT be called directly; a direct call to Run()
|
// via an [EVM] instance but MUST NOT be called directly; a direct call to Run()
|
||||||
|
|
@ -135,13 +141,18 @@ func (p statefulPrecompile) Run([]byte) ([]byte, error) {
|
||||||
type PrecompileEnvironment interface {
|
type PrecompileEnvironment interface {
|
||||||
ChainConfig() *params.ChainConfig
|
ChainConfig() *params.ChainConfig
|
||||||
Rules() params.Rules
|
Rules() params.Rules
|
||||||
ReadOnly() bool
|
|
||||||
// StateDB will be non-nil i.f.f !ReadOnly().
|
// StateDB will be non-nil i.f.f !ReadOnly().
|
||||||
StateDB() StateDB
|
StateDB() StateDB
|
||||||
// ReadOnlyState will always be non-nil.
|
// ReadOnlyState will always be non-nil.
|
||||||
ReadOnlyState() libevm.StateReader
|
ReadOnlyState() libevm.StateReader
|
||||||
Addresses() *libevm.AddressContext
|
|
||||||
IncomingCallType() CallType
|
IncomingCallType() CallType
|
||||||
|
Addresses() *libevm.AddressContext
|
||||||
|
ReadOnly() bool
|
||||||
|
// Equivalent to respective methods on [Contract].
|
||||||
|
Gas() uint64
|
||||||
|
UseGas(uint64) (hasEnoughGas bool)
|
||||||
|
Value() *uint256.Int
|
||||||
|
|
||||||
BlockHeader() (types.Header, error)
|
BlockHeader() (types.Header, error)
|
||||||
BlockNumber() *big.Int
|
BlockNumber() *big.Int
|
||||||
|
|
@ -150,7 +161,7 @@ type PrecompileEnvironment interface {
|
||||||
// Call is equivalent to [EVM.Call] except that the `caller` argument is
|
// Call is equivalent to [EVM.Call] except that the `caller` argument is
|
||||||
// removed and automatically determined according to the type of call that
|
// removed and automatically determined according to the type of call that
|
||||||
// invoked the precompile.
|
// invoked the precompile.
|
||||||
Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, _ ...CallOption) (ret []byte, gasRemaining uint64, _ error)
|
Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, _ ...CallOption) (ret []byte, _ error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (args *evmCallArgs) env() *environment {
|
func (args *evmCallArgs) env() *environment {
|
||||||
|
|
@ -174,7 +185,7 @@ func (args *evmCallArgs) env() *environment {
|
||||||
|
|
||||||
// This is equivalent to the `contract` variables created by evm.*Call*()
|
// This is equivalent to the `contract` variables created by evm.*Call*()
|
||||||
// methods, for non precompiles, to pass to [EVMInterpreter.Run].
|
// methods, for non precompiles, to pass to [EVMInterpreter.Run].
|
||||||
contract := NewContract(args.caller, AccountRef(self), value, args.gas)
|
contract := NewContract(args.caller, AccountRef(self), value, args.gasRemaining)
|
||||||
if args.callType == DelegateCall {
|
if args.callType == DelegateCall {
|
||||||
contract = contract.AsDelegate()
|
contract = contract.AsDelegate()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import (
|
||||||
"github.com/ava-labs/libevm/libevm"
|
"github.com/ava-labs/libevm/libevm"
|
||||||
"github.com/ava-labs/libevm/libevm/ethtest"
|
"github.com/ava-labs/libevm/libevm/ethtest"
|
||||||
"github.com/ava-labs/libevm/libevm/hookstest"
|
"github.com/ava-labs/libevm/libevm/hookstest"
|
||||||
|
"github.com/ava-labs/libevm/libevm/legacy"
|
||||||
"github.com/ava-labs/libevm/params"
|
"github.com/ava-labs/libevm/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -106,6 +107,7 @@ type statefulPrecompileOutput struct {
|
||||||
ChainID *big.Int
|
ChainID *big.Int
|
||||||
Addresses *libevm.AddressContext
|
Addresses *libevm.AddressContext
|
||||||
StateValue common.Hash
|
StateValue common.Hash
|
||||||
|
ValueReceived *uint256.Int
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
BlockNumber, Difficulty *big.Int
|
BlockNumber, Difficulty *big.Int
|
||||||
BlockTime uint64
|
BlockTime uint64
|
||||||
|
|
@ -159,6 +161,7 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
ChainID: env.ChainConfig().ChainID,
|
ChainID: env.ChainConfig().ChainID,
|
||||||
Addresses: env.Addresses(),
|
Addresses: env.Addresses(),
|
||||||
StateValue: env.ReadOnlyState().GetState(precompile, slot),
|
StateValue: env.ReadOnlyState().GetState(precompile, slot),
|
||||||
|
ValueReceived: env.Value(),
|
||||||
ReadOnly: env.ReadOnly(),
|
ReadOnly: env.ReadOnly(),
|
||||||
BlockNumber: env.BlockNumber(),
|
BlockNumber: env.BlockNumber(),
|
||||||
BlockTime: env.BlockTime(),
|
BlockTime: env.BlockTime(),
|
||||||
|
|
@ -170,7 +173,11 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
}
|
}
|
||||||
hooks := &hookstest.Stub{
|
hooks := &hookstest.Stub{
|
||||||
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
||||||
precompile: vm.NewStatefulPrecompile(run),
|
precompile: vm.NewStatefulPrecompile(
|
||||||
|
// In production, the new function signature should be used, but
|
||||||
|
// this just exercises the converter.
|
||||||
|
legacy.PrecompiledStatefulContract(run).Upgrade(),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
hooks.Register(t)
|
hooks.Register(t)
|
||||||
|
|
@ -181,7 +188,8 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
Difficulty: rng.BigUint64(),
|
Difficulty: rng.BigUint64(),
|
||||||
}
|
}
|
||||||
input := rng.Bytes(8)
|
input := rng.Bytes(8)
|
||||||
value := rng.Hash()
|
stateValue := rng.Hash()
|
||||||
|
transferValue := rng.Uint256()
|
||||||
chainID := rng.BigUint64()
|
chainID := rng.BigUint64()
|
||||||
|
|
||||||
caller := common.HexToAddress("CA11E12") // caller of the precompile
|
caller := common.HexToAddress("CA11E12") // caller of the precompile
|
||||||
|
|
@ -197,13 +205,15 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
¶ms.ChainConfig{ChainID: chainID},
|
¶ms.ChainConfig{ChainID: chainID},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
state.SetState(precompile, slot, value)
|
state.SetState(precompile, slot, stateValue)
|
||||||
|
state.SetBalance(caller, new(uint256.Int).Not(uint256.NewInt(0)))
|
||||||
evm.Origin = eoa
|
evm.Origin = eoa
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
call func() ([]byte, uint64, error)
|
call func() ([]byte, uint64, error)
|
||||||
wantAddresses *libevm.AddressContext
|
wantAddresses *libevm.AddressContext
|
||||||
|
wantTransferValue *uint256.Int
|
||||||
// Note that this only covers evm.readOnly being true because of the
|
// Note that this only covers evm.readOnly being true because of the
|
||||||
// precompile's call. See TestInheritReadOnly for alternate case.
|
// precompile's call. See TestInheritReadOnly for alternate case.
|
||||||
wantReadOnly bool
|
wantReadOnly bool
|
||||||
|
|
@ -212,7 +222,7 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "EVM.Call()",
|
name: "EVM.Call()",
|
||||||
call: func() ([]byte, uint64, error) {
|
call: func() ([]byte, uint64, error) {
|
||||||
return evm.Call(callerContract, precompile, input, gasLimit, uint256.NewInt(0))
|
return evm.Call(callerContract, precompile, input, gasLimit, transferValue)
|
||||||
},
|
},
|
||||||
wantAddresses: &libevm.AddressContext{
|
wantAddresses: &libevm.AddressContext{
|
||||||
Origin: eoa,
|
Origin: eoa,
|
||||||
|
|
@ -220,12 +230,13 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
Self: precompile,
|
Self: precompile,
|
||||||
},
|
},
|
||||||
wantReadOnly: false,
|
wantReadOnly: false,
|
||||||
|
wantTransferValue: transferValue,
|
||||||
wantCallType: vm.Call,
|
wantCallType: vm.Call,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "EVM.CallCode()",
|
name: "EVM.CallCode()",
|
||||||
call: func() ([]byte, uint64, error) {
|
call: func() ([]byte, uint64, error) {
|
||||||
return evm.CallCode(callerContract, precompile, input, gasLimit, uint256.NewInt(0))
|
return evm.CallCode(callerContract, precompile, input, gasLimit, transferValue)
|
||||||
},
|
},
|
||||||
wantAddresses: &libevm.AddressContext{
|
wantAddresses: &libevm.AddressContext{
|
||||||
Origin: eoa,
|
Origin: eoa,
|
||||||
|
|
@ -233,6 +244,7 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
Self: caller,
|
Self: caller,
|
||||||
},
|
},
|
||||||
wantReadOnly: false,
|
wantReadOnly: false,
|
||||||
|
wantTransferValue: transferValue,
|
||||||
wantCallType: vm.CallCode,
|
wantCallType: vm.CallCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -246,6 +258,7 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
Self: caller,
|
Self: caller,
|
||||||
},
|
},
|
||||||
wantReadOnly: false,
|
wantReadOnly: false,
|
||||||
|
wantTransferValue: uint256.NewInt(0),
|
||||||
wantCallType: vm.DelegateCall,
|
wantCallType: vm.DelegateCall,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -259,6 +272,7 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
Self: precompile,
|
Self: precompile,
|
||||||
},
|
},
|
||||||
wantReadOnly: true,
|
wantReadOnly: true,
|
||||||
|
wantTransferValue: uint256.NewInt(0),
|
||||||
wantCallType: vm.StaticCall,
|
wantCallType: vm.StaticCall,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +282,8 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
||||||
wantOutput := statefulPrecompileOutput{
|
wantOutput := statefulPrecompileOutput{
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
Addresses: tt.wantAddresses,
|
Addresses: tt.wantAddresses,
|
||||||
StateValue: value,
|
StateValue: stateValue,
|
||||||
|
ValueReceived: tt.wantTransferValue,
|
||||||
ReadOnly: tt.wantReadOnly,
|
ReadOnly: tt.wantReadOnly,
|
||||||
BlockNumber: header.Number,
|
BlockNumber: header.Number,
|
||||||
BlockTime: header.Time,
|
BlockTime: header.Time,
|
||||||
|
|
@ -318,11 +333,11 @@ func TestInheritReadOnly(t *testing.T) {
|
||||||
hooks := &hookstest.Stub{
|
hooks := &hookstest.Stub{
|
||||||
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
||||||
precompile: vm.NewStatefulPrecompile(
|
precompile: vm.NewStatefulPrecompile(
|
||||||
func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
|
func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
|
||||||
if env.ReadOnly() {
|
if env.ReadOnly() {
|
||||||
return []byte{ifReadOnly}, suppliedGas, nil
|
return []byte{ifReadOnly}, nil
|
||||||
}
|
}
|
||||||
return []byte{ifNotReadOnly}, suppliedGas, nil
|
return []byte{ifNotReadOnly}, nil
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -535,21 +550,21 @@ func TestPrecompileMakeCall(t *testing.T) {
|
||||||
|
|
||||||
hooks := &hookstest.Stub{
|
hooks := &hookstest.Stub{
|
||||||
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
||||||
sut: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
sut: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) {
|
||||||
var opts []vm.CallOption
|
var opts []vm.CallOption
|
||||||
if bytes.Equal(input, unsafeCallerProxyOptSentinel) {
|
if bytes.Equal(input, unsafeCallerProxyOptSentinel) {
|
||||||
opts = append(opts, vm.WithUNSAFECallerAddressProxying())
|
opts = append(opts, vm.WithUNSAFECallerAddressProxying())
|
||||||
}
|
}
|
||||||
// We are ultimately testing env.Call(), hence why this is the SUT.
|
// We are ultimately testing env.Call(), hence why this is the SUT.
|
||||||
return env.Call(dest, precompileCallData, suppliedGas, uint256.NewInt(0), opts...)
|
return env.Call(dest, precompileCallData, env.Gas(), uint256.NewInt(0), opts...)
|
||||||
}),
|
}),
|
||||||
dest: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
dest: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) {
|
||||||
out := &statefulPrecompileOutput{
|
out := &statefulPrecompileOutput{
|
||||||
Addresses: env.Addresses(),
|
Addresses: env.Addresses(),
|
||||||
ReadOnly: env.ReadOnly(),
|
ReadOnly: env.ReadOnly(),
|
||||||
Input: input, // expected to be callData
|
Input: input, // expected to be callData
|
||||||
}
|
}
|
||||||
return out.Bytes(), suppliedGas, nil
|
return out.Bytes(), nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -696,8 +711,8 @@ func TestPrecompileCallWithTracer(t *testing.T) {
|
||||||
|
|
||||||
hooks := &hookstest.Stub{
|
hooks := &hookstest.Stub{
|
||||||
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{
|
||||||
precompile: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
precompile: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) {
|
||||||
return env.Call(contract, nil, suppliedGas, uint256.NewInt(0))
|
return env.Call(contract, nil, env.Gas(), uint256.NewInt(0))
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
||||||
"github.com/ava-labs/libevm/common"
|
"github.com/ava-labs/libevm/common"
|
||||||
|
"github.com/ava-labs/libevm/common/math"
|
||||||
"github.com/ava-labs/libevm/core/types"
|
"github.com/ava-labs/libevm/core/types"
|
||||||
"github.com/ava-labs/libevm/libevm"
|
"github.com/ava-labs/libevm/libevm"
|
||||||
"github.com/ava-labs/libevm/libevm/options"
|
"github.com/ava-labs/libevm/libevm/options"
|
||||||
|
|
@ -37,6 +38,10 @@ type environment struct {
|
||||||
callType CallType
|
callType CallType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *environment) Gas() uint64 { return e.self.Gas }
|
||||||
|
func (e *environment) UseGas(gas uint64) bool { return e.self.UseGas(gas) }
|
||||||
|
func (e *environment) Value() *uint256.Int { return new(uint256.Int).Set(e.self.Value()) }
|
||||||
|
|
||||||
func (e *environment) ChainConfig() *params.ChainConfig { return e.evm.chainConfig }
|
func (e *environment) ChainConfig() *params.ChainConfig { return e.evm.chainConfig }
|
||||||
func (e *environment) Rules() params.Rules { return e.evm.chainRules }
|
func (e *environment) Rules() params.Rules { return e.evm.chainRules }
|
||||||
func (e *environment) ReadOnlyState() libevm.StateReader { return e.evm.StateDB }
|
func (e *environment) ReadOnlyState() libevm.StateReader { return e.evm.StateDB }
|
||||||
|
|
@ -44,6 +49,15 @@ func (e *environment) IncomingCallType() CallType { return e.callType }
|
||||||
func (e *environment) BlockNumber() *big.Int { return new(big.Int).Set(e.evm.Context.BlockNumber) }
|
func (e *environment) BlockNumber() *big.Int { return new(big.Int).Set(e.evm.Context.BlockNumber) }
|
||||||
func (e *environment) BlockTime() uint64 { return e.evm.Context.Time }
|
func (e *environment) BlockTime() uint64 { return e.evm.Context.Time }
|
||||||
|
|
||||||
|
func (e *environment) refundGas(add uint64) error {
|
||||||
|
gas, overflow := math.SafeAdd(e.self.Gas, add)
|
||||||
|
if overflow {
|
||||||
|
return ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
e.self.Gas = gas
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *environment) ReadOnly() bool {
|
func (e *environment) ReadOnly() bool {
|
||||||
// A switch statement provides clearer code coverage for difficult-to-test
|
// A switch statement provides clearer code coverage for difficult-to-test
|
||||||
// cases.
|
// cases.
|
||||||
|
|
@ -87,11 +101,11 @@ func (e *environment) BlockHeader() (types.Header, error) {
|
||||||
return *hdr, nil
|
return *hdr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *environment) Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) ([]byte, uint64, error) {
|
func (e *environment) Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) ([]byte, error) {
|
||||||
return e.callContract(Call, addr, input, gas, value, opts...)
|
return e.callContract(Call, addr, input, gas, value, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) (retData []byte, retGas uint64, retErr error) {
|
func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) (retData []byte, retErr error) {
|
||||||
// Depth and read-only setting are handled by [EVMInterpreter.Run], which
|
// Depth and read-only setting are handled by [EVMInterpreter.Run], which
|
||||||
// isn't used for precompiles, so we need to do it ourselves to maintain the
|
// isn't used for precompiles, so we need to do it ourselves to maintain the
|
||||||
// expected invariants.
|
// expected invariants.
|
||||||
|
|
@ -118,8 +132,12 @@ func (e *environment) callContract(typ CallType, addr common.Address, input []by
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.readOnly && value != nil && !value.IsZero() {
|
if in.readOnly && value != nil && !value.IsZero() {
|
||||||
return nil, gas, ErrWriteProtection
|
return nil, ErrWriteProtection
|
||||||
}
|
}
|
||||||
|
if !e.UseGas(gas) {
|
||||||
|
return nil, ErrOutOfGas
|
||||||
|
}
|
||||||
|
|
||||||
if t := e.evm.Config.Tracer; t != nil {
|
if t := e.evm.Config.Tracer; t != nil {
|
||||||
var bigVal *big.Int
|
var bigVal *big.Int
|
||||||
if value != nil {
|
if value != nil {
|
||||||
|
|
@ -129,13 +147,17 @@ func (e *environment) callContract(typ CallType, addr common.Address, input []by
|
||||||
|
|
||||||
startGas := gas
|
startGas := gas
|
||||||
defer func() {
|
defer func() {
|
||||||
t.CaptureEnd(retData, startGas-retGas, retErr)
|
t.CaptureEnd(retData, startGas-e.Gas(), retErr)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch typ {
|
switch typ {
|
||||||
case Call:
|
case Call:
|
||||||
return e.evm.Call(caller, addr, input, gas, value)
|
ret, returnGas, callErr := e.evm.Call(caller, addr, input, gas, value)
|
||||||
|
if err := e.refundGas(returnGas); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ret, callErr
|
||||||
case CallCode, DelegateCall, StaticCall:
|
case CallCode, DelegateCall, StaticCall:
|
||||||
// TODO(arr4n): these cases should be very similar to CALL, hence the
|
// TODO(arr4n): these cases should be very similar to CALL, hence the
|
||||||
// early abstraction, to signal to future maintainers. If implementing
|
// early abstraction, to signal to future maintainers. If implementing
|
||||||
|
|
@ -144,6 +166,6 @@ func (e *environment) callContract(typ CallType, addr common.Address, input []by
|
||||||
// compatibility.
|
// compatibility.
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return nil, gas, fmt.Errorf("unimplemented precompile call type %v", typ)
|
return nil, fmt.Errorf("unimplemented precompile call type %v", typ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,5 @@ package vm
|
||||||
// The original RunPrecompiledContract was migrated to being a method on
|
// The original RunPrecompiledContract was migrated to being a method on
|
||||||
// [evmCallArgs]. We need to replace it for use by regular geth tests.
|
// [evmCallArgs]. We need to replace it for use by regular geth tests.
|
||||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||||
return (*evmCallArgs)(nil).RunPrecompiledContract(p, input, suppliedGas)
|
return new(evmCallArgs).RunPrecompiledContract(p, input, suppliedGas)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
libevm/legacy/legacy.go
Normal file
39
libevm/legacy/legacy.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2024 the libevm authors.
|
||||||
|
//
|
||||||
|
// The libevm additions to go-ethereum are free software: you can redistribute
|
||||||
|
// them and/or modify them under the terms of the GNU Lesser General Public License
|
||||||
|
// as published by the Free Software Foundation, either version 3 of the License,
|
||||||
|
// or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The libevm additions are distributed in the hope that they will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see
|
||||||
|
// <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package legacy provides converters between legacy types and their refactored
|
||||||
|
// equivalents.
|
||||||
|
package legacy
|
||||||
|
|
||||||
|
import "github.com/ava-labs/libevm/core/vm"
|
||||||
|
|
||||||
|
// PrecompiledStatefulContract is the legacy signature of
|
||||||
|
// [vm.PrecompiledStatefulContract], which explicitly accepts and returns gas
|
||||||
|
// values. Instances SHOULD NOT use the [vm.PrecompileEnvironment]
|
||||||
|
// gas-management methods as this may result in unexpected behaviour.
|
||||||
|
type PrecompiledStatefulContract func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error)
|
||||||
|
|
||||||
|
// Upgrade converts the legacy precompile signature into the now-required form.
|
||||||
|
func (c PrecompiledStatefulContract) Upgrade() vm.PrecompiledStatefulContract {
|
||||||
|
return func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
|
||||||
|
gas := env.Gas()
|
||||||
|
ret, remainingGas, err := c(env, input, gas)
|
||||||
|
if used := gas - remainingGas; used < gas {
|
||||||
|
env.UseGas(used)
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue