import { ethers } from "ethers" ; import { network } from "hardhat" ; import { EthereumProvider } from "hardhat/types" ; import { StructLog , Call } from "./interface" ; export async function getCalls ( txHash , network ) { const res = await network . send ( "debug_traceTransaction" , [ txHash , { tracer : "callTracer" , reexec : 5000 } , ] ) ; if ( res . structLogs ) { return await parseGanacheStructLogs ( txHash , network , res . structLogs ) ; } else { return res ; } } async function parseGanacheStructLogs ( txHash , network , structLogs ) { const tx = await network . send ( "eth_getTransactionByHash" , [ txHash ] ) ; const r = await network . send ( "eth_getTransactionReceipt" , [ txHash ] ) ; let call = { type : "CALL" , from : r . from , to : r . to ?? ethers . constants . AddressZero , value : r . value , gas : r . gas , gasUsed : r . gasUsed , input : tx . input , output : "0x" , time : "" , calls : [ { } ] , } ; if ( call . to === ethers . constants . AddressZero && r . contractAddress ) { call . type = "CREATE" ; call . to = r . contractAddress ; } const SYSTEM_OPCODES = [ "CREATE" , "CREATE2" , "CALL" , "DELEGATECALL" , "STATICCALL" , "CALLCODE" , "RETURN" , "REVERT" , ] ; const callstack = call . calls as Call [ ] ; console . log ( "cl" , callstack . length ) ; let descended = false ; structLogs . forEach ( ( log ) => { /**
         * https://github.com/ethereum/go-ethereum/blob/053ed9cc847647a9b3ef707d0efe7104c4ab2a4c/eth/tracers/internal/tracers/call_tracer.js
         */ if ( log . error ) { console . log ( "error op" , log . op , log . error ) ; return ; } const isSysOp = SYSTEM_OPCODES . includes ( log . op ) ; if ( isSysOp ) { console . log ( "start sysop" , log . op ) ; } if ( isSysOp && ( log . op === "CREATE" || log . op === "CREATE2" ) ) { const inOff = Number ( "0x" + log . stack [ 1 ] ) ; const inEnd = inOff + Number ( "0x" + log . stack [ 2 ] ) ; const call = { type : log . op , from : log . contract , to : "" , input : log . memory . slice ( inOff , inEnd ) . join ( " " ) , gas : "0x" + log . gas . toString ( 16 ) , gasUsed : "0x" + log . gasCost . toString ( 16 ) , value : "0x" + log . stack [ 0 ] , } ; callstack . push ( call ) ; descended = true ; return ; } if ( isSysOp && log . op == "SELFDESTRUCT" ) { const left = callstack . length ; if ( callstack [ left - 1 ] . calls === undefined ) { callstack [ left - 1 ] . calls = [ ] ; } callstack [ left - 1 ] . calls ?. push ( { type : log . op , from : log . contract , to : bytes32ToBytes20 ( log . stack [ 0 ] ) , gas : "0x" + log . gas . toString ( 16 ) , gasUsed : log . gasCost . toString ( 16 ) , } ) ; return ; } if ( isSysOp && ( log . op == "CALL" || log . op == "CALLCODE" || log . op == "DELEGATECALL" || log . op == "STATICCALL" ) ) { const to = bytes32ToBytes20 ( log . stack [ 1 ] ) ; const off = log . op === "DELEGATECALL" || log . op === "STATICCALL" ? 0 : 1 ; const inOff = Number ( "0x" + log . stack [ 2 + off ] ) ; const inEnd = inOff + Number ( "0x" + log . stack [ 3 + off ] ) ; const call = { type : log . op , from : bytes32ToBytes20 ( log . stack [ 1 ] ) , to , input : log . memory . slice ( inOff , inEnd ) . join ( " " ) , _gasIn : "0x" + log . gas . toString ( 16 ) , _gasCost : log . gasCost . toString ( 16 ) , _outOff : log . stack [ 4 + off ] , _outLen : log . stack [ 5 + off ] , } ; if ( log . op !== "DELEGATECALL" && log . op !== "STATICCALL" ) { call . value = "0x" + log . stack [ 2 ] ; } callstack . push ( call ) ; console . log ( "callstack" , callstack ) ; descended = true ; return ; } if ( descended ) { if ( log . depth >= callstack . length ) { callstack [ callstack . length - 1 ] . gas = "0x" + log . gas . toString ( 16 ) ; } else { } descended = false ; } if ( isSysOp && log . op == "REVERT" ) { callstack [ callstack . length - 1 ] . error = "execution reverted" ; return ; } if ( log . depth == callstack . length - 1 ) { const call = callstack . pop ( ) ; if ( call === undefined ) { return ; } if ( call . _gasIn !== undefined && call . _gasCost !== undefined ) { if ( call . type == "CREATE" || call . type == "CREATE2" ) { call . gasUsed = "0x" + ( + call . _gasIn - + call . _gasCost - log . gas ) . toString ( 16 ) ; delete call . _gasIn ; delete call . _gasCost ; const ret = log . stack [ 0 ] ; if ( ret !== ethers . constants . HashZero ) { call . to = bytes32ToBytes20 ( ret ) ; } else if ( call . error === undefined ) { call . error = "internal failure" ; } } else { if ( call . gas !== undefined ) { call . gasUsed = "0x" + ( + call . _gasIn - + call . _gasCost + + call . gas - log . gas ) . toString ( 16 ) ; } var ret = log . stack [ 0 ] ; if ( ret !== ethers . constants . HashZero && call . _outOff !== undefined && call . _outLen !== undefined ) { call . output = log . memory . slice ( + call . _outOff , + call . _outOff + + call . _outLen ) . join ( " " ) ; } else if ( call . error === undefined ) { call . error = "internal failure" ; } delete call . _gasIn ; delete call . _gasCost ; delete call . _outOff ; delete call . _outLen ; } } var left = callstack . length ; if ( left !== 0 ) { if ( callstack [ left - 1 ] . calls === undefined ) { callstack [ left - 1 ] . calls = [ ] ; } callstack [ left - 1 ] . calls ?. push ( call ) ; } } } ) ; return call ; } function bytes32ToBytes20 ( input ) { if ( input . slice ( 0 , 2 ) !== "0x" ) { input = "0x" + input ; } return ethers . utils . hexZeroPad ( ethers . utils . hexStripZeros ( input ) , 20 ) ; }