@@ -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+
227549func BenchmarkTracers (b * testing.B ) {
228550 files , err := dir .ReadDir (filepath .Join ("testdata" , "call_tracer" ))
229551 if err != nil {
0 commit comments