If you’ve ever received a “gas message” error which makes no sense in Solidity then you’ve probably come across one of it’s greatest flaws. Error reporting and debugging isn’t great for blockchain developers but in this tutorial I’m going to provide some tips, tools and resources to debug Solidity smart contracts.
- Debug & Test Solidity Smart Contracts
- Solidity Debugging Tools & Resources
- Solidity Error Reporting
- 8 Strategies For Robust Contracts
- Next Level Solidity Debugging Framework
Debug & Test Solidity Smart Contracts
Smart contracts are often complex, immutable and difficult to fix. That’s why testing and debugging your Solidity contracts is crucial to ensure that they operate flawlessly.
Testing allows you to verify that your contract functions behave as expected. By writing comprehensive unit tests, you can catch bugs and defects before they are deployed.
Debugging is the process of identifying and fixing errors in your code and generally happens either at the compile stage or when we get a failed transaction. It involves using tools and techniques to inspect the state of your contract, trace the execution of your code, and find the root cause of any issues.
Solidity Debugging Tools & Resources
I generally work in a multi-stage process where I’ll write proof-of-concept code and then refactor this and start testing to make it somewhat fit to be deployed, at least on Goerli.
Most typos, missing variables and silly bugs are found at the proof of concept stage. These will be picked up when compiling. I like to do this as much as possible in remix but larger projects may be more well suited to a framework such as hardhat.
The error reporting in remix at the compile level is actually pretty good. It highlights the lines where it thinks there’s an issue and gives informative details about what you’ve messed up.
Once the thing compiles I like to do unit tests and system tests to try and find more bugs. I have switched between hardhat and foundry for this but am finding myself coming back to hardhat more and more because of it’s excellent scripting framework and ethers support.
A simple unit test for a function may look something like this:-
it("Should allow 3rd party creation of perps", async () => {
const newPerp = await perpFactory.createPerp('FOOBAR',100);
const perpCount = await perpFactory.perpCount();
expect(perpCount).to.equal(2);
});
Full hardhat unit tests for this contract are here: https://github.com/jamesbachini/PerpFactory/blob/main/test/0x1.05-Perp.js
The other tool that I use for debugging a lot is tenderly which allows you to paste in a transaction hash and then debug the flow to try and figure out where it went wrong.
On mainnet there is also @samczsun’s transaction viewer which provides a breakdown of the call trace and then you can click on each call to get more information about the function calls etc. This is really useful for investigating MEV bots in the wild.
These tools are useful at different times throughout the development process but all come under the Solidity debugger umbrella. It’s worth checking them out if you aren’t already familiar, so you know what is available and how to use them when you do come up against a bug.
Solidity Error Reporting
My biggest complaint with Solidity development is error reporting on reverted transactions. The EVM often doesn’t provide much feedback if a transaction fails leaving the developer in the dark as to what went wrong.
My pet hate is the “Error: gas is missing” or “Error: can’t calculate gas” errors which I get sent at least once a week asking for help. These errors often just mean the transaction is going to revert and wont go through. The reason could be anything from a bad token address to some obscure bug that puts the entire protocol at risk.
By adding require, revert, assert statements to the solidity code we can at least get a custom message which will identify where the problem is.
require(_amount > 0, "Stake32 no funds sent");
Notice we put a contract reference at the start of the error which is very useful when there are multiple contracts or you are interacting with 3rd party interfaces.
8 Strategies For Robust Contracts
- Use a linter either in vscode or remix to check your code for errors and best practices as you type
- Write unit tests for your contract to ensure they function correctly
- Write system tests and fuzz parameters to try and break your own code
- Use defensive programming techniques, such as checking for null values and input errors
- Use openzeppelin libraries where possible as they are battle tested
- Use try-catch blocks to handle exceptions and prevent the transaction failing
- Use custom revert, assert, require statements to provide better error reporting
- Don’t rely on 3rd party interfaces to behave predictably, especially if they run upgradeable contracts
Next Level Solidity Debugging Framework
Nomic Labs are working on something. These are the guys that built hardhat and I saw a presentation at DevCon Bogota where they announced a new debugging framework for Solidity developers.
My somewhat limited understanding is that it will be a new compiler and local EVM that offers more detailed error reporting. This then enables better debugging tools to be created within Hardhat or maybe there will be a new debugging module added.
It’s something that could definitely improve the workflow for Solidity devs in the future so keep an eye on their releases.