Skip to content

Commit e7035f1

Browse files
authored
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.
1 parent e35febe commit e7035f1

File tree

6 files changed

+144
-67
lines changed

6 files changed

+144
-67
lines changed

core/vm/contracts.libevm.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,11 @@ func (args *evmCallArgs) env() *environment {
223223
}
224224

225225
return &environment{
226-
evm: args.evm,
227-
self: contract,
228-
callType: args.callType,
226+
evm: args.evm,
227+
self: contract,
228+
callType: args.callType,
229+
rawCaller: args.caller.Address(),
230+
rawSelf: args.addr,
229231
}
230232
}
231233

core/vm/contracts.libevm_test.go

Lines changed: 92 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ type statefulPrecompileOutput struct {
120120
func (o statefulPrecompileOutput) String() string {
121121
var lines []string
122122
out := reflect.ValueOf(o)
123+
FieldLoop:
123124
for i, n := 0, out.NumField(); i < n; i++ {
124125
name := out.Type().Field(i).Name
125126
fld := out.Field(i).Interface()
@@ -129,7 +130,12 @@ func (o statefulPrecompileOutput) String() string {
129130
case []byte:
130131
verb = "%#x"
131132
case *libevm.AddressContext:
132-
verb = "%+v"
133+
lines = append(
134+
lines,
135+
fmt.Sprintf("EVMSemantic addresses: %+v", o.Addresses.EVMSemantic),
136+
fmt.Sprintf("Raw addresses: %+v", o.Addresses.Raw),
137+
)
138+
continue FieldLoop
133139
case vm.CallType:
134140
verb = "%d (%[2]q)"
135141
}
@@ -211,6 +217,13 @@ func TestNewStatefulPrecompile(t *testing.T) {
211217
state.SetBalance(caller, new(uint256.Int).Not(uint256.NewInt(0)))
212218
evm.Origin = eoa
213219

220+
// By definition, the raw caller and self are the same for every test case,
221+
// regardless of the incoming call type.
222+
rawAddresses := libevm.CallerAndSelf{
223+
Caller: caller,
224+
Self: precompile,
225+
}
226+
214227
tests := []struct {
215228
name string
216229
call func() ([]byte, uint64, error)
@@ -227,9 +240,9 @@ func TestNewStatefulPrecompile(t *testing.T) {
227240
return evm.Call(callerContract, precompile, input, gasLimit, transferValue)
228241
},
229242
wantAddresses: &libevm.AddressContext{
230-
Origin: eoa,
231-
Caller: caller,
232-
Self: precompile,
243+
Origin: eoa,
244+
EVMSemantic: rawAddresses,
245+
Raw: &rawAddresses,
233246
},
234247
wantReadOnly: false,
235248
wantTransferValue: transferValue,
@@ -242,8 +255,11 @@ func TestNewStatefulPrecompile(t *testing.T) {
242255
},
243256
wantAddresses: &libevm.AddressContext{
244257
Origin: eoa,
245-
Caller: caller,
246-
Self: caller,
258+
EVMSemantic: libevm.CallerAndSelf{
259+
Caller: caller,
260+
Self: caller,
261+
},
262+
Raw: &rawAddresses,
247263
},
248264
wantReadOnly: false,
249265
wantTransferValue: transferValue,
@@ -256,8 +272,11 @@ func TestNewStatefulPrecompile(t *testing.T) {
256272
},
257273
wantAddresses: &libevm.AddressContext{
258274
Origin: eoa,
259-
Caller: eoa, // inherited from caller
260-
Self: caller,
275+
EVMSemantic: libevm.CallerAndSelf{
276+
Caller: eoa, // inherited from caller
277+
Self: caller,
278+
},
279+
Raw: &rawAddresses,
261280
},
262281
wantReadOnly: false,
263282
wantTransferValue: uint256.NewInt(0),
@@ -269,9 +288,9 @@ func TestNewStatefulPrecompile(t *testing.T) {
269288
return evm.StaticCall(callerContract, precompile, input, gasLimit)
270289
},
271290
wantAddresses: &libevm.AddressContext{
272-
Origin: eoa,
273-
Caller: caller,
274-
Self: precompile,
291+
Origin: eoa,
292+
EVMSemantic: rawAddresses,
293+
Raw: &rawAddresses,
275294
},
276295
wantReadOnly: true,
277296
wantTransferValue: uint256.NewInt(0),
@@ -527,7 +546,7 @@ func TestCanCreateContract(t *testing.T) {
527546
gasUsage := rng.Uint64n(gasLimit)
528547

529548
makeErr := func(cc *libevm.AddressContext, stateVal common.Hash) error {
530-
return fmt.Errorf("Origin: %v Caller: %v Contract: %v State: %v", cc.Origin, cc.Caller, cc.Self, stateVal)
549+
return fmt.Errorf("Origin: %v Caller: %v Contract: %v State: %v", cc.Origin, cc.EVMSemantic.Caller, cc.EVMSemantic.Self, stateVal)
531550
}
532551
hooks := &hookstest.Stub{
533552
CanCreateContractFn: func(cc *libevm.AddressContext, gas uint64, s libevm.StateReader) (uint64, error) {
@@ -555,14 +574,34 @@ func TestCanCreateContract(t *testing.T) {
555574
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
556575
return evm.Create(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0))
557576
},
558-
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create}, value),
577+
wantErr: makeErr(
578+
&libevm.AddressContext{
579+
Origin: origin,
580+
EVMSemantic: libevm.CallerAndSelf{
581+
Caller: caller,
582+
Self: create,
583+
},
584+
// `Raw` is documented as always being nil.
585+
},
586+
value,
587+
),
559588
},
560589
{
561590
name: "Create2",
562591
create: func(evm *vm.EVM) ([]byte, common.Address, uint64, error) {
563592
return evm.Create2(vm.AccountRef(caller), code, gasLimit, uint256.NewInt(0), new(uint256.Int).SetBytes(salt[:]))
564593
},
565-
wantErr: makeErr(&libevm.AddressContext{Origin: origin, Caller: caller, Self: create2}, value),
594+
wantErr: makeErr(
595+
&libevm.AddressContext{
596+
Origin: origin,
597+
EVMSemantic: libevm.CallerAndSelf{
598+
Caller: caller,
599+
Self: create2,
600+
},
601+
// As above re `Raw` always being nil.
602+
},
603+
value,
604+
),
566605
},
567606
}
568607

@@ -630,7 +669,10 @@ func TestPrecompileMakeCall(t *testing.T) {
630669
if bytes.Equal(input, unsafeCallerProxyOptSentinel) {
631670
opts = append(opts, vm.WithUNSAFECallerAddressProxying())
632671
}
633-
// We are ultimately testing env.Call(), hence why this is the SUT.
672+
// We are ultimately testing env.Call(), hence why this is the
673+
// SUT. If this is ever extended to include DELEGATECALL or
674+
// CALLCODE then the expected [libevm.AddressContext.Raw] values
675+
// of the tests cases also need to change.
634676
return env.Call(dest, precompileCallData, env.Gas(), uint256.NewInt(0), opts...)
635677
}),
636678
dest: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) {
@@ -663,8 +705,10 @@ func TestPrecompileMakeCall(t *testing.T) {
663705
want: statefulPrecompileOutput{
664706
Addresses: &libevm.AddressContext{
665707
Origin: eoa,
666-
Caller: sut,
667-
Self: dest,
708+
EVMSemantic: libevm.CallerAndSelf{
709+
Caller: sut,
710+
Self: dest,
711+
},
668712
},
669713
Input: precompileCallData,
670714
},
@@ -675,8 +719,10 @@ func TestPrecompileMakeCall(t *testing.T) {
675719
want: statefulPrecompileOutput{
676720
Addresses: &libevm.AddressContext{
677721
Origin: eoa,
678-
Caller: caller, // overridden by CallOption
679-
Self: dest,
722+
EVMSemantic: libevm.CallerAndSelf{
723+
Caller: caller, // overridden by CallOption
724+
Self: dest,
725+
},
680726
},
681727
Input: precompileCallData,
682728
},
@@ -686,8 +732,10 @@ func TestPrecompileMakeCall(t *testing.T) {
686732
want: statefulPrecompileOutput{
687733
Addresses: &libevm.AddressContext{
688734
Origin: eoa,
689-
Caller: caller, // SUT runs as its own caller because of CALLCODE
690-
Self: dest,
735+
EVMSemantic: libevm.CallerAndSelf{
736+
Caller: caller, // SUT runs as its own caller because of CALLCODE
737+
Self: dest,
738+
},
691739
},
692740
Input: precompileCallData,
693741
},
@@ -698,8 +746,10 @@ func TestPrecompileMakeCall(t *testing.T) {
698746
want: statefulPrecompileOutput{
699747
Addresses: &libevm.AddressContext{
700748
Origin: eoa,
701-
Caller: caller, // CallOption is a NOOP
702-
Self: dest,
749+
EVMSemantic: libevm.CallerAndSelf{
750+
Caller: caller, // CallOption is a NOOP
751+
Self: dest,
752+
},
703753
},
704754
Input: precompileCallData,
705755
},
@@ -709,8 +759,10 @@ func TestPrecompileMakeCall(t *testing.T) {
709759
want: statefulPrecompileOutput{
710760
Addresses: &libevm.AddressContext{
711761
Origin: eoa,
712-
Caller: caller, // as with CALLCODE
713-
Self: dest,
762+
EVMSemantic: libevm.CallerAndSelf{
763+
Caller: caller, // as with CALLCODE
764+
Self: dest,
765+
},
714766
},
715767
Input: precompileCallData,
716768
},
@@ -721,8 +773,10 @@ func TestPrecompileMakeCall(t *testing.T) {
721773
want: statefulPrecompileOutput{
722774
Addresses: &libevm.AddressContext{
723775
Origin: eoa,
724-
Caller: caller, // CallOption is a NOOP
725-
Self: dest,
776+
EVMSemantic: libevm.CallerAndSelf{
777+
Caller: caller, // CallOption is a NOOP
778+
Self: dest,
779+
},
726780
},
727781
Input: precompileCallData,
728782
},
@@ -732,8 +786,10 @@ func TestPrecompileMakeCall(t *testing.T) {
732786
want: statefulPrecompileOutput{
733787
Addresses: &libevm.AddressContext{
734788
Origin: eoa,
735-
Caller: sut,
736-
Self: dest,
789+
EVMSemantic: libevm.CallerAndSelf{
790+
Caller: sut,
791+
Self: dest,
792+
},
737793
},
738794
Input: precompileCallData,
739795
// This demonstrates that even though the precompile makes a
@@ -749,8 +805,10 @@ func TestPrecompileMakeCall(t *testing.T) {
749805
want: statefulPrecompileOutput{
750806
Addresses: &libevm.AddressContext{
751807
Origin: eoa,
752-
Caller: caller, // overridden by CallOption
753-
Self: dest,
808+
EVMSemantic: libevm.CallerAndSelf{
809+
Caller: caller, // overridden by CallOption
810+
Self: dest,
811+
},
754812
},
755813
Input: precompileCallData,
756814
ReadOnly: true,
@@ -760,6 +818,9 @@ func TestPrecompileMakeCall(t *testing.T) {
760818

761819
for _, tt := range tests {
762820
t.Run(tt.incomingCallType.String(), func(t *testing.T) {
821+
// From the perspective of `dest` after a CALL from `sut`.
822+
tt.want.Addresses.Raw = &tt.want.Addresses.EVMSemantic
823+
763824
t.Logf("calldata = %q", tt.eoaTxCallData)
764825
state, evm := ethtest.NewZeroEVM(t)
765826
evm.Origin = eoa
@@ -816,26 +877,3 @@ func TestPrecompileCallWithTracer(t *testing.T) {
816877
require.NoErrorf(t, json.Unmarshal(gotJSON, &got), "json.Unmarshal(%T.GetResult(), %T)", tracer, &got)
817878
require.Equal(t, value, got[contract].Storage[zeroHash], "value loaded with SLOAD")
818879
}
819-
820-
//nolint:testableexamples // Including output would only make the example more complicated and hide the true intent
821-
func ExamplePrecompileEnvironment() {
822-
// To determine the actual caller of a precompile, as against the effective
823-
// caller (under EVM rules, as exposed by `Addresses().Caller`):
824-
actualCaller := func(env vm.PrecompileEnvironment) common.Address {
825-
if env.IncomingCallType() == vm.DelegateCall {
826-
// DelegateCall acts as if it were its own caller.
827-
return env.Addresses().Self
828-
}
829-
// CallCode could return either `Self` or `Caller` as it acts as its
830-
// caller but doesn't inherit the caller's caller as DelegateCall does.
831-
// Having it handled here is arbitrary from a behavioural perspective
832-
// and is done only to simplify the code.
833-
//
834-
// Call and StaticCall don't affect self/caller semantics in any way.
835-
return env.Addresses().Caller
836-
}
837-
838-
// actualCaller would typically be a top-level function. It's only a
839-
// variable to include it in this example function.
840-
_ = actualCaller
841-
}

core/vm/environment.libevm.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type environment struct {
3636
evm *EVM
3737
self *Contract
3838
callType CallType
39+
40+
rawSelf, rawCaller common.Address
3941
}
4042

4143
func (e *environment) Gas() uint64 { return e.self.Gas }
@@ -79,8 +81,14 @@ func (e *environment) ReadOnly() bool {
7981
func (e *environment) Addresses() *libevm.AddressContext {
8082
return &libevm.AddressContext{
8183
Origin: e.evm.Origin,
82-
Caller: e.self.CallerAddress,
83-
Self: e.self.Address(),
84+
EVMSemantic: libevm.CallerAndSelf{
85+
Caller: e.self.CallerAddress,
86+
Self: e.self.Address(),
87+
},
88+
Raw: &libevm.CallerAndSelf{
89+
Caller: e.rawCaller,
90+
Self: e.rawSelf,
91+
},
8492
}
8593
}
8694

core/vm/evm.libevm.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ import (
2525
// canCreateContract is a convenience wrapper for calling the
2626
// [params.RulesHooks.CanCreateContract] hook.
2727
func (evm *EVM) canCreateContract(caller ContractRef, contractToCreate common.Address, gas uint64) (remainingGas uint64, _ error) {
28-
addrs := &libevm.AddressContext{Origin: evm.Origin, Caller: caller.Address(), Self: contractToCreate}
28+
addrs := &libevm.AddressContext{
29+
Origin: evm.Origin,
30+
EVMSemantic: libevm.CallerAndSelf{
31+
Caller: caller.Address(),
32+
Self: contractToCreate,
33+
},
34+
// The "raw" caller isn't guaranteed to be known if the caller is a
35+
// delegate so the `Raw` field is documented as always being nil.
36+
}
2937
gas, err := evm.chainRules.Hooks().CanCreateContract(addrs, gas, evm.StateDB)
3038

3139
// 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
3442
log.Debug(
3543
"Contract creation blocked by libevm hook",
3644
"origin", addrs.Origin,
37-
"caller", addrs.Caller,
38-
"contract", addrs.Self,
45+
"caller", addrs.EVMSemantic.Caller,
46+
"contract", addrs.EVMSemantic.Self,
3947
"hooks", log.TypeOf(evm.chainRules.Hooks()),
4048
"reason", err,
4149
)

libevm/libevm.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,30 @@ type StateReader interface {
6161
// AddressContext carries addresses available to contexts such as calls and
6262
// contract creation.
6363
//
64-
// With respect to contract creation, the Self address MAY be the predicted
65-
// address of the contract about to be deployed, which may not exist yet.
64+
// With respect to contract creation, the EVMSemantic.Self address MAY be the
65+
// predicted address of the contract about to be deployed, which might not exist
66+
// yet.
6667
type AddressContext struct {
6768
Origin common.Address // equivalent to vm.ORIGIN op code
68-
Caller common.Address // equivalent to vm.CALLER op code
69-
Self common.Address // equivalent to vm.ADDRESS op code
69+
// EVMSemantic addresses are those defined by the rules of the EVM, based on
70+
// the type of call made to a contract; i.e. the addresses pushed to the
71+
// stack by the vm.CALLER and vm.SELF op codes, respectively.
72+
EVMSemantic CallerAndSelf
73+
// Raw addresses are those that would be available to a contract under a
74+
// standard CALL; i.e. not interpreted according EVM rules. They are the
75+
// "intuitive" addresses such that the `Caller` is the account that called
76+
// `Self` even if it did so via DELEGATECALL or CALLCODE (in which cases
77+
// `Raw` and `EVMSemantic` would differ).
78+
//
79+
// Raw MUST NOT be nil when returned to a precompile implementation but MAY
80+
// be nil in other situations (e.g. hooks), which MUST document behaviour on
81+
// a case-by-case basis.
82+
Raw *CallerAndSelf
83+
}
84+
85+
// CallerAndSelf carries said addresses for use in an [AddressContext], where
86+
// the definitions of `Caller` and `Self` are defined based on context.
87+
type CallerAndSelf struct {
88+
Caller common.Address
89+
Self common.Address
7090
}

0 commit comments

Comments
 (0)