Effective Smart Contract Testing: Developer Revert Comments
Gas efficient, targeted revert tests? How to have your Brownie and eat it too!
A good approach when testing a smart contract is to write a test for every branch of a function. Consider the following example:
There are four branches to consider when testing
- A successful transfer, with verification that the balances have adjusted correctly.
- A transfer that fails because the caller is not the owner.
- A transfer that fails because the receiver is not permitted.
- A transfer that fails because the sender does not have a sufficient balance.
With the given code, tests for the failed paths are of limited effectiveness. For example, consider the following test:
The test passes, but we cannot be certain that the revert occurred due to an insufficient balance. It is possible that
accounts is not permitted to receive tokens, and we have a case of the right result from the wrong behavior. Now, imagine that we later perform a refactor on the contract and accidentally remove the check for sufficient balance. Since we are not actually testing what we think we’re testing, this test still passes, and we’re left with a rather large undetected bug in the code.
Revert Strings to the Rescue!
Fortunately there is a solution: we can add error strings to our require statements, and include them in our tests. Here is our updated code:
And now we update our test to specifically target the expected error string:
Now the only way for our test to pass is if the call to
adminTransfer reverts with an “Insufficient balance” error string. Refactor away, friends, our test cases are solid.
Unfortunately, the use of error strings comes with a trade-off. Each error string adds a minimum 20,000 gas to the contract deployment cost, and increases transaction costs when the function is executed. Including an error string for every
revert statement is often impractical and sometimes simply not possible due to contract size limits.
So we are left with a hard decision. Better test cases, or more efficient code?
Developer Revert Comments to the Rescue!
The good news: we don’t actually have to make this decision 😄 Brownie allows us to include revert strings as source code comments! These error strings are not included in the compiled bytecode, but still accessible when running tests. It’s the best of both worlds — you can write tests that target a specific
revert statement without compromising the gas efficiency of your contract.
Check out our example code, updated to take advantage of developer revert comments:
And the updated test case:
Using developer revert comments is simple. At the end of a line of code that includes a potential revert, add a comment starting with
// dev: in Solidity or
# dev: in Vyper. Brownie detects these comments and does the rest automagically. It’s that easy!
To Learn More…
Check out the Brownie documentation to learn more about developer revert comments, or more generally about testing smart contracts in Python.
You can also join the Brownie Gitter channel to ask questions or talk to other developers who are using Brownie for testing. And, as always, feel free to reach out to me directly via email or Telegram. I would love to hear from you.