diff --git a/core/vm/contracts.libevm.go b/core/vm/contracts.libevm.go index 7d06638ae99..d56dbd1c396 100644 --- a/core/vm/contracts.libevm.go +++ b/core/vm/contracts.libevm.go @@ -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, } } diff --git a/core/vm/contracts.libevm_test.go b/core/vm/contracts.libevm_test.go index 6f9eec3b0a7..525528fb414 100644 --- a/core/vm/contracts.libevm_test.go +++ b/core/vm/contracts.libevm_test.go @@ -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 -} diff --git a/core/vm/environment.libevm.go b/core/vm/environment.libevm.go index 7c3ed811f87..edd6c4314b6 100644 --- a/core/vm/environment.libevm.go +++ b/core/vm/environment.libevm.go @@ -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, + }, } } diff --git a/core/vm/evm.libevm.go b/core/vm/evm.libevm.go index 956885a1319..c2c807c1378 100644 --- a/core/vm/evm.libevm.go +++ b/core/vm/evm.libevm.go @@ -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, ) diff --git a/libevm/libevm.go b/libevm/libevm.go index a1817ee52e5..a429d604879 100644 --- a/libevm/libevm.go +++ b/libevm/libevm.go @@ -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 } diff --git a/params/hooks.libevm.go b/params/hooks.libevm.go index 15f9c01ee2e..cdff4e436e7 100644 --- a/params/hooks.libevm.go +++ b/params/hooks.libevm.go @@ -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 }