UNPKG

@tevm/actions

Version:

A typesafe library for writing forge scripts in typescript

174 lines (164 loc) 6.53 kB
import { createAddress } from '@tevm/address' import { createImpersonatedTx } from '@tevm/tx' import { hexToBigInt, hexToBytes, hexToNumber } from '@tevm/utils' import { forkAndCacheBlock } from '../internal/forkAndCacheBlock.js' import { serializeTraceResult } from '../internal/serializeTraceResult.js' import { requestProcedure } from '../requestProcedure.js' import { traceCallHandler } from './traceCallHandler.js' /** * Creates a JSON-RPC procedure handler for the `debug_traceTransaction` method * * This handler traces the execution of a historical transaction using the EVM's step-by-step * execution tracing capabilities. It reconstructs the state at the point of the transaction * by replaying all previous transactions in the block and then provides a detailed trace. * * @param {import('@tevm/node').TevmNode} client - The TEVM node instance * @returns {import('./DebugProcedure.js').DebugTraceTransactionProcedure} A handler function for debug_traceTransaction requests * @throws {Error} If the transaction cannot be found * @throws {Error} If the parent block's state root is not available and cannot be forked * * @example * ```javascript * import { createTevmNode } from '@tevm/node' * import { createAddress } from '@tevm/address' * import { debugTraceTransactionJsonRpcProcedure } from '@tevm/actions' * import { SimpleContract } from '@tevm/contract' * * // Create a node and deploy a contract * const node = createTevmNode({ miningConfig: { type: 'auto' } }) * const contract = SimpleContract.withAddress(createAddress('0x1234').toString()) * * // Deploy contract * const deployResult = await node.tevmDeploy(contract.deploy(1n)) * * // Call a contract method that will create a transaction * const callResult = await node.tevmCall({ * createTransaction: true, * ...contract.write.set(42n) * }) * * // Get the transaction hash from the call result * const txHash = callResult.txHash * * // Create the debug procedure handler * const debugProcedure = debugTraceTransactionJsonRpcProcedure(node) * * // Trace the transaction * const trace = await debugProcedure({ * jsonrpc: '2.0', * method: 'debug_traceTransaction', * params: [{ * transactionHash: txHash, * tracer: 'callTracer' // Or other tracer options * }], * id: 1 * }) * * console.log('Transaction trace:', trace.result) * ``` */ export const debugTraceTransactionJsonRpcProcedure = (client) => { /** * @template {'callTracer' | 'prestateTracer'} TTracer * @template {boolean} TDiffMode * @param {import('./DebugJsonRpcRequest.js').DebugTraceTransactionJsonRpcRequest<TTracer, TDiffMode>} request * @returns {Promise<import('./DebugJsonRpcResponse.js').DebugTraceTransactionJsonRpcResponse<TTracer, TDiffMode>>} */ return async (request) => { const { tracer, timeout, tracerConfig, transactionHash } = request.params[0] if (timeout !== undefined) { client.logger.warn('Warning: timeout is currently respected param of debug_traceTransaction') } client.logger.debug({ transactionHash, tracer, tracerConfig }, 'debug_traceTransaction: executing with params') const transactionByHashResponse = await requestProcedure(client)({ method: 'eth_getTransactionByHash', params: [transactionHash], jsonrpc: '2.0', id: 1, }) if (transactionByHashResponse.error) { return { error: { code: transactionByHashResponse.error.code.toString(), message: transactionByHashResponse.error.message, }, ...(request.id !== undefined ? { id: request.id } : {}), jsonrpc: '2.0', method: request.method, } } const vm = await client.getVm() const block = await vm.blockchain.getBlock(hexToBytes(transactionByHashResponse.result.blockHash)) const parentBlock = await vm.blockchain.getBlock(block.header.parentHash) const previousTx = block.transactions.filter( (_, i) => i < hexToNumber(transactionByHashResponse.result.transactionIndex), ) // handle the case where the state root is from a preforked block const hasStateRoot = await vm.stateManager.hasStateRoot(parentBlock.header.stateRoot) if (!hasStateRoot && client.forkTransport) { await forkAndCacheBlock(client, parentBlock) } else if (!hasStateRoot) { return { jsonrpc: '2.0', method: request.method, ...(request.id !== undefined ? { id: request.id } : {}), error: { code: '-32602', message: 'State root not available for parent block', }, } } // clone the VM and set initial state const vmClone = await vm.deepCopy() await vmClone.stateManager.setStateRoot(parentBlock.header.stateRoot) // execute all transactions before the current one committing to the state for (const tx of previousTx) { await vmClone.runTx({ block, skipNonce: true, skipBalance: true, skipHardForkValidation: true, skipBlockGasLimitValidation: true, tx: createImpersonatedTx( { ...tx, gasPrice: null, impersonatedAddress: createAddress(tx.getSenderAddress()), }, { freeze: false, common: vmClone.common.ethjsCommon, allowUnlimitedInitCodeSize: true, }, ), }) } // now execute a debug_traceCall const traceResult = await traceCallHandler({ ...client, getVm: () => Promise.resolve(vmClone) })({ tracer, ...(transactionByHashResponse.result.to !== undefined ? { to: transactionByHashResponse.result.to } : {}), ...(transactionByHashResponse.result.from !== undefined ? { from: transactionByHashResponse.result.from } : {}), ...(transactionByHashResponse.result.gas !== undefined ? { gas: hexToBigInt(transactionByHashResponse.result.gas) } : {}), ...(transactionByHashResponse.result.gasPrice !== undefined ? { gasPrice: hexToBigInt(transactionByHashResponse.result.gasPrice) } : {}), ...(transactionByHashResponse.result.value !== undefined ? { value: hexToBigInt(transactionByHashResponse.result.value) } : {}), ...(transactionByHashResponse.result.input !== undefined ? { data: transactionByHashResponse.result.input } : {}), ...(transactionByHashResponse.result.blockHash !== undefined ? { blockTag: transactionByHashResponse.result.blockHash } : {}), ...(timeout !== undefined ? { timeout } : {}), .../** @type {any} */ (tracerConfig !== undefined ? { tracerConfig } : {}), }) return { method: request.method, result: /** @type {any} */ (serializeTraceResult(traceResult)), jsonrpc: '2.0', ...(request.id ? { id: request.id } : {}), } } }