mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 05:41:35 +00:00
feat!: disambiguate EVM-semantic and raw caller/self addresses for precompiles (#211)
## Why this should be merged Provides precompiles with unambiguous access to contextual addresses, without the consumer needing to understand how they change under different call types. ## How this works The `libevm.AddressContext` type, which used to carry 3 addresses, now provides different versions of `Caller` and `Self`. The EVM-semantic versions are as defined by the rules of the EVM (and available before this change). The raw versions are the unmodified caller and self. ## How this was tested Extension of existing UTs to include raw addresses in addition to existing, EVM-semantic ones.
This commit is contained in:
parent
e35febe777
commit
e7035f19ee
6 changed files with 144 additions and 67 deletions
|
|
@ -223,9 +223,11 @@ func (args *evmCallArgs) env() *environment {
|
|||
}
|
||||
|
||||
return &environment{
|
||||
evm: args.evm,
|
||||
self: contract,
|
||||
callType: args.callType,
|
||||
evm: args.evm,
|
||||
self: contract,
|
||||
callType: args.callType,
|
||||
rawCaller: args.caller.Address(),
|
||||
rawSelf: args.addr,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ type statefulPrecompileOutput struct {
|
|||
func (o statefulPrecompileOutput) String() string {
|
||||
var lines []string
|
||||
out := reflect.ValueOf(o)
|
||||
FieldLoop:
|
||||
for i, n := 0, out.NumField(); i < n; i++ {
|
||||
name := out.Type().Field(i).Name
|
||||
fld := out.Field(i).Interface()
|
||||
|
|
@ -129,7 +130,12 @@ func (o statefulPrecompileOutput) String() string {
|
|||
case []byte:
|
||||
verb = "%#x"
|
||||
case *libevm.AddressContext:
|
||||
verb = "%+v"
|
||||
lines = append(
|
||||
lines,
|
||||
fmt.Sprintf("EVMSemantic addresses: %+v", o.Addresses.EVMSemantic),
|
||||
fmt.Sprintf("Raw addresses: %+v", o.Addresses.Raw),
|
||||
)
|
||||
continue FieldLoop
|
||||
case vm.CallType:
|
||||
verb = "%d (%[2]q)"
|
||||
}
|
||||
|
|
@ -211,6 +217,13 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
|||
state.SetBalance(caller, new(uint256.Int).Not(uint256.NewInt(0)))
|
||||
evm.Origin = eoa
|
||||
|
||||
// By definition, the raw caller and self are the same for every test case,
|
||||
// regardless of the incoming call type.
|
||||
rawAddresses := libevm.CallerAndSelf{
|
||||
Caller: caller,
|
||||
Self: precompile,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
call func() ([]byte, uint64, error)
|
||||
|
|
@ -227,9 +240,9 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
|||
return evm.Call(callerContract, precompile, input, gasLimit, transferValue)
|
||||
},
|
||||
wantAddresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller,
|
||||
Self: precompile,
|
||||
Origin: eoa,
|
||||
EVMSemantic: rawAddresses,
|
||||
Raw: &rawAddresses,
|
||||
},
|
||||
wantReadOnly: false,
|
||||
wantTransferValue: transferValue,
|
||||
|
|
@ -242,8 +255,11 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
|||
},
|
||||
wantAddresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller,
|
||||
Self: caller,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller,
|
||||
Self: caller,
|
||||
},
|
||||
Raw: &rawAddresses,
|
||||
},
|
||||
wantReadOnly: false,
|
||||
wantTransferValue: transferValue,
|
||||
|
|
@ -256,8 +272,11 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
|||
},
|
||||
wantAddresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: eoa, // inherited from caller
|
||||
Self: caller,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: eoa, // inherited from caller
|
||||
Self: caller,
|
||||
},
|
||||
Raw: &rawAddresses,
|
||||
},
|
||||
wantReadOnly: false,
|
||||
wantTransferValue: uint256.NewInt(0),
|
||||
|
|
@ -269,9 +288,9 @@ func TestNewStatefulPrecompile(t *testing.T) {
|
|||
return evm.StaticCall(callerContract, precompile, input, gasLimit)
|
||||
},
|
||||
wantAddresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller,
|
||||
Self: precompile,
|
||||
Origin: eoa,
|
||||
EVMSemantic: rawAddresses,
|
||||
Raw: &rawAddresses,
|
||||
},
|
||||
wantReadOnly: true,
|
||||
wantTransferValue: uint256.NewInt(0),
|
||||
|
|
@ -527,7 +546,7 @@ func TestCanCreateContract(t *testing.T) {
|
|||
gasUsage := rng.Uint64n(gasLimit)
|
||||
|
||||
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.EVMSemantic.Caller, cc.EVMSemantic.Self, stateVal)
|
||||
}
|
||||
hooks := &hookstest.Stub{
|
||||
CanCreateContractFn: func(cc *libevm.AddressContext, gas uint64, s libevm.StateReader) (uint64, error) {
|
||||
|
|
@ -555,14 +574,34 @@ func TestCanCreateContract(t *testing.T) {
|
|||
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
|
||||
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,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller,
|
||||
Self: create,
|
||||
},
|
||||
// `Raw` is documented as always being nil.
|
||||
},
|
||||
value,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Create2",
|
||||
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
|
||||
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,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller,
|
||||
Self: create2,
|
||||
},
|
||||
// As above re `Raw` always being nil.
|
||||
},
|
||||
value,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -630,7 +669,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
if bytes.Equal(input, unsafeCallerProxyOptSentinel) {
|
||||
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. If this is ever extended to include DELEGATECALL or
|
||||
// CALLCODE then the expected [libevm.AddressContext.Raw] values
|
||||
// of the tests cases also need to change.
|
||||
return env.Call(dest, precompileCallData, env.Gas(), uint256.NewInt(0), opts...)
|
||||
}),
|
||||
dest: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) {
|
||||
|
|
@ -663,8 +705,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: sut,
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: sut,
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
},
|
||||
|
|
@ -675,8 +719,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller, // overridden by CallOption
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller, // overridden by CallOption
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
},
|
||||
|
|
@ -686,8 +732,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller, // SUT runs as its own caller because of CALLCODE
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller, // SUT runs as its own caller because of CALLCODE
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
},
|
||||
|
|
@ -698,8 +746,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller, // CallOption is a NOOP
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller, // CallOption is a NOOP
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
},
|
||||
|
|
@ -709,8 +759,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller, // as with CALLCODE
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller, // as with CALLCODE
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
},
|
||||
|
|
@ -721,8 +773,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller, // CallOption is a NOOP
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller, // CallOption is a NOOP
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
},
|
||||
|
|
@ -732,8 +786,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: sut,
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: sut,
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
// This demonstrates that even though the precompile makes a
|
||||
|
|
@ -749,8 +805,10 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
want: statefulPrecompileOutput{
|
||||
Addresses: &libevm.AddressContext{
|
||||
Origin: eoa,
|
||||
Caller: caller, // overridden by CallOption
|
||||
Self: dest,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller, // overridden by CallOption
|
||||
Self: dest,
|
||||
},
|
||||
},
|
||||
Input: precompileCallData,
|
||||
ReadOnly: true,
|
||||
|
|
@ -760,6 +818,9 @@ func TestPrecompileMakeCall(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.incomingCallType.String(), func(t *testing.T) {
|
||||
// From the perspective of `dest` after a CALL from `sut`.
|
||||
tt.want.Addresses.Raw = &tt.want.Addresses.EVMSemantic
|
||||
|
||||
t.Logf("calldata = %q", tt.eoaTxCallData)
|
||||
state, evm := ethtest.NewZeroEVM(t)
|
||||
evm.Origin = eoa
|
||||
|
|
@ -816,26 +877,3 @@ func TestPrecompileCallWithTracer(t *testing.T) {
|
|||
require.NoErrorf(t, json.Unmarshal(gotJSON, &got), "json.Unmarshal(%T.GetResult(), %T)", tracer, &got)
|
||||
require.Equal(t, value, got[contract].Storage[zeroHash], "value loaded with SLOAD")
|
||||
}
|
||||
|
||||
//nolint:testableexamples // Including output would only make the example more complicated and hide the true intent
|
||||
func ExamplePrecompileEnvironment() {
|
||||
// To determine the actual caller of a precompile, as against the effective
|
||||
// caller (under EVM rules, as exposed by `Addresses().Caller`):
|
||||
actualCaller := func(env vm.PrecompileEnvironment) common.Address {
|
||||
if env.IncomingCallType() == vm.DelegateCall {
|
||||
// DelegateCall acts as if it were its own caller.
|
||||
return env.Addresses().Self
|
||||
}
|
||||
// CallCode could return either `Self` or `Caller` as it acts as its
|
||||
// caller but doesn't inherit the caller's caller as DelegateCall does.
|
||||
// Having it handled here is arbitrary from a behavioural perspective
|
||||
// and is done only to simplify the code.
|
||||
//
|
||||
// Call and StaticCall don't affect self/caller semantics in any way.
|
||||
return env.Addresses().Caller
|
||||
}
|
||||
|
||||
// actualCaller would typically be a top-level function. It's only a
|
||||
// variable to include it in this example function.
|
||||
_ = actualCaller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ type environment struct {
|
|||
evm *EVM
|
||||
self *Contract
|
||||
callType CallType
|
||||
|
||||
rawSelf, rawCaller common.Address
|
||||
}
|
||||
|
||||
func (e *environment) Gas() uint64 { return e.self.Gas }
|
||||
|
|
@ -79,8 +81,14 @@ func (e *environment) ReadOnly() bool {
|
|||
func (e *environment) Addresses() *libevm.AddressContext {
|
||||
return &libevm.AddressContext{
|
||||
Origin: e.evm.Origin,
|
||||
Caller: e.self.CallerAddress,
|
||||
Self: e.self.Address(),
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: e.self.CallerAddress,
|
||||
Self: e.self.Address(),
|
||||
},
|
||||
Raw: &libevm.CallerAndSelf{
|
||||
Caller: e.rawCaller,
|
||||
Self: e.rawSelf,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,15 @@ import (
|
|||
// canCreateContract is a convenience wrapper for calling the
|
||||
// [params.RulesHooks.CanCreateContract] hook.
|
||||
func (evm *EVM) canCreateContract(caller ContractRef, contractToCreate common.Address, gas uint64) (remainingGas uint64, _ error) {
|
||||
addrs := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: contractToCreate}
|
||||
addrs := &libevm.AddressContext{
|
||||
Origin: evm.Origin,
|
||||
EVMSemantic: libevm.CallerAndSelf{
|
||||
Caller: caller.Address(),
|
||||
Self: contractToCreate,
|
||||
},
|
||||
// The "raw" caller isn't guaranteed to be known if the caller is a
|
||||
// delegate so the `Raw` field is documented as always being nil.
|
||||
}
|
||||
gas, err := evm.chainRules.Hooks().CanCreateContract(addrs, gas, evm.StateDB)
|
||||
|
||||
// NOTE that this block only performs logging and that all paths propagate
|
||||
|
|
@ -34,8 +42,8 @@ func (evm *EVM) canCreateContract(caller ContractRef, contractToCreate common.Ad
|
|||
log.Debug(
|
||||
"Contract creation blocked by libevm hook",
|
||||
"origin", addrs.Origin,
|
||||
"caller", addrs.Caller,
|
||||
"contract", addrs.Self,
|
||||
"caller", addrs.EVMSemantic.Caller,
|
||||
"contract", addrs.EVMSemantic.Self,
|
||||
"hooks", log.TypeOf(evm.chainRules.Hooks()),
|
||||
"reason", err,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -61,10 +61,30 @@ type StateReader interface {
|
|||
// AddressContext carries addresses available to contexts such as calls and
|
||||
// contract creation.
|
||||
//
|
||||
// With respect to contract creation, the Self address MAY be the predicted
|
||||
// address of the contract about to be deployed, which may not exist yet.
|
||||
// With respect to contract creation, the EVMSemantic.Self address MAY be the
|
||||
// predicted address of the contract about to be deployed, which might not exist
|
||||
// yet.
|
||||
type AddressContext struct {
|
||||
Origin common.Address // equivalent to vm.ORIGIN op code
|
||||
Caller common.Address // equivalent to vm.CALLER op code
|
||||
Self common.Address // equivalent to vm.ADDRESS op code
|
||||
// EVMSemantic addresses are those defined by the rules of the EVM, based on
|
||||
// the type of call made to a contract; i.e. the addresses pushed to the
|
||||
// stack by the vm.CALLER and vm.SELF op codes, respectively.
|
||||
EVMSemantic CallerAndSelf
|
||||
// Raw addresses are those that would be available to a contract under a
|
||||
// standard CALL; i.e. not interpreted according EVM rules. They are the
|
||||
// "intuitive" addresses such that the `Caller` is the account that called
|
||||
// `Self` even if it did so via DELEGATECALL or CALLCODE (in which cases
|
||||
// `Raw` and `EVMSemantic` would differ).
|
||||
//
|
||||
// Raw MUST NOT be nil when returned to a precompile implementation but MAY
|
||||
// be nil in other situations (e.g. hooks), which MUST document behaviour on
|
||||
// a case-by-case basis.
|
||||
Raw *CallerAndSelf
|
||||
}
|
||||
|
||||
// CallerAndSelf carries said addresses for use in an [AddressContext], where
|
||||
// the definitions of `Caller` and `Self` are defined based on context.
|
||||
type CallerAndSelf struct {
|
||||
Caller common.Address
|
||||
Self common.Address
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ type RulesHooks interface {
|
|||
// by returning a nil (allowed) or non-nil (blocked) error.
|
||||
type RulesAllowlistHooks interface {
|
||||
// CanCreateContract is called after the deployer's nonce is incremented but
|
||||
// before all other state-modifying actions.
|
||||
// before all other state-modifying actions. The [libevm.AddressContext.Raw]
|
||||
// field will always be nil.
|
||||
CanCreateContract(_ *libevm.AddressContext, gas uint64, _ libevm.StateReader) (gasRemaining uint64, _ error)
|
||||
CanExecuteTransaction(from common.Address, to *common.Address, _ libevm.StateReader) error
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue