Replies: 1 comment 2 replies
-
@cromewar This article is quite a mess, but maybe we could spin off a series of shorter tl;dr articles where we discuss good practices on how to solve problems. Here are some ideas:
|
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Manual and unaided debugging of Solidity Python blockchain applications.
I haven't worked directly in software development for many years, so I'm not an expert when it comes to programming. But somehow I know it differently. Finding bugs in code used to be a lot easier. Maybe it's just me, but I find debugging blockchain applications quite tedious. Now, in the last month, I've become active in the discussion group around this repository. Judging by the questions I read and sometimes answer, I think I'm not alone. Many users struggle just like I do with error messages that rarely help find the error. In this article, I will go through an example that illustrates the code error scenario. There are of course other scenarios, such as incorrect settings, software version problems, or network errors, to name a few.
To anticipate what went wrong in the example that follows. I had tried to do mathematical operations with different types of integers in my solidity code. This is of course a noob mistake. The problem, once I found it, was very easy to fix. It just wasn't easy to spot from the information I got.
What I will describe in the following is on the one hand the flow in the development process and at which point you notice that something is wrong and on the other hand what clues there are that can help you to find out where to start looking for the error.
The Solidity Code
The following code has been reduced to the essentials for debugging purposes. I have systematically switched off all other error sources one after the other. For example, I do not load the data stream key from the configuration file and do not pass it to the constructor during deployment.
There are no error messages under VSCode or Remix and the code compiles without any problems. Yet this code produces and error when running the test.
Output of the compilation
How I became aware of the error
After completing my contract and compiling it uneventfully (i.e. without error warnings). I set about writing a test for the getEntranceFee() function.
It looks like the following. What you see here is already reduced considerably to exclude additional error sources in order to better isolate the error. For this, I created a separate file so that I can test it separately via
brownie test [path]
. This way I don't ruin the work I had put into writing the test until the error occurred.The error message
I will now show the complete error message so that it becomes clear how much of the output text does not help in solving the problem. There are 72 lines in the error message. After this, I will go into what of it is helpful in finding the error.
Helpful error output
And these lines help to better isolate the error.
In the first part you can see from which line in the Python code something went wrong.
In the last line you can see that there was a VirtualMachineError and that this caused a "revert".
(As always, completely useless is the following text: "INFO: Could not find files for the given pattern(s).")
What other typical error sources are there in the test code and how can we exclude them as errors?
Why should you do this? The more complex the code becomes, and the more files and libraries have to interact, the harder it is to find a bug. There is also the possibility that several errors occur in combination. A systematic procedure to temporarily resolve these dependencies is therefore helpful in order to be able to narrow down the source(s) of the error by means of an exclusion procedure.
Possible source of error:
A key value is read from the configuration file and is not set or not set correctly, for example in the wrong network.
Exclusion:
For simplicity, the key can simply be written directly into the code.
Possible source of error:
A function returns a wrong value or no value at all. In this case getEntranceFee().
Exclusion:
Simply set the return value statically.
Possible error source:
A change to the solidity code was made but not compiled.
Exclusion:
Compile before testing.
Back to the error message
In the error message, there is a section "FAILURES". Here the code breaks at the point where lottery.getEntranceFee() is called. So the deployment seems to have worked. The function does not return any value at all.
Furthermore, there is a line
E brownie.exceptions.VirtualMachineError: revert
This indicates that the transaction was reverted.
Here at the latest, one should take a closer look at the Solidity contract. The fact that the above-mentioned error message does not refer to positions in the Solidity code is due to the fact that Python is already working with a compiled file and there is no reference to our .sol file.
some tooling after all
The first thing I did now is to compile the solidity code again. Only as a test. Again, no problems. Now I opened a remix window in the browser and copied the code into it and compiled it. Again no error messages. Connected the wallet, started Injected Web, copied the chainlink address for ETH/USD for Rinkeby and deployed the contract with the data feed address I just copied. Still no error message. If I now click on the getEntranceFee button in Remix, I get "call to LotterySimple.getEntranceFee errored: execution reverted" in the terminal.
You have to realise here how many other sources of error I have already ruled out. The rest, however, means thinking.
The Solidity smart contract
Here is the function where the error is.
We have been able to narrow the search down to 5 lines of code.
Notices
If you look closely at the data types, there are very different forms here: uint8, uint256, int256 and the constant TICKET_PRICE_USD, which is created at the beginning of the contract with uint16.
uint16 public TICKET_PRICE_USD;
Using different data types without typecasting in mathematical operations is always critical. Especially when types with small memory areas are thrown together with types that have a very large memory requirement. Namely, here uint16, which can never be larger than 65.535, and a uint256 which can be an insanely large number.
Try to fix the problem:
In this line of code
uint256 ticketPriceUsdWeiEquivalent = TICKET_PRICE_USD * 10**18;
I replaced the variable with the desired number 50 like this
uint256 ticketPriceUsdWeiEquivalent = 50 * 10**18;
The test then ran successfully. Of course, this is not the final solution, this is easily achieved by typecasting as follows.
uint256 ticketPriceUsdWeiEquivalent = uint256(TICKET_PRICE_USD) * 10**18;
Summary
This relatively long post has the following key points.
Beta Was this translation helpful? Give feedback.
All reactions