Smart Contract Debugging: Common Errors and How to Fix Them

Smart contract debugging is the discipline of finding syntax, runtime, logic, and deployment faults before they turn into irreversible on-chain losses. There is no single tool that does the whole job. You need compiler checks, local tests, transaction traces, fuzzing, static analysis, and a review process that treats every failed transaction as evidence.
If you write Solidity, you already know the pain. A function works in Remix, fails in a script, then reverts on a fork with a message like VM Exception while processing transaction: reverted with reason string 'Ownable: caller is not the owner'. That is not a mysterious blockchain problem. It is usually a permissions, state, ABI, gas, or business logic problem hiding behind a short error message.

What Smart Contract Debugging Looks Like in Practice
Modern smart contract debugging is layered. You start close to the code, then move outward toward realistic network behavior.
- Compiler and linter feedback: Use Solidity compiler messages, solhint, and IDE diagnostics to catch syntax and version issues early.
- Local execution: Hardhat, Foundry, Truffle, and Ganache let you reproduce failures without paying real gas.
- Mainnet-fork simulation: Hardhat and Foundry can run tests against forked Ethereum mainnet state, which is useful when a contract depends on Uniswap pools, Chainlink feeds, token balances, or proxy contracts.
- Transaction tracing: Remix, Tenderly, and Ethereum client methods such as
debug.traceTransactioncan show where execution failed at the EVM level. - Static analysis and fuzzing: Slither, MythX, Echidna, and Foundry fuzz tests catch patterns that ordinary unit tests often miss.
To be blunt, running a new contract on a live network before you can reproduce failures locally is poor engineering. On Ethereum, mainnet chain ID is 1, and mistakes there cost real funds.
Common Smart Contract Errors and Fixes
1. Syntax Errors
Syntax errors stop compilation. They are usually the easiest errors to fix, but they still slow teams down when compiler versions are inconsistent across local machines, CI, and deployment scripts.
Common causes include:
- Missing semicolons or braces
- Malformed imports
- Incorrect function visibility such as missing
public,external,internal, orprivate - Using code written for a different Solidity compiler version
- Incorrect interface definitions
How to fix them: Pin your compiler version in configuration. If your contract uses pragma solidity ^0.8.20;, make sure Hardhat, Foundry, and CI are compiling with a compatible Solidity 0.8.x compiler. Run a linter before tests. Do not ignore warnings about shadowed variables or missing SPDX identifiers, since small warnings often sit near larger design mistakes.
2. Runtime Reverts
A runtime revert means the contract compiled, but execution stopped. The cause may be a failed require, a custom error, a failed external call, a panic, or an out-of-gas condition.
Typical symptoms include:
execution revertedreverted with reason stringreverted with custom errorpanic code 0x11, which in Solidity 0.8.x usually means arithmetic overflow or underflow outside anuncheckedblock
How to debug: Read the revert reason first. Then reproduce the same call locally with the same sender, value, calldata, and block state. In Hardhat, a common beginner mistake is calling an owner-only function from the first test account when deployment transferred ownership to another address. The error message may be clear, but only if your test asserts the caller explicitly.
Use Remix for line-by-line debugging when you have the transaction hash and block context. Use Tenderly when you need a richer execution trace, gas profile, decoded calldata, and state inspection. For deployed Ethereum contracts, a node trace through debug.traceTransaction can identify the opcode or internal call where execution broke.
How to fix: Check preconditions, caller permissions, token approvals, and payable values. If the function expects ETH, the client must send value and the Solidity function must be marked payable. If it calls an ERC-20 token, confirm allowance and balance before assuming the contract logic is wrong.
3. Out-of-Gas Errors
Gas bugs are sneaky because they often pass tests with small data sets. A loop over 10 users works. A loop over 10,000 users fails or becomes too expensive to call.
Watch for:
- Loops over dynamic arrays stored on-chain
- Unbounded iteration through mappings via auxiliary arrays
- Recursive logic
- Expensive storage writes inside loops
- Complex calculations that could be moved off-chain
How to debug: Compare gas usage across test sizes. Do not test only the happy path. Measure gas with Hardhat gas reporter, Foundry gas snapshots, or Tenderly traces. If gas grows linearly with the number of users, orders, NFTs, or vault positions, you probably have a production issue waiting to happen.
How to fix: Add batching, pagination, pull-based withdrawals, or off-chain indexing. For example, instead of one admin function distributing rewards to every participant, let each participant claim individually after the contract records a Merkle root or accounting checkpoint.
4. Invalid Opcode, Invalid Jump, and Low-Level Call Failures
These errors usually point to deeper EVM issues: inline assembly, corrupted calldata, incorrect low-level calls, or wrong assumptions about return data.
Low-level calls such as call, delegatecall, and staticcall require careful handling. With proxies, mistakes become harder to see because the user calls a proxy address while execution runs in an implementation contract.
How to debug: Trace the transaction and inspect internal calls. For proxy contracts, verify the implementation address, initializer status, admin address, and storage layout. Proxy misconfiguration is a recurring DeFi failure pattern, especially when initialization is skipped or storage variables are reordered across upgrades.
How to fix: Avoid inline assembly unless you need it. If you use upgradeable contracts, follow a documented upgrade pattern, test storage layout changes, and never leave an implementation uninitialized. When using OpenZeppelin upgradeable contracts, call the initializer once and protect it from being called again.
5. Logic and Business Logic Errors
Logic errors are the expensive ones. The transaction succeeds, events emit, and no revert appears. Yet the contract pays the wrong user, accepts a manipulated price, breaks an invariant, or lets an attacker repeat an action.
The OWASP Smart Contract Top 10 lists business logic vulnerabilities as a major category because automated tools struggle to infer intent. A scanner can flag an external call before a state update. It cannot always know that your liquidation formula pays too much when collateral decimals differ.
Common examples include:
- Reentrancy: External calls occur before internal state is updated.
- Arithmetic mistakes: Decimal mismatches between tokens, especially 6-decimal USDC and 18-decimal ETH-style assets.
- Timestamp dependence: Critical decisions rely too heavily on
block.timestamp. - Oracle manipulation: A single price source determines high-value payouts.
- Broken access control: A role check is missing or applied to the wrong function.
How to debug: Write tests around invariants, not only outputs. For a vault, total shares and total assets should maintain expected relationships. For an NFT sale, supply should never exceed the cap. For a lending protocol, a healthy account should not be liquidatable unless the oracle and collateral math say so.
Use fuzzing. Echidna and Foundry can generate inputs you would not think to write by hand. In practice, fuzz tests catch strange edge cases such as zero amounts, max uint values, repeated calls, and boundary timestamps.
How to fix: Apply checks-effects-interactions. Use Solidity 0.8.x checked arithmetic unless you have measured and justified an unchecked block. For price feeds, prefer trusted oracle networks and consider TWAP designs where appropriate. Document assumptions in plain language so reviewers can compare the intended behavior with the code.
6. Deployment and Configuration Errors
Many apparent contract bugs are deployment mistakes. The code is fine, but the script is pointing at the wrong network, account, ABI, or contract address.
Use this checklist before blaming Solidity:
- Confirm the network. Are you on Ethereum mainnet, Sepolia, Polygon, Arbitrum, or a local fork?
- Confirm the signer. Is the caller the owner or assigned role holder?
- Confirm the contract address. Copy it from the deployment artifact, not a chat message.
- Confirm the ABI. A stale ABI can encode the wrong function call.
- Confirm gas funds. The account needs native currency for gas.
- Confirm constructor and initializer parameters.
- Confirm token approvals and payable value.
On Stellar, debugging has a different flavor. Transaction lifetime matters, and guidance commonly recommends time bounds or ledger bounds because un-applied transactions are unlikely to remain relevant after roughly 1-2 minutes. The lesson transfers well: every chain has environment-specific failure modes.
A Practical Smart Contract Debugging Workflow
Use this workflow when a transaction fails or behaves oddly:
- Reproduce the issue locally. Use Hardhat, Foundry, Truffle, or Ganache. If the bug depends on live liquidity or token state, fork mainnet.
- Reduce the test case. Strip the scenario down to the minimum inputs, sender, and state needed to fail.
- Read the trace. Use Remix, Tenderly, or client tracing to inspect the internal call path.
- Add temporary instrumentation. In Hardhat,
console.logworks only in the local development environment after importinghardhat/console.sol. Remove it before production deployment. - Check invariants. Ask what must always remain true, then write tests for those conditions.
- Run static analysis. Slither is quick and useful, but do not treat it as a substitute for review.
- Fuzz critical functions. Use Foundry or Echidna for state-changing functions that move funds, mint assets, or update accounting.
- Review with another developer. Fresh eyes catch wrong assumptions faster than another hour staring at the same trace.
Where Professional Training Fits
If you are building production contracts, debugging is not separate from security. It is the daily method for proving that the contract does what you claim. Treat this article as a study map for topics commonly covered in smart contract and blockchain developer training.
The Certified Smart Contract Developer™, Certified Blockchain Developer™, and Certified Blockchain Expert™ programs pair well with the practices above. Whichever you pick, combine certification study with hands-on work: write ERC-20 and ERC-721 contracts, break them on a local fork, then fix the failures with traces and tests.
Next Step: Build a Debugging Habit
Pick one contract you have written, even a small token or NFT mint. Add unit tests, run Slither, fuzz one state-changing function, and replay one transaction in Remix or Tenderly. If you want a structured path after that, start with the Certified Smart Contract Developer™ program and focus your practice on Hardhat, Foundry, Solidity 0.8.x, and real transaction traces.
Related Articles
View AllSmart Contracts
Common Smart Contract Vulnerabilities Every Developer Must Know
Learn the most common smart contract vulnerabilities, including access control flaws, reentrancy, oracle manipulation, flash loans, and upgrade risks.
Smart Contracts
Smart Contract Security Checklist: Common Vulnerabilities and How to Prevent Exploits
A practical smart contract security checklist covering access control, reentrancy, oracle risks, flash loans, upgrade safety, and testing to prevent common Web3 exploits.
Smart Contracts
Smart Contract Certification Guide: How to Validate Your Blockchain Skills
Learn how smart contract certification validates blockchain skills, which developer or auditor path to choose, and how to prove expertise with real projects.
Trending Articles
How Blockchain Secures AI Data
Understand how blockchain technology is being applied to protect the integrity and security of AI training data.
What is AWS? A Beginner's Guide to Cloud Computing
Everything you need to know about Amazon Web Services, cloud computing fundamentals, and career opportunities.
Claude AI Tools for Productivity
Discover Claude AI tools for productivity to streamline tasks, manage workflows, and improve efficiency.