core/tracing: fix nonce revert edge case

This commit is contained in:
Sina Mahmoodi 2026-03-09 18:19:53 +01:00
parent b8a3fa7d06
commit 88d96d61a5
2 changed files with 48 additions and 4 deletions

View file

@ -155,10 +155,18 @@ func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reaso
}
func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) {
// When a contract is created, the nonce of the creator is incremented.
// This change is not reverted when the creation fails.
if reason != NonceChangeContractCreator {
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
if reason == NonceChangeContractCreator {
// When a contract is created via CREATE/CREATE2, the creator's nonce is
// incremented. The EVM does not revert this when the CREATE frame itself
// fails (the nonce change happens before the EVM snapshot). However, if
// a parent frame reverts, the nonce must be reverted along with everything
// else.
//
// To achieve this, advance the current frame's revision point past this
// entry. The CREATE frame's revert won't touch it (it's below the revision),
// but a parent frame's revert will (it's above the parent's revision).
j.revisions[len(j.revisions)-1] = len(j.entries)
}
if j.hooks.OnNonceChangeV2 != nil {
j.hooks.OnNonceChangeV2(addr, prev, new, reason)

View file

@ -219,6 +219,42 @@ func TestNonceIncOnCreate(t *testing.T) {
}
}
// TestNonceIncOnCreateParentReverts checks that the creator's nonce increment
// from CREATE survives the CREATE frame's own revert but is properly reverted
// when the parent call frame reverts.
func TestNonceIncOnCreateParentReverts(t *testing.T) {
const opCREATE = 0xf0
tr := &testTracer{t: t}
wr, err := WrapWithJournal(&Hooks{OnNonceChange: tr.OnNonceChange})
if err != nil {
t.Fatalf("failed to wrap test tracer: %v", err)
}
addr := common.HexToAddress("0x1234")
{
// Parent call frame
wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0))
{
// CREATE frame — creator nonce incremented, then CREATE reverts
wr.OnEnter(1, opCREATE, addr, addr, nil, 1000, big.NewInt(0))
wr.OnNonceChangeV2(addr, 0, 1, NonceChangeContractCreator)
wr.OnExit(1, nil, 100, errors.New("revert"), true)
}
// After CREATE reverts, nonce should still be 1
if tr.nonce != 1 {
t.Fatalf("nonce after CREATE revert: got %v, want 1", tr.nonce)
}
// Parent frame also reverts
wr.OnExit(0, nil, 150, errors.New("revert"), true)
}
// After parent reverts, nonce should be back to 0
if tr.nonce != 0 {
t.Fatalf("nonce after parent revert: got %v, want 0", tr.nonce)
}
}
func TestOnNonceChangeV2(t *testing.T) {
tr := &testTracer{t: t}
wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2})