Skip to content
Logo

Debugging Transactions

This guide covers replaying and debugging transactions using cast run, analyzing traces, and diagnosing common failure patterns.

Replaying a transaction

Use cast run to replay any transaction locally:

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc

This executes the transaction against the state at the block where it was mined, showing the full execution trace.

Trace verbosity levels

Control trace output with -v flags:

FlagShows
(none)Basic result
-vFunction calls
-vvCalls with parameters
-vvvCalls, parameters, and return values
-vvvvFull trace including internal calls
-vvvvvFull trace with storage changes
Detailed trace
$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc -vvvv

Analyzing a failed transaction

Get the transaction details

$ cast tx $TX_HASH --rpc-url https://ethereum.reth.rs/rpc

Check the receipt for failure

$ cast receipt $TX_HASH --rpc-url https://ethereum.reth.rs/rpc

Look for status: 0 which indicates failure.

Replay with traces

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc -vvvv

Decode the revert reason

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc 2>&1 | grep -A5 "revert"

Decoding revert reasons

cast run automatically decodes revert reasons when possible:

$ cast run 0x1234... --rpc-url https://ethereum.reth.rs/rpc
Revert: InsufficientBalance(1000000000000000000, 500000000000000000)

For custom errors, ensure you have the contract ABI available:

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc --label 0xContractAddress:MyContract

Debugging with quick mode

For faster debugging, use quick mode which skips some validation:

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc --quick

Labeling addresses

Make traces more readable by labeling known addresses:

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc \
    --label 0x6B175474E89094C44Da98b954EeD3CDB5BE3830:DAI \
    --label 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D:UniswapRouter

Debugging in Forge tests

Debug test failures with traces:

$ forge test --match-test test_Failing -vvvv

Use console.log for strategic debugging:

import {console} from "forge-std/console.sol";
 
function test_Debug() public {
    console.log("Balance before:", token.balanceOf(alice));
    console.log("Allowance:", token.allowance(alice, address(this)));
    
    vm.prank(alice);
    token.transfer(bob, 100);
    
    console.log("Balance after:", token.balanceOf(alice));
}

Using the debugger

Forge includes an interactive debugger:

$ forge test --match-test test_Failing --debug

Debugger controls:

KeyAction
nNext step
sStep into
oStep out
gJump to start
GJump to end
cContinue
qQuit

Common failure patterns

Out of gas

Error: OutOfGas

Diagnosis: The transaction ran out of gas before completing.

Solution: Increase gas limit or optimize the contract.

# Check actual gas used vs limit
$ cast tx $TX_HASH --rpc-url https://ethereum.reth.rs/rpc | grep -E "gas|gasLimit"

Revert with no message

Error: Revert

Diagnosis: A require() or revert() with no message, or an assertion failed.

Solution: Check the trace for the exact call that failed:

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc -vvvvv

Stack too deep

Error: Stack too deep

Diagnosis: Too many local variables in a function.

Solution: Reduce local variables or use structs. See the Stack Too Deep guide for detailed solutions.

Invalid opcode

Error: InvalidFEOpcode

Diagnosis: Hit an assert() failure or uninitialized function pointer.

Solution: Check for assertion violations in the trace.

Comparing successful vs failed transactions

Debug by comparing a working transaction with a failed one:

# Get traces for both
$ cast run $SUCCESS_TX --rpc-url $RPC -vvvv > success.txt
$ cast run $FAILED_TX --rpc-url $RPC -vvvv > failed.txt
 
# Compare
$ diff success.txt failed.txt

Simulating with modified state

Use Anvil to replay with modifications:

# Fork at the block before the transaction
$ anvil --fork-url https://ethereum.reth.rs/rpc --fork-block-number $BLOCK_MINUS_ONE

Then in a Forge test or script, modify state and replay:

function test_DebugWithModifiedState() public {
    // Give the sender more tokens
    deal(address(token), sender, 1000 ether);
    
    // Replay the transaction
    vm.prank(sender);
    target.call(originalCalldata);
}

Gas profiling a transaction

Analyze gas usage breakdown:

$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc -vvvv 2>&1 | grep -E "gas:|Gas"

Best practices

PracticeDescription
Save failed tx hashesKeep a log of failed transactions for analysis
Use labelsLabel known contracts for readable traces
Fork at correct blockAlways fork at block - 1 for accurate state
Check state changesUse -vvvvv to see storage modifications
Compare transactionsDiff successful vs failed tx traces