Handle Errors in Solidity With Require and Revert
A good programmer is someone that can imagine and handle all the cases that will.
What you will learn
- Where handle errors in solidity with require and revert fits into an Ethereum application.
- Which assumptions should be tested before a mainnet deployment.
- What to read next when this concept is working.
A good programmer is someone that can imagine and handle all the cases that will happens during the execution of the program. For this, Solidity offers different functions that we’ll cover in this article: require and revert.
Require
The require Solidity function guarantees validity of the condition(s) passed as parameter that cannot be detected before execution. It checks inputs, contract state variables and return values from calls to external contracts.
The same engineering term can describe different protocol rules. Use the Bitcoin and Ethereum issuance, control, and execution comparison.
Solidity manages errors by using state-reverting exceptions. These exceptions revert all modifications done to the state in the current call, including all sub-calls. Additionally, it can show an error to the caller if a string is passed as parameter.
Require is convenient for checking inputs of a function especially in modifiers for example. A common use case for example is to check if the caller of the function is really the owner of the smart contract:
Ethereum calls and Bitcoin spends use different state models. Compare them with the guide to Bitcoin inputs, outputs, fees, and signatures.
modifier onlyOwner() { require(msg.sender == owner); _; } or in the case of an ERC20 token, checking if the address sending tokens has enough tokens to perform the transfer:
function transfer(address receiver, uint256 numTokens) public override returns (bool) { require(numTokens <= balances[msg.sender]); balances[msg.sender] = balances[msg.sender].sub(numTokens); balances[receiver] = balances[receiver].add(numTokens); emit Transfer(msg.sender, receiver, numTokens); return true; } The require function allows you to pass a second parameter to describe the error that will be seen by the user as a string. For eg:
To compare network design without reducing it to tickers, use the cryptocurrency network-design comparison tool.
require(msg.sender == owner, "Caller is not the owner"); If we execute the transaction from a different account than the owner of the smart contract, the following will happen on Remix logs.
When the conditions of a require function are not met, all the changes made inside of the transaction are reverted and the remaining gas is returned to the user.
Revert
Revert is quite similar to require but does only accept one optional parameter: a string explaining why the revert happened. The revert function stops the execution of the smart contract, revert all changes that were made during the execution and return the remaining gas to the caller.
Revert can be used instead of require when it makes it easier to read the condition on why it fails. For example we could write the two previous examples like this:
modifier onlyOwner() { if (msg.sender != owner) { revert("Caller is not the owner"); } _; } or in the case of the ERC20 transfer function:
function transfer(address receiver, uint256 numTokens) public override returns (bool) { if (numTokens > balances[msg.sender]) { revert("Not enough tokens to transfer"); } balances[msg.sender] = balances[msg.sender].sub(numTokens); balances[receiver] = balances[receiver].add(numTokens); emit Transfer(msg.sender, receiver, numTokens); return true; } In conclusion you can see that Solidity offers everything you need to handle any cases that your contract will meet during execution. We always advice you to think about who is calling your code, where are you transferring value to and from who, are the inputs correct and is the output of an external function correct?
Turn the example into production evidence
A working demo is the beginning of verification, not the end. Add tests for failure paths, inspect every permission, and record the chain, contract address, compiler, and dependency versions used for the deployment.
Check behavior
Test expected inputs, boundary values, reverts, events, and calls made to other contracts.
Check operations
Plan key storage, monitoring, pause or upgrade authority, and a response if an assumption stops being true.