Skip to content

Commit 8bdefb9

Browse files
committed
Merge branch 'main' into arr4n/reentrancy-guard
2 parents 358a2d2 + 35926db commit 8bdefb9

14 files changed

+506
-58
lines changed

core/state/statedb.libevm.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ func RegisterExtras(s StateDBHooks) {
8282
registeredExtras.MustRegister(s)
8383
}
8484

85+
// WithTempRegisteredExtras temporarily registers `s` as if calling
86+
// [RegisterExtras] the same type parameter. After `fn` returns, the
87+
// registration is returned to its former state, be that none or the types
88+
// originally passed to [RegisterExtras].
89+
//
90+
// This MUST NOT be used on a live chain. It is solely intended for off-chain
91+
// consumers that require access to extras. Said consumers SHOULD NOT, however
92+
// call this function directly. Use the libevm/temporary.WithRegisteredExtras()
93+
// function instead as it atomically overrides all possible packages.
94+
func WithTempRegisteredExtras(s StateDBHooks, fn func()) {
95+
registeredExtras.TempOverride(s, fn)
96+
}
97+
8598
// TestOnlyClearRegisteredExtras clears the arguments previously passed to
8699
// [RegisterExtras]. It panics if called from a non-testing call stack.
87100
//

core/state/statedb.libevm_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ func (highByteFlipper) TransformStateKey(_ common.Address, key common.Hash) comm
150150
return flipHighByte(key)
151151
}
152152

153+
type noopHooks struct{}
154+
155+
func (noopHooks) TransformStateKey(_ common.Address, key common.Hash) common.Hash {
156+
return key
157+
}
158+
153159
func TestTransformStateKey(t *testing.T) {
154160
rawdb := rawdb.NewMemoryDatabase()
155161
trie := triedb.NewDatabase(rawdb, nil)
@@ -209,6 +215,16 @@ func TestTransformStateKey(t *testing.T) {
209215
assertCommittedEq(t, flippedKey, regularVal)
210216
assertCommittedEq(t, flippedKey, flippedVal, noTransform)
211217

218+
t.Run("WithTempRegisteredExtras", func(t *testing.T) {
219+
WithTempRegisteredExtras(noopHooks{}, func() {
220+
// No-op hooks are equivalent to using the `noTransform` option.
221+
// NOTE this is NOT the intended usage of [WithTempRegisteredExtras]
222+
// and is simply an easy way to test the temporary registration.
223+
assertEq(t, regularKey, regularVal)
224+
assertEq(t, flippedKey, flippedVal)
225+
})
226+
})
227+
212228
updatedVal := common.Hash{'u', 'p', 'd', 'a', 't', 'e', 'd'}
213229
sdb.SetState(addr, regularKey, updatedVal)
214230
assertEq(t, regularKey, updatedVal)

core/types/block.libevm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ type BlockBodyHooks interface {
130130
// to no type having been registered.
131131
type NOOPBlockBodyHooks struct{}
132132

133-
var _ BlockBodyPayload[*NOOPBlockBodyHooks] = NOOPBlockBodyHooks{}
133+
var _ BlockBodyPayload[*NOOPBlockBodyHooks] = (*NOOPBlockBodyHooks)(nil)
134134

135135
func (NOOPBlockBodyHooks) Copy() *NOOPBlockBodyHooks { return &NOOPBlockBodyHooks{} }
136136

core/types/rlp_payload.libevm.go

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,27 @@ import (
4444
// [Header] or [Block] / [Body] is a non-nil `HPtr` or `BPtr` respectively. The
4545
// latter guarantee ensures that hooks won't be called on nil-pointer receivers.
4646
func RegisterExtras[
47-
H any, HPtr interface {
48-
HeaderHooks
49-
*H
50-
},
51-
B any, BPtr interface {
52-
BlockBodyPayload[BPtr]
53-
*B
54-
},
47+
H any, HPtr HeaderHooksPointer[H],
48+
B any, BPtr BlockBodyHooksPointer[B, BPtr],
5549
SA any,
5650
]() ExtraPayloads[HPtr, BPtr, SA] {
57-
extra := ExtraPayloads[HPtr, BPtr, SA]{
51+
payloads, ctors := payloadsAndConstructors[H, HPtr, B, BPtr, SA]()
52+
registeredExtras.MustRegister(ctors)
53+
log.Info(
54+
"Registered core/types extras",
55+
"Header", log.TypeOf(pseudo.Zero[HPtr]().Value.Get()),
56+
"Block/Body", log.TypeOf(pseudo.Zero[BPtr]().Value.Get()),
57+
"StateAccount", log.TypeOf(pseudo.Zero[SA]().Value.Get()),
58+
)
59+
return payloads
60+
}
61+
62+
func payloadsAndConstructors[
63+
H any, HPtr HeaderHooksPointer[H],
64+
B any, BPtr BlockBodyHooksPointer[B, BPtr],
65+
SA any,
66+
]() (ExtraPayloads[HPtr, BPtr, SA], *extraConstructors) {
67+
payloads := ExtraPayloads[HPtr, BPtr, SA]{
5868
Header: pseudo.NewAccessor[*Header, HPtr](
5969
(*Header).extraPayload,
6070
func(h *Header, t *pseudo.Type) { h.extra = t },
@@ -72,7 +82,7 @@ func RegisterExtras[
7282
func(a StateOrSlimAccount, t *pseudo.Type) { a.extra().t = t },
7383
),
7484
}
75-
registeredExtras.MustRegister(&extraConstructors{
85+
ctors := &extraConstructors{
7686
stateAccountType: func() string {
7787
var x SA
7888
return fmt.Sprintf("%T", x)
@@ -84,23 +94,51 @@ func RegisterExtras[
8494
newHeader: pseudo.NewConstructor[H]().NewPointer, // i.e. non-nil HPtr
8595
newBlockOrBody: pseudo.NewConstructor[B]().NewPointer, // i.e. non-nil BPtr
8696
newStateAccount: pseudo.NewConstructor[SA]().Zero,
87-
hooks: extra,
88-
})
89-
log.Info(
90-
"Registered core/types extras",
91-
"Header", log.TypeOf(pseudo.Zero[HPtr]().Value.Get()),
92-
"Block/Body", log.TypeOf(pseudo.Zero[BPtr]().Value.Get()),
93-
"StateAccount", log.TypeOf(pseudo.Zero[SA]().Value.Get()),
94-
)
95-
return extra
97+
hooks: payloads,
98+
}
99+
return payloads, ctors
100+
}
101+
102+
// WithTempRegisteredExtras temporarily registers `HPtr`, `BPtr`, and `SA` as if
103+
// calling [RegisterExtras] the same type parameters. The [ExtraPayloads] are
104+
// passed to `fn` instead of being returned; the argument MUST NOT be persisted
105+
// beyond the life of `fn`. After `fn` returns, the registration is returned to
106+
// its former state, be that none or the types originally passed to
107+
// [RegisterExtras].
108+
//
109+
// This MUST NOT be used on a live chain. It is solely intended for off-chain
110+
// consumers that require access to extras. Said consumers SHOULD NOT, however
111+
// call this function directly. Use the libevm/temporary.WithRegisteredExtras()
112+
// function instead as it atomically overrides all possible packages.
113+
func WithTempRegisteredExtras[
114+
H, B, SA any,
115+
HPtr HeaderHooksPointer[H],
116+
BPtr BlockBodyHooksPointer[B, BPtr],
117+
](fn func(ExtraPayloads[HPtr, BPtr, SA])) {
118+
payloads, ctors := payloadsAndConstructors[H, HPtr, B, BPtr, SA]()
119+
registeredExtras.TempOverride(ctors, func() { fn(payloads) })
120+
}
121+
122+
// A HeaderHooksPointer is a type constraint for an implementation of
123+
// [HeaderHooks] with a pointer receiver.
124+
type HeaderHooksPointer[H any] interface {
125+
HeaderHooks
126+
*H
127+
}
128+
129+
// A BlockBodyHooksPointer is a type constraint for an implementation of
130+
// [BlockBodyPayload] with a pointer receiver.
131+
type BlockBodyHooksPointer[B any, Self any] interface {
132+
BlockBodyPayload[Self]
133+
*B
96134
}
97135

98136
// A BlockBodyPayload is an implementation of [BlockBodyHooks] that is also able
99137
// to clone itself. Both [Block.Body] and [Block.WithBody] require this
100138
// functionality to copy the payload between the types.
101-
type BlockBodyPayload[BPtr any] interface {
139+
type BlockBodyPayload[Self any] interface {
102140
BlockBodyHooks
103-
Copy() BPtr
141+
Copy() Self
104142
}
105143

106144
// TestOnlyClearRegisteredExtras clears the [Extras] previously passed to
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package types
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
24+
25+
"github.com/ava-labs/libevm/rlp"
26+
)
27+
28+
type tempBlockBodyHooks struct {
29+
X string
30+
NOOPBlockBodyHooks
31+
}
32+
33+
func (b *tempBlockBodyHooks) Copy() *tempBlockBodyHooks {
34+
return &tempBlockBodyHooks{X: b.X}
35+
}
36+
37+
func (b *tempBlockBodyHooks) BlockRLPFieldsForEncoding(*BlockRLPProxy) *rlp.Fields {
38+
return &rlp.Fields{
39+
Required: []any{b.X},
40+
}
41+
}
42+
43+
func TestTempRegisteredExtras(t *testing.T) {
44+
TestOnlyClearRegisteredExtras()
45+
t.Cleanup(TestOnlyClearRegisteredExtras)
46+
47+
rlpWithoutHooks, err := rlp.EncodeToBytes(&Block{})
48+
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T) without hooks", &Block{})
49+
50+
extras := RegisterExtras[NOOPHeaderHooks, *NOOPHeaderHooks, NOOPBlockBodyHooks, *NOOPBlockBodyHooks, bool]()
51+
testPrimaryExtras := func(t *testing.T) {
52+
t.Helper()
53+
b := new(Block)
54+
got, err := rlp.EncodeToBytes(b)
55+
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T) with %T hooks", b, extras.Block.Get(b))
56+
assert.Equalf(t, rlpWithoutHooks, got, "rlp.EncodeToBytes(%T) with noop hooks; expect same as without hooks", b)
57+
}
58+
59+
t.Run("before_temp", testPrimaryExtras)
60+
t.Run("WithTempRegisteredExtras", func(t *testing.T) {
61+
WithTempRegisteredExtras(func(extras ExtraPayloads[*NOOPHeaderHooks, *tempBlockBodyHooks, bool]) {
62+
const val = "Hello, world"
63+
b := new(Block)
64+
payload := &tempBlockBodyHooks{X: val}
65+
extras.Block.Set(b, payload)
66+
67+
got, err := rlp.EncodeToBytes(b)
68+
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T) with %T hooks", b, extras.Block.Get(b))
69+
want, err := rlp.EncodeToBytes([]string{val})
70+
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T{%[1]v})", []string{val})
71+
72+
assert.Equalf(t, want, got, "rlp.EncodeToBytes(%T) with %T hooks", b, payload)
73+
})
74+
})
75+
t.Run("after_temp", testPrimaryExtras)
76+
}

core/vm/contracts.libevm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ func (args *evmCallArgs) env() *environment {
241241
self = args.addr
242242

243243
case DelegateCall:
244-
value = nil
244+
value = nil // inherited from `args.caller` inside [Contract.AsDelegate]
245245
fallthrough
246246
case CallCode:
247247
self = args.caller.Address()

core/vm/contracts.libevm_test.go

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ type statefulPrecompileOutput struct {
109109
ChainID *big.Int
110110
Addresses *libevm.AddressContext
111111
StateValue common.Hash
112-
ValueReceived *uint256.Int
112+
CallValue *uint256.Int
113113
ReadOnly bool
114114
BlockNumber, Difficulty *big.Int
115115
BlockTime uint64
@@ -169,7 +169,7 @@ func TestNewStatefulPrecompile(t *testing.T) {
169169
ChainID: env.ChainConfig().ChainID,
170170
Addresses: env.Addresses(),
171171
StateValue: env.ReadOnlyState().GetState(precompile, slot),
172-
ValueReceived: env.Value(),
172+
CallValue: env.Value(),
173173
ReadOnly: env.ReadOnly(),
174174
BlockNumber: env.BlockNumber(),
175175
BlockTime: env.BlockTime(),
@@ -197,12 +197,13 @@ func TestNewStatefulPrecompile(t *testing.T) {
197197
}
198198
input := rng.Bytes(8)
199199
stateValue := rng.Hash()
200-
transferValue := rng.Uint256()
200+
callCallerValue := rng.Uint256()
201+
callPrecompileValue := rng.Uint256()
201202
chainID := rng.BigUint64()
202203

203204
caller := common.HexToAddress("CA11E12") // caller of the precompile
204205
eoa := common.HexToAddress("E0A") // caller of the precompile-caller
205-
callerContract := vm.NewContract(vm.AccountRef(eoa), vm.AccountRef(caller), uint256.NewInt(0), 1e6)
206+
callerContract := vm.NewContract(vm.AccountRef(eoa), vm.AccountRef(caller), callCallerValue, 1e6)
206207

207208
state, evm := ethtest.NewZeroEVM(
208209
t,
@@ -225,10 +226,10 @@ func TestNewStatefulPrecompile(t *testing.T) {
225226
}
226227

227228
tests := []struct {
228-
name string
229-
call func() ([]byte, uint64, error)
230-
wantAddresses *libevm.AddressContext
231-
wantTransferValue *uint256.Int
229+
name string
230+
call func() ([]byte, uint64, error)
231+
wantAddresses *libevm.AddressContext
232+
wantCallValue *uint256.Int
232233
// Note that this only covers evm.readOnly being true because of the
233234
// precompile's call. See TestInheritReadOnly for alternate case.
234235
wantReadOnly bool
@@ -237,21 +238,21 @@ func TestNewStatefulPrecompile(t *testing.T) {
237238
{
238239
name: "EVM.Call()",
239240
call: func() ([]byte, uint64, error) {
240-
return evm.Call(callerContract, precompile, input, gasLimit, transferValue)
241+
return evm.Call(callerContract, precompile, input, gasLimit, callPrecompileValue)
241242
},
242243
wantAddresses: &libevm.AddressContext{
243244
Origin: eoa,
244245
EVMSemantic: rawAddresses,
245246
Raw: &rawAddresses,
246247
},
247-
wantReadOnly: false,
248-
wantTransferValue: transferValue,
249-
wantCallType: vm.Call,
248+
wantReadOnly: false,
249+
wantCallValue: callPrecompileValue,
250+
wantCallType: vm.Call,
250251
},
251252
{
252253
name: "EVM.CallCode()",
253254
call: func() ([]byte, uint64, error) {
254-
return evm.CallCode(callerContract, precompile, input, gasLimit, transferValue)
255+
return evm.CallCode(callerContract, precompile, input, gasLimit, callPrecompileValue)
255256
},
256257
wantAddresses: &libevm.AddressContext{
257258
Origin: eoa,
@@ -261,9 +262,9 @@ func TestNewStatefulPrecompile(t *testing.T) {
261262
},
262263
Raw: &rawAddresses,
263264
},
264-
wantReadOnly: false,
265-
wantTransferValue: transferValue,
266-
wantCallType: vm.CallCode,
265+
wantReadOnly: false,
266+
wantCallValue: callPrecompileValue,
267+
wantCallType: vm.CallCode,
267268
},
268269
{
269270
name: "EVM.DelegateCall()",
@@ -278,9 +279,9 @@ func TestNewStatefulPrecompile(t *testing.T) {
278279
},
279280
Raw: &rawAddresses,
280281
},
281-
wantReadOnly: false,
282-
wantTransferValue: uint256.NewInt(0),
283-
wantCallType: vm.DelegateCall,
282+
wantReadOnly: false,
283+
wantCallValue: callCallerValue, // Important difference from [vm.EVM.Call]
284+
wantCallType: vm.DelegateCall,
284285
},
285286
{
286287
name: "EVM.StaticCall()",
@@ -292,9 +293,9 @@ func TestNewStatefulPrecompile(t *testing.T) {
292293
EVMSemantic: rawAddresses,
293294
Raw: &rawAddresses,
294295
},
295-
wantReadOnly: true,
296-
wantTransferValue: uint256.NewInt(0),
297-
wantCallType: vm.StaticCall,
296+
wantReadOnly: true,
297+
wantCallValue: uint256.NewInt(0),
298+
wantCallType: vm.StaticCall,
298299
},
299300
}
300301

@@ -304,7 +305,7 @@ func TestNewStatefulPrecompile(t *testing.T) {
304305
ChainID: chainID,
305306
Addresses: tt.wantAddresses,
306307
StateValue: stateValue,
307-
ValueReceived: tt.wantTransferValue,
308+
CallValue: tt.wantCallValue,
308309
ReadOnly: tt.wantReadOnly,
309310
BlockNumber: header.Number,
310311
BlockTime: header.Time,

0 commit comments

Comments
 (0)