5.1 KiB
5.1 KiB
Debugging Workflows
Foundry debugging tools for finding and fixing smart contract issues.
Verbosity Levels
forge test # Summary only
forge test -v # Show passing test names
forge test -vv # Print logs for all tests
forge test -vvv # Traces for failing tests
forge test -vvvv # Traces for all tests + setup
forge test -vvvvv # All traces + storage changes
Use:
-vv: Quick check of console.log output-vvv: First step when test fails-vvvv: Full debugging with all traces
Understanding Traces
Trace Format
[24661] TestContract::testFunction()
├─ [2262] Target::read()
│ └─ ← 0
├─ [20398] Target::write(42)
│ └─ ← ()
└─ ← ()
[gas]: Gas consumed by call and nested calls- Green: Successful calls
- Red: Reverting calls
- Blue: Cheatcode calls
- Cyan: Emitted logs
- Yellow: Contract deployments
Common Trace Errors
| Error | Meaning |
|---|---|
OOG |
Out of gas |
Revert |
Transaction reverted |
InvalidFEOpcode |
Unknown opcode (0xFE) |
NotActivated |
EVM feature not available |
Console Logging
Basic Usage
import {console} from "forge-std/console.sol";
function test_Debug() public {
console.log("Value:", value);
console.log("Address:", msg.sender);
console.log("Balance:", address(this).balance);
}
Format Specifiers
console.log("String: %s", "hello");
console.log("Decimal: %d", 123);
console.log("Hex: %x", 255);
// Multiple arguments (up to 4)
console.log("From %s to %s: %d", from, to, amount);
// Type-specific
console.logBytes32(hash);
console.logAddress(token);
console.logBool(success);
Debugging Pattern
function test_Transfer() public {
console.log("=== Transfer ===");
console.log("From:", from);
console.log("To:", to);
console.log("Amount:", amount);
token.transfer(to, amount);
console.log("Balance after:", token.balanceOf(to));
}
Breakpoints
Set breakpoints in code, jump to them in debugger:
function test_Complex() public {
vm.breakpoint("start");
uint256 x = calculate();
vm.breakpoint("middle");
process(x);
vm.breakpoint("end");
}
In debugger, press ' + letter to jump (e.g., 'a for first breakpoint).
Interactive Debugger
Starting
# Debug specific test
forge test --debug --match-test "test_Function"
# Debug script
forge script script/Deploy.s.sol --debug
# Debug transaction from chain
cast run --debug 0x123...
Debugger Layout
Four quadrants:
- Top-left: EVM opcodes (current instruction highlighted)
- Top-right: Stack state
- Bottom-left: Solidity source code
- Bottom-right: Memory contents
Navigation Keys
Movement:
j/k: Step forward/backwardg/G: Go to beginning/endc/C: Next/previous CALL instruction'a-z: Jump to breakpoint
Display:
t: Toggle stack labelsm: Toggle memory as UTF8h: Helpq: Quit
Debugging Workflow
# 1. Test fails
forge test --match-test "test_Deposit"
# 2. See what failed
forge test -vvv --match-test "test_Deposit"
# 3. Add console.log for quick debugging
# OR use interactive debugger
forge test --debug --match-test "test_Deposit"
# 4. Step through with j/k, watch stack with t
Gas Profiling
Gas Reports
forge test --gas-report
Output:
│ Function │ min │ avg │ median │ max │ calls │
├─────────────┼───────┼───────┼────────┼───────┼───────┤
│ transfer │ 2900 │ 5234 │ 5200 │ 8901 │ 145 │
│ balanceOf │ 596 │ 596 │ 596 │ 596 │ 234 │
Gas Snapshots
forge snapshot # Create snapshot
forge snapshot --diff # Compare to previous
forge snapshot --check # Fail if changed
Inline Measurement
function test_GasUsage() public {
uint256 gasBefore = gasleft();
contract.operation();
uint256 gasUsed = gasBefore - gasleft();
console.log("Gas used:", gasUsed);
}
Common Error Patterns
Assertion Failure
AssertionError: a == b
Expected: 1000000
Actual: 500000
Debug: Run with -vvv to trace calculation.
Revert Without Message
Error: reverted
Debug:
- Run with
-vvvvfor full trace - Find red (reverting) call in trace
- Add console.log before suspect operations
Out of Gas
Error: OutOfGas
Fix: Reduce loop iterations or split into batches.
Custom Error
vm.expectRevert(abi.encodeWithSelector(
InsufficientBalance.selector,
1000, // have
2000 // need
));
token.transfer(recipient, 2000);
Best Practices
- Start simple: Use console.log before --debug
- Isolate tests: Test one thing per test function
- Use descriptive logs: Log state at each step
- Check assumptions: Verify preconditions
- Save traces:
forge test -vvvv > trace.txt