4、Debugging Tools
Master Foundry's powerful debugging tools to troubleshoot and optimize your smart contracts.
Console Logging
Basic Logging
import {console} from "forge-std/console.sol";
function test_WithLogging() public {
console.log("Starting test");
console.log("Balance:", token.balanceOf(user));
console.log("Address:", user);
}
Available Console Functions
console.log(string memory);
console.log(uint256);
console.log(address);
console.log(bool);
console.logBytes(bytes memory);
console.logBytes32(bytes32);
Verbosity Levels
Control output detail with verbosity flags:
# Level 1: Show test results
forge test
# Level 2: Show logs for failing tests
forge test -v
# Level 3: Show logs for all tests
forge test -vv
# Level 4: Show traces for failing tests
forge test -vvv
# Level 5: Show traces for all tests
forge test -vvvv
Stack Traces
Reading Stack Traces
forge test -vvvv
# Output shows:
# [PASS] test_Transfer()
# [1234] Token::transfer(0x123..., 100)
# ├─ [5678] IERC20::balanceOf(0x123...)
# │ └─ ← 1000
# ├─ emit Transfer(from: 0x123..., to: 0x456..., value: 100)
# └─ ← true
Gas Reporting
Basic Gas Reports
forge test --gas-report
Detailed Gas Reports
# Per contract
forge test --gas-report
# Save to file
forge test --gas-report > gas-report.txt
Gas Snapshots
# Create gas snapshot
forge snapshot
# Compare with snapshot
forge snapshot --diff
# Update snapshot
forge snapshot --check
Debugging Failed Tests
Using Traces
function test_FailingTest() public {
// This will fail
token.transfer(user2, 1000000000);
}
forge test --match-test test_FailingTest -vvvv
Inspecting Reverts
# Shows why transaction reverted
forge test -vvvv
Breakpoint Debugging
Using vm.breakpoint
function test_WithBreakpoint() public {
token.mint(user1, 100);
// Inspect state here
vm.breakpoint("after_mint");
token.transfer(user2, 50);
}
State Inspection
Reading Storage
function test_InspectStorage() public {
bytes32 value = vm.load(address(token), bytes32(uint256(0)));
console.logBytes32(value);
}
Checking Balances
function test_CheckBalances() public {
console.log("ETH Balance:", address(this).balance);
console.log("Token Balance:", token.balanceOf(address(this)));
}
Debugger Mode
Interactive Debugging
# Run with debugger
forge test --debug test_Function
Debugger Commands
n- Next steps- Step intoc- Continueq- Quitp <var>- Print variable
Coverage Analysis
Generate Coverage Report
forge coverage
Detailed Coverage
# HTML report
forge coverage --report lcov
genhtml lcov.info --output-directory coverage
# Open in browser
open coverage/index.html
Fork Debugging
Debug on Fork
function test_ForkDebug() public {
uint256 fork = vm.createFork("mainnet");
vm.selectFork(fork);
// Debug against real state
console.log("Block:", block.number);
}
Replay Transactions
# Replay specific transaction
cast run <TX_HASH> --debug
Common Debugging Patterns
Debug Helper Functions
function _logState() internal view {
console.log("=== State ===");
console.log("Balance:", token.balanceOf(user));
console.log("Total Supply:", token.totalSupply());
console.log("============");
}
function test_WithHelper() public {
_logState();
token.mint(user, 100);
_logState();
}
Conditional Logging
function test_ConditionalLog() public {
uint256 balance = token.balanceOf(user);
if (balance > 100) {
console.log("High balance:", balance);
}
}
Performance Profiling
Measure Gas Usage
function test_GasProfile() public {
uint256 gasBefore = gasleft();
token.transfer(user2, 100);
uint256 gasUsed = gasBefore - gasleft();
console.log("Gas used:", gasUsed);
}
Benchmark Functions
function test_Benchmark() public {
uint256 iterations = 100;
uint256 gasBefore = gasleft();
for (uint i = 0; i < iterations; i++) {
token.transfer(user2, 1);
}
uint256 gasUsed = gasBefore - gasleft();
console.log("Avg gas per call:", gasUsed / iterations);
}
Advanced Debugging
Memory Inspection
function test_InspectMemory() public {
bytes memory data = abi.encode(123, "hello");
console.logBytes(data);
}
Event Debugging
function test_DebugEvents() public {
vm.recordLogs();
token.transfer(user2, 100);
Vm.Log[] memory logs = vm.getRecordedLogs();
console.log("Events emitted:", logs.length);
}
Troubleshooting Tools
Check Expectations
function test_ExpectationCheck() public {
vm.expectRevert();
token.transfer(user2, 999999);
console.log("Revert was expected");
}
Snapshot Debugging
function test_SnapshotDebug() public {
uint256 snap = vm.snapshot();
token.mint(user, 100);
console.log("After mint:", token.balanceOf(user));
vm.revertTo(snap);
console.log("After revert:", token.balanceOf(user));
}
Best Practices
- Use appropriate verbosity: Start with
-vv, increase as needed - Strategic logging: Add logs at key decision points
- Gas snapshots: Track gas changes over time
- Coverage checks: Aim for high test coverage
- Clean up logs: Remove debug logs before committing
Common Issues
Issue: Tests passing locally but failing in CI
# Check for randomness issues
forge test --seed 123456
# Check for gas differences
forge test --gas-report
Issue: Unclear revert reason
# Use maximum verbosity
forge test --match-test test_Name -vvvvv
Issue: Slow tests
# Profile to find bottlenecks
forge test --gas-report
# Check specific test
forge test --match-test test_Slow -vv
Next Steps
- Best Practices - Learn testing best practices
- Performance Optimization - Optimize your tests
- Troubleshooting - Common problems and solutions