Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Commit

Permalink
Blog post about custom errors
Browse files Browse the repository at this point in the history
  • Loading branch information
hrkrshnn committed Apr 20, 2021
1 parent 0c156a9 commit 6d1a46d
Showing 1 changed file with 150 additions and 0 deletions.
150 changes: 150 additions & 0 deletions _posts/2021-04-20-custom-errors.md
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.

0 comments on commit 6d1a46d

Please sign in to comment.