Skip to content

Commit 754b0d1

Browse files
authored
Merge branch 'main' into ceyonur/check-underlying-basic-type
2 parents 2eea63d + 910e897 commit 754b0d1

File tree

15 files changed

+676
-34
lines changed

15 files changed

+676
-34
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ type PrecompileEnvironment interface {
220220
// Call is equivalent to [EVM.Call] except that the `caller` argument is
221221
// removed and automatically determined according to the type of call that
222222
// invoked the precompile.
223+
//
224+
// WARNING: using this method makes the precompile susceptible to reentrancy
225+
// attacks as with a regular contract. The Checks-Effects-Interactions
226+
// pattern, libevm's `reentrancy` package, or some other protection MUST be
227+
// used in conjunction with `Call()`.
223228
Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, _ ...CallOption) (ret []byte, _ error)
224229
}
225230

core/vm/evm.libevm_test.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,23 @@ func TestOverrideNewEVMArgs(t *testing.T) {
6363
hooks := evmArgOverrider{newEVMchainID: chainID}
6464
hooks.register(t)
6565

66-
evm := NewEVM(BlockContext{}, TxContext{}, nil, nil, Config{})
67-
got := evm.ChainConfig().ChainID
68-
require.Equalf(t, big.NewInt(chainID), got, "%T.ChainConfig().ChainID set by NewEVM() hook", evm)
66+
assertChainID := func(t *testing.T, want int64) {
67+
t.Helper()
68+
evm := NewEVM(BlockContext{}, TxContext{}, nil, nil, Config{})
69+
got := evm.ChainConfig().ChainID
70+
require.Equalf(t, big.NewInt(want), got, "%T.ChainConfig().ChainID set by NewEVM() hook", evm)
71+
}
72+
assertChainID(t, chainID)
73+
74+
t.Run("WithTempRegisteredHooks", func(t *testing.T) {
75+
override := evmArgOverrider{newEVMchainID: 24680}
76+
WithTempRegisteredHooks(&override, func() {
77+
assertChainID(t, override.newEVMchainID)
78+
})
79+
t.Run("after", func(t *testing.T) {
80+
assertChainID(t, chainID)
81+
})
82+
})
6983
}
7084

7185
func TestOverrideEVMResetArgs(t *testing.T) {

core/vm/hooks.libevm.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ func RegisterHooks(h Hooks) {
2727
libevmHooks.MustRegister(h)
2828
}
2929

30+
// WithTempRegisteredHooks temporarily registers `h` as if calling
31+
// [RegisterHooks] the same type parameter. After `fn` returns, the registration
32+
// is returned to its former state, be that none or the types originally passed
33+
// to [RegisterHooks].
34+
//
35+
// This MUST NOT be used on a live chain. It is solely intended for off-chain
36+
// consumers that require access to extras. Said consumers SHOULD NOT, however
37+
// call this function directly. Use the libevm/temporary.WithRegisteredExtras()
38+
// function instead as it atomically overrides all possible packages.
39+
func WithTempRegisteredHooks(h Hooks, fn func()) {
40+
libevmHooks.TempOverride(h, fn)
41+
}
42+
3043
// TestOnlyClearRegisteredHooks clears the [Hooks] previously passed to
3144
// [RegisterHooks]. It panics if called from a non-testing call stack.
3245
func TestOnlyClearRegisteredHooks() {

libevm/reentrancy/guard.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 reentrancy provides a reentrancy guard for stateful precompiles that
18+
// make outgoing calls to other contracts.
19+
//
20+
// Reentrancy occurs when the contract (C) called by a precompile (P) makes a
21+
// further call back into P, which may result in theft of funds (see DAO hack).
22+
// A reentrancy guard detects these recursive calls and reverts.
23+
package reentrancy
24+
25+
import (
26+
"github.com/ava-labs/libevm/common"
27+
"github.com/ava-labs/libevm/core/vm"
28+
"github.com/ava-labs/libevm/crypto"
29+
"github.com/ava-labs/libevm/libevm"
30+
)
31+
32+
var slotPreimagePrefix = []byte("libevm-reentrancy-guard-")
33+
34+
// Guard returns [vm.ErrExecutionReverted] i.f.f. it has already been called
35+
// with the same `key`, by the same contract, in the same transaction. It
36+
// otherwise returns nil. The `key` MAY be nil.
37+
//
38+
// Contract equality is defined as the [libevm.AddressContext] "self" address
39+
// being the same under EVM semantics.
40+
func Guard(env vm.PrecompileEnvironment, key []byte) error {
41+
self := env.Addresses().EVMSemantic.Self
42+
slot := crypto.Keccak256Hash(slotPreimagePrefix, key)
43+
44+
sdb := env.StateDB()
45+
if sdb.GetTransientState(self, slot) != (common.Hash{}) {
46+
return vm.ErrExecutionReverted
47+
}
48+
sdb.SetTransientState(self, slot, common.Hash{1})
49+
return nil
50+
}
51+
52+
// Keep the `libevm` import to allow the linked comment on [Guard]. The package
53+
// is imported by `vm` anyway so this is a noop but it improves developer
54+
// experience.
55+
var _ = (*libevm.AddressContext)(nil)

0 commit comments

Comments
 (0)