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/rpcThis 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:
| Flag | Shows |
|---|---|
| (none) | Basic result |
-v | Function calls |
-vv | Calls with parameters |
-vvv | Calls, parameters, and return values |
-vvvv | Full trace including internal calls |
-vvvvv | Full trace with storage changes |
$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc -vvvvAnalyzing a failed transaction
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:MyContractDebugging with quick mode
For faster debugging, use quick mode which skips some validation:
$ cast run $TX_HASH --rpc-url https://ethereum.reth.rs/rpc --quickLabeling 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:UniswapRouterDebugging in Forge tests
Debug test failures with traces:
$ forge test --match-test test_Failing -vvvvUse 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 --debugDebugger controls:
| Key | Action |
|---|---|
n | Next step |
s | Step into |
o | Step out |
g | Jump to start |
G | Jump to end |
c | Continue |
q | Quit |
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 -vvvvvStack 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.txtSimulating 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_ONEThen 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
| Practice | Description |
|---|---|
| Save failed tx hashes | Keep a log of failed transactions for analysis |
| Use labels | Label known contracts for readable traces |
| Fork at correct block | Always fork at block - 1 for accurate state |
| Check state changes | Use -vvvvv to see storage modifications |
| Compare transactions | Diff successful vs failed tx traces |
