An Intro to Solidity Debugging
Welcome to this quick-start tutorial for debugging Solidity.
The goal of this article is to introduce high-level concepts in debugging Solidity smart contracts primarily using the Remix online IDE and additional resources to learn more.
Remix Online IDE
Get Remix
In this tutorial I will be using the Remix Javascript VM. If you have not yet become familiar with Remix here's a good place to start:
First, it helps to have a high-level understanding of what exactly is happening when you write and deploy a solidity smart contract. Solidity code is compiled into Bytecode and interacts with the Ethereum blockchain using JSON RPC.
There are two primary functions used to call a smart contract function:
_eth_call = View or pure functions (calls) can use return statements but no events
_eth_sendTransaction = Functions that modify the blockchain and will cost ether (transactions) can use events
Types of Errors
When debugging solidity there are 3 major error types:
Syntax Errors = easy to fix - Caught by compiler.
Runtime Errors = Happen after contract has been deployed and solidity has been compiled to bytecode. These will create reverted transactions are not as easy to debug as syntax errors.
Logic Errors = Occur after the contract is deployed. Not a runtime error in that the EVM (Ethereum Virtual Machine) has no issues but the logic of the code isn’t correct.
Common Tools for Debugging
Compiler and debugging = Remix and truffle (syntax, runtime errors, logic)
Linter = IDE Plugins for Visual Studio Code and other IDEs (syntax, logic)
Tests = Run tests with Mocha, ect. (runtime errors, logic)
Using Remix for Syntax Errors
Remix is great at catching syntax and runtime errors. The Remix online IDE will point out errors as they occur during code creation.
Common Runtime errors
Out of gas - Not enough gas to complete transaction
Revert - An opcode of vm stops transaction may refund ether
Invalid opcode - An opcode is executed that does not exist. Vm stops transaction
Invalid JUMP - Execute a function that does not exist usually calling a function in another contract that does not exist
Stack overflow - Recursion goes too deep, 1024 x limit for any function to call itself
Stack underflow - Try to read item on stack that does not exist
Debugging an Out of Gas error with Remix
Gas limit = The maximum amount we are willing to pay to execute a function.
The gas limit is relevant mostly for functions that are based on conditional logic and costs can vary depending on options used.
If you are unsure about how Gas works, this may help.
How to tell if you have out of gas error in Remix (You won’t always get an accurate error code for this)
Here’s a simple smart contract to test the out of gas error. Note the low Gas Limit that has been set. Try running the set function using only 30000 gas and note the error.
It doesn’t tell you that it’s an out of gas error but when you see execution failed and gas - transaction cost are the same then it’s likely an out of gas error.
Double the gas, re-run the function then check the transaction cost field - rinse and repeat to get the correct min amount of gas necessary.
Here's a list of gas prices for solidity code execution
Understanding the Invalid Opcode / Revert Error
Run each function in Remix to see these errors in action.
Debugging with Remix
In the test contract below we are creating a simple wallet that can only hold up to 1000 Wei. The code has a bug. You can test the contract by first sending 300 Wei, then another 300, then 400 which in theory should work since we are not exceeding the magic number of 1000. As it turns out however we run into a problem.
To start the Remix debugger click the “debug” button to the right of the error message that appears below the code window. On the right panel you will then see this -
Note the opcodes (PUSH1, MSTORE, etc.) which is what Solidity gets compiled into for the Ethereum Virtual Machine to execute.
It’s not entirely necessary to know all the opcodes but useful to get familiar with them especially for debugging purposes. You can find them here
The top-down, sequential ordering of the opcodes designates the steps being taken during code execution. The current step is the one which is highlighted 000 PUSH1 80 | vm trace step: 28
The slider below the opcode step window shows you where you are in the steps sequence. As you move the slider the lines of code responsible for the opcode execution are highlighted.
*Note - One line of Solidity code may map to more than one opcode execution
Moving forward, we can advance the slider to line 8 of our code and what we want to know is what amount the balance variable is holding. As we can see here it starts out at 0.
Then if we use the step-through arrow button in this next image while keeping an eye on the balance local variable
we see that at the point we run our require statement which adds the balance and msg.value that we are already at 1000 Wei which means the last deposit of 400 Wei we sent had already been added to the balance before we execute the require check and at that point if we try to add msg.value to balance we are 400 Wei over the limit.
The code will work if we simply remove “+ msg.value” from our require statement.
In Closing...
Remix is a powerful tool for debugging Solidity. I hope you have enjoyed this intro to solidity debugging. Learn more about the Remix debugger at
https://remix.readthedocs.io/en/latest/tutorial_debug.html
Other useful resources for Solidity debugging:
(used for base concepts in this tutorial) Remix debugging tutorials
https://www.youtube.com/channel/UCZM8XQjNOyG2ElPpEUtNasA
New verification tool
https://medium.com/limechain/zeppelin-solidity-release-candidate-new-functional-verification-tool-5b722e8d10b2
Debugging with Truffle
https://www.youtube.com/watch?v=0r24KRMvHG0
Mocha Gas Estimator
https://github.com/cgewecke/eth-gas-reporter
Start writing here...