This repository has been archived by the owner on Jul 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
--- | ||
layout: post | ||
published: true | ||
title: Custom Errors in Solidity | ||
date: 2021-04-20 | ||
author: Solidity Team | ||
category: Explainers | ||
--- | ||
|
||
[Solidity v0.8.4](https://github.com/ethereum/solidity/releases/tag/v0.8.4) provides a convenient | ||
and gas-efficient way to explain to user why an operation failed. Errors can be defined inside and | ||
outside of contracts (including interfaces and libraries). | ||
|
||
## Example | ||
|
||
The following contract shows an example usage of an Error: | ||
|
||
```solidity | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.4; | ||
contract VendingMachine { | ||
address payable owner = payable(msg.sender); | ||
error Unauthorized(); | ||
function withdraw() public { | ||
if (msg.sender != owner) | ||
revert Unauthorized(); | ||
owner.transfer(address(this).balance); | ||
} | ||
// ... | ||
} | ||
``` | ||
|
||
The syntax of Errors are similar to that of | ||
[Events](https://docs.soliditylang.org/en/latest/contracts.html#events). They have to be used | ||
together with the [revert | ||
statement](https://docs.soliditylang.org/en/latest/control-structures.html#revert-statement) which | ||
causes all changes in the current call to be reverted and passes the error data back to the caller. | ||
|
||
### Errors in depth | ||
|
||
In the above example, `revert Unauthorized();` is equivalent to the following Yul code: | ||
|
||
``` | ||
let free_mem_ptr := mload(64) | ||
mstore(free_mem_ptr, 0x82b4290000000000000000000000000000000000000000000000000000000000) | ||
revert(free_mem_ptr, 4) | ||
``` | ||
|
||
This is the same as the ABI encoding of a function call with the name `Unauthorized()`. Here | ||
`0x82b42900` is the 'selector' for `Unauthorized()`. In contrast, using a revert string, i.e., | ||
`revert("Unauthorized");` leads to the following Yul code: | ||
|
||
``` | ||
let free_mem_ptr := mload(64) | ||
mstore(free_mem_ptr, 0x08c379a000000000000000000000000000000000000000000000000000000000) | ||
mstore(add(free_mem_ptr, 4), 32) | ||
mstore(add(free_mem_ptr, 36), 12) | ||
mstore(add(free_mem_ptr, 68), "Unauthorized") | ||
revert(free_mem_ptr, 100) | ||
``` | ||
|
||
Here `0x08c379a` is the 'selector' of `Error(string)`. One can see that custom Errors can decrease | ||
both deploy (always) and runtime gas costs (only when the revert condition is met). | ||
|
||
## Errors with Parameters | ||
|
||
It is also possible to have errors that take Parameters. For example, | ||
|
||
```solidity | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.4; | ||
/// Insufficient balance for transfer. Needed `required` but only | ||
/// `available` available. | ||
/// @param available balance available. | ||
/// @param required requested amount to transfer. | ||
error InsufficientBalance(uint256 available, uint256 required); | ||
contract TestToken { | ||
mapping(address => uint) balance; | ||
function transfer(address to, uint256 amount) public { | ||
if (amount > balance[msg.sender]) | ||
// Error call using named parameters. Equivalent to | ||
// InsufficientBalance(balance[msg.sender], amount) | ||
revert InsufficientBalance({ | ||
available: balance[msg.sender], | ||
required: amount | ||
}); | ||
balance[msg.sender] -= amount; | ||
balance[to] += amount; | ||
} | ||
// ... | ||
} | ||
``` | ||
|
||
The error data would be encoded identically as the ABI encoding for function calls, i.e., | ||
``abi.encodeWithSignature("InsufficientBalance(uint256,uint256)", balance[msg.sender], amount)``. | ||
|
||
We hope that frameworks will provide support for Errors. The following is an example on decoding | ||
error data using [ethers.js](https://docs.ethers.io/v5/): | ||
|
||
```javascript | ||
import { ethers } from "ethers"; | ||
|
||
// As a workaround, we have a function with the same name and type as the error in the abi | ||
const abi = [{ | ||
"inputs": [{ | ||
"internalType": "uint256", | ||
"name": "available", | ||
"type": "uint256" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "required", | ||
"type": "uint256" | ||
} | ||
], | ||
"name": "InsufficientBalance", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}]; | ||
|
||
const interface = new ethers.utils.Interface(abi); | ||
// Error data obtained after calling `TestToken.transfer(...)`. | ||
const error_data = "0xcf47918100000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000100000000"; | ||
interface.decodeFunctionData(interface.functions['InsufficientBalance(uint256,uint256)'], error_data); | ||
// [ | ||
// BigNumber { _hex: '0x0100', _isBigNumber: true }, | ||
// BigNumber { _hex: '0x0100000000', _isBigNumber: true }, | ||
// available: BigNumber { _hex: '0x0100', _isBigNumber: true }, | ||
// required: BigNumber { _hex: '0x0100000000', _isBigNumber: true } | ||
// ] | ||
``` | ||
|
||
The compiler includes all errors that a contract can emit in the contract's | ||
[ABI-JSON](https://docs.soliditylang.org/en/latest/abi-spec.html?#json). Note that this will not | ||
include errors emitted through external calls. Similarly, developers can provide | ||
[NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html) documentation for Errors | ||
which would then be part of the user and developer documentation. | ||
|
||
Note that one can never trust the error data. The error data by default bubbles up through the chain | ||
of external calls, which means that a contract may receive an error not defined in any of the | ||
contracts it calls directly. Furthermore, any contract can fake any error by returning data that | ||
matches an error signature, even if the error is not defined anywhere. | ||
|
||
Currently, there is no convenient way to catch errors in Solidity, but this is planned. | ||
|