WTF Solidity: 15. Errors
Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies.
Twitter: @0xAA_Science | @WTFAcademy_
Community: Discord|Wechat|Website wtf.academy
Codes and tutorials are open source on GitHub: github.com/AmazingAng/WTFSolidity
In this chapter, we will introduce three ways to throw exceptions in solidity: error, require, and assert.
Errors
Solidity has many functions for error handling. Errors can occur at compile time or runtime.
Error
error statement is a new feature in solidity 0.8. It saves gas and informs users why the operation failed. It is the recommended way to throw error in solidity.
Custom errors are defined using the error statement, which can be used inside and outside of contracts. Below, we created a TransferNotOwner error, which will throw an error when the caller is not the token owner during transfer:
error TransferNotOwner(); // custom error
In functions, error must be used together with revert statement.
function transferOwner1(uint256 tokenId, address newOwner) public {
if(_owners[tokenId] != msg.sender){
revert TransferNotOwner();
}
_owners[tokenId] = newOwner;
}
The transferOwner1() function will check if the caller is the owner of the token; if not, it will throw a TransferNotOwner error and revert the transaction.
Require
require statement was the most commonly used method for error handling prior to solidity 0.8. It is still popular among developers.
Syntax of require:
require(condition, "error message");
An exception will be thrown when the condition is not met.
Despite its simplicity, the gas consumption is higher than error statement: the gas consumption grows linearly as the length of the error message increases.
Now, let's rewrite the above transferOwner function with the require statement:
function transferOwner2(uint256 tokenId, address newOwner) public {
require(_owners[tokenId] == msg.sender, "Transfer Not Owner");
_owners[tokenId] = newOwner;
}
Assert
The assert statement is generally used for debugging purposes, because it does not include error message to inform the user.
Syntax of assert:
`assert(condition);
If the condition is not met, an error will be thrown.
Let's rewrite the transferOwner function with the assert statement:
function transferOwner3(uint256 tokenId, address newOwner) public {
assert(_owners[tokenId] == msg.sender);
_owners[tokenId] = newOwner;
}
Remix Demo
After deploying Error contract.
error: Enter auint256number and a non-zero address, and call thetransferOwner1()function. The console will throw a customTransferNotOwnererror.
require: Enter auint256number and a non-zero address, and call thetransferOwner2()function. The console will throw an error and output the error message"Transfer Not Owner".
assert: Enter auint256number and non-zero address and call thetransferOwner3function. The console will throw an error without any error messages.
Gas comparison
Let's compare the gas consumption of error, require, and assert.
You can find the gas consumption for each function call with the Debug button of the remix console:
- gas for
error:24457wei - gas for
require:24755wei - gas for
assert:24473wei
We can see that the error consumes the least gas, followed by the assert, while the require consumes the most gas!
Therefore, error not only informs the user on the error message, but also saves gas.
Summary
In this chapter, we introduced 3 statements to handle errors in Solidity: error, require, and assert. After comparing their gas consumption, error statement is the cheapest, while require has the highest gas consumption.