The Reality of Smart Contract Debugging

Debugging smart contracts is like debugging any other software, except when it breaks in production, you can't just restart the process. That failed transaction is stuck on the blockchain forever, mocking your "thoroughly tested" code. Foundry actually gives you tools to figure out what went wrong, but you need to know which buttons to push.

Common Production Failures

Gas Estimation Failures: Your deploy script worked fine on your local Anvil node but fails when you try to deploy to mainnet. This usually means your contract hit a gas limit that doesn't exist locally, or you're trying to deploy during a gas spike and your estimate is way off.

Revert Without Reason: The transaction reverted but there's no error message. This happens when you use require() without a message, or when the revert happens in assembly code. Foundry's traces can show you exactly where it died.

Constructor Arguments Mismatch: You copied the constructor arguments from your test but forgot that production uses different addresses, or you encoded the arguments wrong and your contract deployed with garbage data.

The Foundry Debugging Arsenal

Forge Test with Verbosity: When a test fails, run it with -vvvv to get full traces. This shows every internal call and state change, which is usually enough to spot the problem.

forge test --match-test testFailingFunction -vvvv

Call Traces: These show the exact execution path of a failed transaction. You can see which function called which, where it reverted, and what the state looked like at each step. Way more useful than Etherscan's basic transaction info.

Interactive Debugger: For complex failures, you can step through the transaction line by line. It's slow but thorough - think gdb for smart contracts.

forge test --match-test testComplexFailure --debug

Real Debugging Workflow

Foundry Debugging Process

When something breaks, here's the process that actually works:

  1. Reproduce Locally: Fork the production network state and run your failing transaction locally. This gives you full debugging access to a production-identical environment.
## Fork mainnet and replay the exact failure
forge test --fork-url $MAINNET_RPC --match-test testFailingFunction -vvvv
  1. Add Traces: Run the failing test with maximum verbosity to see the full call stack. Look for the deepest function call that reverted.

  2. Check State: Use Cast to query the current state of contracts involved in the failure. Often the issue is that some prerequisite wasn't met.

## Check contract state at the time of failure
cast storage $CONTRACT_ADDRESS $SLOT_NUMBER --rpc-url $MAINNET_RPC --block $FAILURE_BLOCK
  1. Isolate the Problem: Write a minimal test that reproduces just the failing part. Strip away everything else until you have the simplest possible failure case.

The key insight is that most "mysterious" failures have simple explanations - wrong addresses, insufficient approvals, timing assumptions that don't hold on mainnet, or edge cases your tests didn't cover.

Why This Matters More Than You Think

Production debugging skills separate developers who ship reliably from those who ship hope. When your deployment fails at 2am and the team is waiting for a fix, knowing how to quickly isolate and debug the problem is worth more than any framework knowledge.

Foundry's debugging tools are powerful, but they're not intuitive if you're coming from JavaScript debugging. The investment in learning them pays off every time you avoid a multi-hour debugging session or catch a bug before it hits mainnet.

Common Foundry Debug Scenarios

Q

Why does my test pass locally but deployment fails with "execution reverted"?

A

Your local Anvil node doesn't have the same state as production. Fork the actual network and run your deploy script locally:

forge script DeployScript --fork-url $MAINNET_RPC --broadcast --verify

Most likely your contract is calling something that doesn't exist on mainnet, or trying to use an address that has different permissions than your test setup.

Q

How do I debug a transaction that reverted without an error message?

A

Run the transaction with traces enabled. If it's a test, use -vvvv. If it's a real transaction, you can replay it locally:

cast run 0x[transaction-hash] --debug

The trace will show exactly where the revert happened, even if there's no error message. Look for the deepest function call in the stack - that's usually where the actual problem is.

Q

What does "EvmError: OutOfGas" mean and how do I fix it?

A

Your transaction ran out of gas, but this can happen for different reasons:

  1. Infinite Loop: Check for loops that don't have proper exit conditions
  2. Gas Limit Too Low: Increase the gas limit in your script or test
  3. Complex Computation: Your function is doing too much work in one call

Use gas profiling to see where the gas is going:

forge test --gas-report --match-test yourFailingTest
Q

Why do my invariant tests randomly fail?

A

Invariant tests fail when the fuzzer finds an input sequence that breaks your assumptions. The failure is usually showing you a real edge case:

  1. Check the failing sequence: The test output shows exactly which function calls broke your invariant
  2. Reproduce manually: Write a normal test that calls the same sequence to understand what happened
  3. Fix the invariant or the code: Either your invariant is wrong or your code has a bug

Don't just increase the number of runs hoping it goes away - the fuzzer found something real.

Q

How do I debug constructor failures during deployment?

A

Constructor failures are usually about wrong arguments or insufficient permissions. Check:

  1. Argument encoding: Use cast abi-encode to verify your constructor arguments are encoded correctly
  2. Dependency addresses: Make sure all contract addresses in your constructor exist on the target network
  3. Permissions: Check if your deployer address has the required permissions (token approvals, owner roles, etc.)
Q

What's the fastest way to find where a complex transaction failed?

A

Start with the call trace, but focus on the return values:

forge test --match-test complexTest -vvvv | grep "Return"

Failed calls return empty data or revert reasons. Work backwards from the failure point to understand what state caused the problem.

Q

Why does my forge script work in simulation but fail when broadcasting?

A

Simulation runs with unlimited gas and perfect conditions. Broadcasting hits real network constraints:

  1. Gas price fluctuations: Your transaction got stuck in the mempool because gas prices spiked
  2. Nonce conflicts: You have pending transactions that need to clear first
  3. State changes: Someone else's transaction changed the state between your simulation and broadcast

Always simulate with realistic gas limits: --gas-estimate-multiplier 130

Q

How do I debug failed calls to external contracts?

A

External calls fail for reasons outside your control. Check:

  1. Contract exists: cast code $CONTRACT_ADDRESS returns non-empty
  2. Function exists: The contract might be a proxy or have been upgraded
  3. Permissions: Your contract might not have the required role or token approval
  4. Network state: The external contract might be paused or in emergency mode

Use forking to test against the exact network state when the failure occurred.

Advanced Debugging Techniques

The basic traces and debugger handle most problems, but some issues need more specialized approaches. These are the techniques that separate developers who can ship under pressure from those who panic when weird stuff happens.

Forking for Perfect Reproduction

The most powerful debugging technique is forking the exact network state where your problem occurred. This gives you a local environment that matches production perfectly, including all the external contract states and permissions.

## Fork at specific block to reproduce timing-sensitive bugs
forge test --fork-url $MAINNET_RPC --fork-block-number 18500000

This is critical for debugging issues that involve external contracts, oracles, or time-sensitive operations. Your local tests might pass because they use mock contracts, but the real contracts have different behavior.

State Debugging: Once you're forked, you can inspect any contract state on the network. Use Cast to read storage slots, check balances, and verify that all preconditions are actually met.

Fuzzing Failures and Edge Cases

Smart Contract Testing Flow

When your fuzz tests find a failure, the error message shows the exact input that broke your code. This input isn't random - it's designed to find edge cases you missed.

Reproduce the Fuzzing Input:

forge test --match-test fuzzTest --fuzz-seed 12345

Run the same seed to get reproducible failures. Then write a unit test with the exact failing input to understand why it breaks your assumptions.

Coverage-Guided Fuzzing: Foundry v1.3.0 added coverage-guided fuzzing that's much better at finding weird edge cases. When it finds a failure, the input is probably hitting a code path you've never tested manually.

Debugging Gas Issues

Gas failures aren't always about hitting limits - sometimes they indicate deeper algorithmic problems.

Gas Profiling by Function:

forge test --gas-report --match-test expensiveTest

This shows per-function gas usage. If one function is using way more gas than expected, you probably have an algorithmic issue (like an O(n²) operation) rather than a simple "increase gas limit" problem.

Memory vs Storage Access: Reading from storage costs 2,100 gas per slot, reading from memory costs 3 gas. If your gas usage is spiking unexpectedly, you might be hitting storage when you meant to hit memory.

Invariant Testing Failures

Invariant tests are the most sophisticated debugging challenge. When they fail, they've found a sequence of function calls that breaks a property you thought was always true.

Understanding the Attack Sequence: The test output shows exactly which functions were called in what order. This sequence represents a real attack vector or edge case that you need to handle.

State Transition Debugging: Add logging to your invariant tests to see how state changes throughout the attack sequence. Often the issue is that some intermediate state allows operations that should be forbidden.

function invariant_totalSupplyEqualsBalances() public {
    uint256 totalSupply = token.totalSupply();
    uint256 sumBalances = 0;
    
    // Debug: log current state
    console.log("Total supply:", totalSupply);
    console.log("Sum balances:", sumBalances);
    
    assertEq(totalSupply, sumBalances);
}

Production Incident Response

When something breaks in production and users are affected, you need a systematic approach to quickly identify and fix the issue.

Transaction Forensics: For a failed production transaction, get the full trace using the transaction hash:

cast run 0x[tx-hash] --debug

This shows exactly what happened, including all internal calls and state changes. Much more detailed than Etherscan.

State Verification: Check that all external dependencies are in expected states. Often production failures are caused by external contracts being upgraded, paused, or having different permissions than expected.

Minimal Reproduction: Once you understand the failure, create the smallest possible test that reproduces it. This makes it easy to verify that your fix actually works.

When Foundry Debugging Hits Limits

Some issues require tools beyond Foundry:

Assembly Debugging: For inline assembly or low-level issues, you might need to examine the actual EVM bytecode. The EVM debugger can step through individual opcodes.

Cross-Chain Issues: If your contract interacts with multiple chains, you need to fork multiple networks simultaneously, which gets complex. Consider using Tenderly or similar tools for these scenarios.

MEV-Related Failures: If your transaction is being front-run or sandwich attacked, the issue might not be in your code at all. These require understanding mempool dynamics and transaction ordering.

The key insight is that effective debugging is about quickly eliminating possibilities. Start with the most likely causes (wrong addresses, insufficient permissions, gas issues) before diving into complex edge cases. Most production failures have simple explanations once you know where to look.

Foundry vs Other Tools for Smart Contract Debugging

Feature

Foundry

Hardhat

Truffle

Tenderly

Etherscan

Local Debugging

Excellent with traces

Good with console.log

Basic

N/A

N/A

Call Traces

Built-in, detailed

Plugin required

Basic

Excellent

Basic transaction info

Interactive Debugging

Yes, step-through

Limited

No

Yes, visual

No

Gas Profiling

Built-in gas reports

Plugin (hardhat-gas-reporter)

Basic

Advanced analytics

Transaction gas usage

Fork Testing

Fast, cached properly

Works but slower

Basic

N/A

N/A

Fuzz Test Debugging

Native, shows failing inputs

No fuzzing

No fuzzing

N/A

N/A

Invariant Test Debug

Shows attack sequences

No invariant testing

No invariant testing

N/A

N/A

Production Transaction Analysis

cast run with traces

Cannot replay transactions

Cannot replay transactions

Excellent simulation

Basic info only

Error Message Quality

Good Solidity stack traces

JavaScript error wrapping

JavaScript error wrapping

Clear revert reasons

Often just "execution reverted"

State Inspection

Cast for any chain state

Limited to local network

Limited to local network

Multi-chain support

Read-only blockchain data

Deployment Debugging

Script debugging with traces

Task debugging

Migration debugging

Post-deployment analysis

Transaction receipt only

Learning Curve

Steep (new commands)

Easy (familiar JS debugging)

Easy (familiar JS debugging)

Easy (web interface)

Easy (web interface)

Offline Debugging

Full capability

Full capability

Full capability

Requires internet

Requires internet

Cost

Free

Free

Free

Paid plans for advanced features

Free

Related Tools & Recommendations

compare
Similar content

Hardhat vs Foundry: Best Smart Contract Frameworks for Devs

Compare Hardhat vs Foundry, Truffle, and Brownie to pick the best smart contract framework. Learn which tools are actively supported and essential for modern bl

Hardhat
/compare/hardhat/foundry/truffle/brownie/framework-selection-guide
100%
tool
Similar content

Truffle is Dead: Smart Contract Migration & Alternatives

Explore why the Truffle framework was discontinued, its role in smart contract development, and essential migration options and alternatives for your decentrali

Truffle Suite
/tool/truffle/overview
75%
tool
Similar content

Debugging Broken Truffle Projects: Emergency Fix Guide

Debugging Broken Truffle Projects - Emergency Guide

Truffle Suite
/tool/truffle/debugging-broken-projects
69%
tool
Similar content

Hardhat 3 Migration Guide: Speed Up Tests & Secure Your .env

Your Hardhat 2 tests are embarrassingly slow and your .env files are a security nightmare. Here's how to fix both problems without destroying your codebase.

Hardhat
/tool/hardhat/hardhat3-migration-guide
67%
tool
Similar content

Arbitrum Production Debugging: Fix Gas & WASM Errors in Live Dapps

Real debugging for developers who've been burned by production failures

Arbitrum SDK
/tool/arbitrum-development-tools/production-debugging-guide
52%
tool
Similar content

Fix Uniswap v4 Hook Integration Issues - Debug Guide

When your hooks break at 3am and you need fixes that actually work

Uniswap v4
/tool/uniswap-v4/hook-troubleshooting
49%
tool
Similar content

Hardhat Advanced Debugging & Testing: Debug Smart Contracts

Master console.log, stack traces, mainnet forking, and advanced testing techniques that actually work in production

Hardhat
/tool/hardhat/debugging-testing-advanced
47%
tool
Similar content

Foundry: Fast Ethereum Dev Tools Overview - Solidity First

Write tests in Solidity, not JavaScript. Deploy contracts without npm dependency hell.

Foundry
/tool/foundry/overview
45%
tool
Similar content

Optimism Production Troubleshooting - Fix It When It Breaks

The real-world debugging guide for when Optimism doesn't do what the docs promise

Optimism
/tool/optimism/production-troubleshooting
36%
tool
Similar content

Hardhat Ethereum Development: Debug, Test & Deploy Smart Contracts

Smart contract development finally got good - debugging, testing, and deployment tools that actually work

Hardhat
/tool/hardhat/overview
35%
tool
Similar content

Ethereum Layer 2 Development: EIP-4844, Gas Fees & Security

Because mainnet fees will bankrupt your users and your sanity

Ethereum
/tool/ethereum/layer-2-development
35%
howto
Similar content

Deploy Smart Contracts on Optimism: Complete Guide & Gas Savings

Stop paying $200 to deploy hello world contracts. Here's how to use Optimism like a normal person.

/howto/deploy-smart-contracts-optimism/complete-deployment-guide
34%
tool
Similar content

Anchor Framework: Solana Smart Contract Development with Rust

Simplify Solana Program Development with Rust-based Tools and Enhanced Security Features

Anchor Framework
/tool/anchor/overview
32%
tool
Similar content

React Production Debugging: Fix App Crashes & White Screens

Five ways React apps crash in production that'll make you question your life choices.

React
/tool/react/debugging-production-issues
32%
tool
Similar content

Fix Common Xcode Build Failures & Crashes: Troubleshooting Guide

Solve common Xcode build failures, crashes, and performance issues with this comprehensive troubleshooting guide. Learn emergency fixes and debugging strategies

Xcode
/tool/xcode/troubleshooting-guide
32%
tool
Similar content

Grok Code Fast 1: Emergency Production Debugging Guide

Learn how to use Grok Code Fast 1 for emergency production debugging. This guide covers strategies, playbooks, and advanced patterns to resolve critical issues

XAI Coding Agent
/tool/xai-coding-agent/production-debugging-guide
32%
tool
Similar content

Debugging AI Coding Assistant Failures: Copilot, Cursor & More

Your AI assistant just crashed VS Code again? Welcome to the club - here's how to actually fix it

GitHub Copilot
/tool/ai-coding-assistants/debugging-production-failures
31%
tool
Similar content

Helm Troubleshooting Guide: Fix Deployments & Debug Errors

The commands, tools, and nuclear options for when your Helm deployment is fucked and you need to debug template errors at 3am.

Helm
/tool/helm/troubleshooting-guide
31%
tool
Similar content

Hemi Network Bitcoin Integration: Debugging Smart Contract Issues

What actually breaks when you try to build Bitcoin-aware smart contracts

Hemi Network
/tool/hemi/debugging-bitcoin-integration
30%
tool
Similar content

Anchor Framework Performance Optimization: Master Solana Program Efficiency

No-Bullshit Performance Optimization for Production Anchor Programs

Anchor Framework
/tool/anchor/performance-optimization
30%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization