Skip to content

Commit 9e01d17

Browse files
lupin012claudeAskAlexSharov
authored
rpc: add position field to callTracer withLog tests (erigontech#21175)
Add the missing "position" field (always "0x0") to five call_tracer_withLog fixture files, and add three new tests covering position edge cases: log after reverted subcall, mixed success+revert subcalls, and log inside a CREATE frame. closes erigontech#9903 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Alex Sharov <AskAlexSharov@gmail.com>
1 parent 14d5e71 commit 9e01d17

6 files changed

Lines changed: 447 additions & 66 deletions

File tree

execution/tracing/tracers/internal/tracetest/calltrace_test.go

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,328 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
224224
}
225225
}
226226

227+
// evmLog0 is a 5-byte EVM snippet that emits a zero-topic, zero-data LOG0.
228+
var evmLog0 = []byte{byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.LOG0)}
229+
230+
// evmRevert is a 5-byte EVM snippet that REVERTs with no return data.
231+
var evmRevert = []byte{byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.REVERT)}
232+
233+
// evmCallTo returns a 34-byte EVM snippet that CALLs a 20-byte address whose last byte is addr.
234+
// The return value is discarded (POP).
235+
func evmCallTo(addr byte) []byte {
236+
return []byte{
237+
byte(vm.PUSH1), 0x00, // retSize
238+
byte(vm.PUSH1), 0x00, // retOffset
239+
byte(vm.PUSH1), 0x00, // argsSize
240+
byte(vm.PUSH1), 0x00, // argsOffset
241+
byte(vm.PUSH1), 0x00, // value
242+
byte(vm.PUSH20),
243+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
244+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, addr,
245+
byte(vm.GAS), byte(vm.CALL),
246+
byte(vm.POP),
247+
}
248+
}
249+
250+
// TestCallTracerWithLogPositionAfterRevert verifies that the position field in callTracer logs
251+
// correctly counts a reverted subcall:
252+
// - LOG_A emitted before the subcall → position 0x0
253+
// - CALL to addrB which REVERTs (recorded in calls[] even on revert)
254+
// - LOG_B emitted after the reverted subcall → position 0x1
255+
func TestCallTracerWithLogPositionAfterRevert(t *testing.T) {
256+
var (
257+
addrA = common.HexToAddress("0x00000000000000000000000000000000000000aa")
258+
addrB = common.HexToAddress("0x00000000000000000000000000000000000000bb")
259+
)
260+
261+
codeB := evmRevert
262+
263+
codeA := evmLog0
264+
codeA = append(codeA, evmCallTo(0xbb)...)
265+
codeA = append(codeA, evmLog0...)
266+
codeA = append(codeA, byte(vm.STOP))
267+
268+
privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
269+
if err != nil {
270+
t.Fatalf("err %v", err)
271+
}
272+
signer := types.LatestSigner(chainspec.Mainnet.Config)
273+
tx, err := types.SignNewTx(privkey, *signer, &types.LegacyTx{
274+
GasPrice: *uint256.NewInt(0),
275+
CommonTx: types.CommonTx{
276+
GasLimit: 100000,
277+
To: &addrA,
278+
},
279+
})
280+
if err != nil {
281+
t.Fatalf("err %v", err)
282+
}
283+
origin, _ := signer.Sender(tx)
284+
txContext := evmtypes.TxContext{
285+
Origin: origin,
286+
GasPrice: *uint256.NewInt(1),
287+
}
288+
context := evmtypes.BlockContext{
289+
CanTransfer: protocol.CanTransfer,
290+
Transfer: misc.Transfer,
291+
Coinbase: accounts.ZeroAddress,
292+
BlockNumber: 8000000,
293+
Time: 5,
294+
Difficulty: *uint256.NewInt(0x30000),
295+
GasLimit: uint64(6000000),
296+
}
297+
alloc := types.GenesisAlloc{
298+
addrA: types.GenesisAccount{Nonce: 1, Code: codeA},
299+
addrB: types.GenesisAccount{Nonce: 1, Code: codeB},
300+
origin.Value(): types.GenesisAccount{
301+
Nonce: 0,
302+
Balance: big.NewInt(500000000000000),
303+
},
304+
}
305+
rules := context.Rules(chainspec.Mainnet.Config)
306+
m := execmoduletester.New(t)
307+
dbTx, err := m.DB.BeginTemporalRw(m.Ctx)
308+
require.NoError(t, err)
309+
defer dbTx.Rollback()
310+
statedb, _ := testutil.MakePreState(rules, dbTx, alloc, context.BlockNumber)
311+
312+
tracer, err := tracers.New("callTracer", nil, json.RawMessage(`{"withLog":true}`))
313+
if err != nil {
314+
t.Fatalf("failed to create call tracer: %v", err)
315+
}
316+
statedb.SetHooks(tracer.Hooks)
317+
evm := vm.NewEVM(context, txContext, statedb, chainspec.Mainnet.Config, vm.Config{Tracer: tracer.Hooks})
318+
msg, err := tx.AsMessage(*signer, nil, rules)
319+
if err != nil {
320+
t.Fatalf("failed to prepare transaction for tracing: %v", err)
321+
}
322+
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From())
323+
st := protocol.NewTxnExecutor(evm, msg, new(protocol.GasPool).AddGas(tx.GetGasLimit()).AddBlobGas(tx.GetBlobGas()))
324+
vmRet, err := st.Execute(true /* refunds */, false /* gasBailout */)
325+
if err != nil {
326+
t.Fatalf("failed to execute transaction: %v", err)
327+
}
328+
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.ReceiptGasUsed}, err)
329+
330+
res, err := tracer.GetResult()
331+
if err != nil {
332+
t.Fatalf("failed to retrieve trace result: %v", err)
333+
}
334+
var result callTrace
335+
if err := json.Unmarshal(res, &result); err != nil {
336+
t.Fatalf("failed to unmarshal trace: %v", err)
337+
}
338+
339+
require.Len(t, result.Logs, 2, "expected 2 logs in top frame")
340+
require.Equal(t, hexutil.Uint(0), result.Logs[0].Position, "LOG_A: emitted before subcall, position should be 0x0")
341+
require.Equal(t, hexutil.Uint(1), result.Logs[1].Position, "LOG_B: emitted after reverted subcall, position should be 0x1")
342+
require.Len(t, result.Calls, 1, "expected 1 subcall")
343+
require.Equal(t, "execution reverted", result.Calls[0].Error, "subcall should be reverted")
344+
}
345+
346+
// TestCallTracerWithLogPositionMixedSubcalls verifies that position counts both successful
347+
// and reverted subcalls:
348+
// - LOG_A before any subcall → position 0x0
349+
// - CALL addrB (succeeds) → calls[] len becomes 1
350+
// - CALL addrC (reverts) → calls[] len becomes 2
351+
// - LOG_B after both → position 0x2
352+
func TestCallTracerWithLogPositionMixedSubcalls(t *testing.T) {
353+
var (
354+
addrA = common.HexToAddress("0x00000000000000000000000000000000000000aa")
355+
addrB = common.HexToAddress("0x00000000000000000000000000000000000000bb")
356+
addrC = common.HexToAddress("0x00000000000000000000000000000000000000cc")
357+
)
358+
359+
codeB := []byte{byte(vm.STOP)} // succeeds
360+
codeC := evmRevert
361+
362+
codeA := evmLog0
363+
codeA = append(codeA, evmCallTo(0xbb)...)
364+
codeA = append(codeA, evmCallTo(0xcc)...)
365+
codeA = append(codeA, evmLog0...)
366+
codeA = append(codeA, byte(vm.STOP))
367+
368+
privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
369+
if err != nil {
370+
t.Fatalf("err %v", err)
371+
}
372+
signer := types.LatestSigner(chainspec.Mainnet.Config)
373+
tx, err := types.SignNewTx(privkey, *signer, &types.LegacyTx{
374+
GasPrice: *uint256.NewInt(0),
375+
CommonTx: types.CommonTx{GasLimit: 100000, To: &addrA},
376+
})
377+
if err != nil {
378+
t.Fatalf("err %v", err)
379+
}
380+
origin, _ := signer.Sender(tx)
381+
context := evmtypes.BlockContext{
382+
CanTransfer: protocol.CanTransfer,
383+
Transfer: misc.Transfer,
384+
Coinbase: accounts.ZeroAddress,
385+
BlockNumber: 8000000,
386+
Time: 5,
387+
Difficulty: *uint256.NewInt(0x30000),
388+
GasLimit: uint64(6000000),
389+
}
390+
alloc := types.GenesisAlloc{
391+
addrA: types.GenesisAccount{Nonce: 1, Code: codeA},
392+
addrB: types.GenesisAccount{Nonce: 1, Code: codeB},
393+
addrC: types.GenesisAccount{Nonce: 1, Code: codeC},
394+
origin.Value(): types.GenesisAccount{
395+
Nonce: 0,
396+
Balance: big.NewInt(500000000000000),
397+
},
398+
}
399+
rules := context.Rules(chainspec.Mainnet.Config)
400+
m := execmoduletester.New(t)
401+
dbTx, err := m.DB.BeginTemporalRw(m.Ctx)
402+
require.NoError(t, err)
403+
defer dbTx.Rollback()
404+
statedb, _ := testutil.MakePreState(rules, dbTx, alloc, context.BlockNumber)
405+
406+
tracer, err := tracers.New("callTracer", nil, json.RawMessage(`{"withLog":true}`))
407+
if err != nil {
408+
t.Fatalf("failed to create call tracer: %v", err)
409+
}
410+
statedb.SetHooks(tracer.Hooks)
411+
evm := vm.NewEVM(context, evmtypes.TxContext{Origin: origin, GasPrice: *uint256.NewInt(1)}, statedb, chainspec.Mainnet.Config, vm.Config{Tracer: tracer.Hooks})
412+
msg, err := tx.AsMessage(*signer, nil, rules)
413+
if err != nil {
414+
t.Fatalf("failed to prepare transaction for tracing: %v", err)
415+
}
416+
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From())
417+
st := protocol.NewTxnExecutor(evm, msg, new(protocol.GasPool).AddGas(tx.GetGasLimit()).AddBlobGas(tx.GetBlobGas()))
418+
vmRet, err := st.Execute(true, false)
419+
if err != nil {
420+
t.Fatalf("failed to execute transaction: %v", err)
421+
}
422+
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.ReceiptGasUsed}, err)
423+
424+
res, err := tracer.GetResult()
425+
if err != nil {
426+
t.Fatalf("failed to retrieve trace result: %v", err)
427+
}
428+
var result callTrace
429+
if err := json.Unmarshal(res, &result); err != nil {
430+
t.Fatalf("failed to unmarshal trace: %v", err)
431+
}
432+
433+
require.Len(t, result.Logs, 2, "expected 2 logs in top frame")
434+
require.Equal(t, hexutil.Uint(0), result.Logs[0].Position, "LOG_A: before any subcall, position should be 0x0")
435+
require.Equal(t, hexutil.Uint(2), result.Logs[1].Position, "LOG_B: after success+revert subcalls, position should be 0x2")
436+
require.Len(t, result.Calls, 2, "expected 2 subcalls")
437+
require.Empty(t, result.Calls[0].Error, "first subcall (B) should succeed")
438+
require.Equal(t, "execution reverted", result.Calls[1].Error, "second subcall (C) should revert")
439+
}
440+
441+
// TestCallTracerWithLogPositionInCreate verifies that position is tracked correctly inside
442+
// a CREATE frame:
443+
// - Factory (addrA) runs CREATE with init code that:
444+
// - emits LOG_A → position 0x0 (no subcalls yet)
445+
// - CALLs addrC (succeeds)
446+
// - emits LOG_B → position 0x1
447+
func TestCallTracerWithLogPositionInCreate(t *testing.T) {
448+
var (
449+
addrA = common.HexToAddress("0x00000000000000000000000000000000000000aa")
450+
addrC = common.HexToAddress("0x00000000000000000000000000000000000000cc")
451+
)
452+
453+
// Init code: LOG_A, CALL addrC (succeeds), LOG_B, RETURN empty runtime
454+
initCode := evmLog0
455+
initCode = append(initCode, evmCallTo(0xcc)...)
456+
initCode = append(initCode, evmLog0...)
457+
initCode = append(initCode, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.RETURN))
458+
initLen := byte(len(initCode))
459+
460+
// Factory code (15 bytes): CODECOPY init code into memory, then CREATE
461+
// CODECOPY(destOffset=0, codeOffset=factoryLen, size=initLen)
462+
// CREATE(value=0, offset=0, size=initLen)
463+
factoryLen := byte(15) // length of the factory code itself
464+
codeA := []byte{
465+
byte(vm.PUSH1), initLen, // size
466+
byte(vm.PUSH1), factoryLen, // offset of init code in codeA
467+
byte(vm.PUSH1), 0x00, // memory dest
468+
byte(vm.CODECOPY),
469+
byte(vm.PUSH1), initLen, // size
470+
byte(vm.PUSH1), 0x00, // memory offset
471+
byte(vm.PUSH1), 0x00, // value
472+
byte(vm.CREATE),
473+
byte(vm.STOP),
474+
}
475+
codeA = append(codeA, initCode...)
476+
477+
privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
478+
if err != nil {
479+
t.Fatalf("err %v", err)
480+
}
481+
signer := types.LatestSigner(chainspec.Mainnet.Config)
482+
tx, err := types.SignNewTx(privkey, *signer, &types.LegacyTx{
483+
GasPrice: *uint256.NewInt(0),
484+
CommonTx: types.CommonTx{GasLimit: 200000, To: &addrA},
485+
})
486+
if err != nil {
487+
t.Fatalf("err %v", err)
488+
}
489+
origin, _ := signer.Sender(tx)
490+
context := evmtypes.BlockContext{
491+
CanTransfer: protocol.CanTransfer,
492+
Transfer: misc.Transfer,
493+
Coinbase: accounts.ZeroAddress,
494+
BlockNumber: 8000000,
495+
Time: 5,
496+
Difficulty: *uint256.NewInt(0x30000),
497+
GasLimit: uint64(6000000),
498+
}
499+
alloc := types.GenesisAlloc{
500+
addrA: types.GenesisAccount{Nonce: 1, Code: codeA},
501+
addrC: types.GenesisAccount{Nonce: 1, Code: []byte{byte(vm.STOP)}},
502+
origin.Value(): types.GenesisAccount{
503+
Nonce: 0,
504+
Balance: big.NewInt(500000000000000),
505+
},
506+
}
507+
rules := context.Rules(chainspec.Mainnet.Config)
508+
m := execmoduletester.New(t)
509+
dbTx, err := m.DB.BeginTemporalRw(m.Ctx)
510+
require.NoError(t, err)
511+
defer dbTx.Rollback()
512+
statedb, _ := testutil.MakePreState(rules, dbTx, alloc, context.BlockNumber)
513+
514+
tracer, err := tracers.New("callTracer", nil, json.RawMessage(`{"withLog":true}`))
515+
if err != nil {
516+
t.Fatalf("failed to create call tracer: %v", err)
517+
}
518+
statedb.SetHooks(tracer.Hooks)
519+
evm := vm.NewEVM(context, evmtypes.TxContext{Origin: origin, GasPrice: *uint256.NewInt(1)}, statedb, chainspec.Mainnet.Config, vm.Config{Tracer: tracer.Hooks})
520+
msg, err := tx.AsMessage(*signer, nil, rules)
521+
if err != nil {
522+
t.Fatalf("failed to prepare transaction for tracing: %v", err)
523+
}
524+
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From())
525+
st := protocol.NewTxnExecutor(evm, msg, new(protocol.GasPool).AddGas(tx.GetGasLimit()).AddBlobGas(tx.GetBlobGas()))
526+
vmRet, err := st.Execute(true, false)
527+
if err != nil {
528+
t.Fatalf("failed to execute transaction: %v", err)
529+
}
530+
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.ReceiptGasUsed}, err)
531+
532+
res, err := tracer.GetResult()
533+
if err != nil {
534+
t.Fatalf("failed to retrieve trace result: %v", err)
535+
}
536+
var result callTrace
537+
if err := json.Unmarshal(res, &result); err != nil {
538+
t.Fatalf("failed to unmarshal trace: %v", err)
539+
}
540+
541+
require.Len(t, result.Calls, 1, "expected CREATE subcall from factory")
542+
createFrame := result.Calls[0]
543+
require.Equal(t, "CREATE", createFrame.Type, "subcall type should be CREATE")
544+
require.Len(t, createFrame.Logs, 2, "expected 2 logs inside CREATE frame")
545+
require.Equal(t, hexutil.Uint(0), createFrame.Logs[0].Position, "LOG_A in CREATE: before subcall, position should be 0x0")
546+
require.Equal(t, hexutil.Uint(1), createFrame.Logs[1].Position, "LOG_B in CREATE: after subcall, position should be 0x1")
547+
}
548+
227549
func BenchmarkTracers(b *testing.B) {
228550
files, err := dir.ReadDir(filepath.Join("testdata", "call_tracer"))
229551
if err != nil {

execution/tracing/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,17 @@
9494
"topics": [
9595
"0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda"
9696
],
97-
"data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598"
97+
"data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598",
98+
"position": "0x0"
9899
},
99100
{
100101
"index": "0x1",
101102
"address": "0x200edd17f30485a8735878661960cd7a9a95733f",
102103
"topics": [
103104
"0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da"
104105
],
105-
"data": "0x0000000000000000000000000000000000000000000000000000000000000000"
106+
"data": "0x0000000000000000000000000000000000000000000000000000000000000000",
107+
"position": "0x0"
106108
}
107109
],
108110
"value": "0x8ac7230489e80000",

0 commit comments

Comments
 (0)