Documentation for custom errors.

This commit is contained in:
chriseth 2021-02-04 15:58:06 +01:00
parent 15fe07bebe
commit 0c1be06cba
8 changed files with 241 additions and 30 deletions

View File

@ -493,12 +493,51 @@ the arguments not be indexed). Developers may overcome this tradeoff and achieve
efficient search and arbitrary legibility by defining events with two arguments — one
indexed, one not — intended to hold the same value.
.. _abi_errors:
Errors
======
In case of a failure inside a contract, the contract can use a special opcode to abort execution and revert
all state changes. In addition to these effects, descriptive data can be returned to the caller.
This descriptive data is the encoding of an error and its arguments in the same way as data for a function
call.
As an example, let us consider the following contract whose ``transfer`` function always
reverts with a custom error of "insufficient balance":
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract TestToken {
error InsufficientBalance(uint256 available, uint256 required);
function transfer(address /*to*/, uint amount) public pure {
revert InsufficientBalance(0, amount);
}
}
The return data would be encoded in the same way as the function call
``InsufficientBalance(0, amount)`` to the function ``InsufficientBalance(uint256,uint256)``,
i.e. ``0xcf479181``, ``uint256(0)``, ``uint256(amount)``.
The error selectors ``0x00000000`` and ``0xffffffff`` are reserved for future use.
.. warning::
Never trust 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.
.. _abi_json:
JSON
====
The JSON format for a contract's interface is given by an array of function and/or event descriptions.
The JSON format for a contract's interface is given by an array of function, event and error descriptions.
A function description is a JSON object with the fields:
- ``type``: ``"function"``, ``"constructor"``, ``"receive"`` (the :ref:`"receive Ether" function <receive-ether-function>`) or ``"fallback"`` (the :ref:`"default" function <fallback-function>`);
@ -536,18 +575,37 @@ An event description is a JSON object with fairly similar fields:
- ``anonymous``: ``true`` if the event was declared as ``anonymous``.
Errors look as follows:
- ``type``: always ``"error"``
- ``name``: the name of the error.
- ``inputs``: an array of objects, each of which contains:
* ``name``: the name of the parameter.
* ``type``: the canonical type of the parameter (more below).
* ``components``: used for tuple types (more below).
.. note::
There can be multiple errors with the same name and even with identical signature
in the JSON array, for example if the errors originate from different
files in the smart contract or are referenced from another smart contract.
For the ABI, only the name of the error itself is relevant and not where it is
defined.
For example,
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
pragma solidity ^0.8.4;
contract Test {
constructor() { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
error InsufficientBalance(uint256 available, uint256 required);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
@ -557,6 +615,10 @@ would result in the JSON:
.. code-block:: json
[{
"type":"error",
"inputs": [{"name":"available","type":"uint256"},{"name":"required","type":"uint256"}],
"name":"InsufficientBalance"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"

View File

@ -24,6 +24,7 @@ There is no "cron" concept in Ethereum to call a function at a particular event
.. include:: contracts/functions.rst
.. include:: contracts/events.rst
.. include:: contracts/errors.rst
.. include:: contracts/inheritance.rst

82
docs/contracts/errors.rst Normal file
View File

@ -0,0 +1,82 @@
.. index:: ! error, revert
.. _errors:
*******************************
Errors and the Revert Statement
*******************************
Errors in Solidity provide a convenient and gas-efficient way to explain to the
user why an operation failed. They can be defined inside and outside of contracts (including interfaces and libraries).
They have to be used together with the :ref:`revert statement <revert-statement>`
which causes
all changes in the current call to be reverted and passes the error data back to the
caller.
::
// 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])
revert InsufficientBalance({
available: balance[msg.sender],
required: amount
});
balance[msg.sender] -= amount;
balance[to] += amount;
}
// ...
}
Errors cannot be overloaded or overridden but are inherited.
The same error can be defined in multiple places as long as the scopes are distinct.
Instances of errors can only be created using ``revert`` statements.
The error creates data that is then passed to the caller with the revert operation
to either return to the off-chain component or catch it in a :ref:`try/catch statement <try-catch>`.
Note that an error can only be caught when coming from an external call,
reverts happening in internal calls or inside the same function cannot be caught.
If you do not provide any parameters, the error only needs four bytes of
data and you can use :ref:`NatSpec <natspec>` as above
to further explain the reasons behind the error, which is not stored on chain.
This makes this a very cheap and convenient error-reporting feature at the same time.
More specifically, an error instance is ABI-encoded in the same way as
a function call to a function of the same name and types would be
and then used as the return data in the ``revert`` opcode.
This means that the data consists of a 4-byte selector followed by :ref:`ABI-encoded<abi>` data.
The selector consists of the first four bytes of the keccak256-hash of the signature of the error type.
.. note::
It is possible for a contract to revert
with different errors of the same name or even with errors defined in different places
that are indistinguishable by the caller. For the outside, i.e. the ABI,
only the name of the error is relevant, not the contract or file where it is defined.
The statement ``require(condition, "description");`` would be equivalent to
``if (!condition) revert Error("description")`` if you could define
``error Error(string)``.
Note, however, that ``Error`` is a built-in type and cannot be defined in user-supplied code.
Similarly, a failing ``assert`` or similar conditions will revert with an error
of the built-in type ``Panic(uint256)``.
.. note::
Error data should only be used to give an indication of failure, but
not as a means for control-flow. The reason is that the revert data
of inner calls is propagated back through the chain of external calls
by default. This means that an inner call
can "forge" revert data that looks like it could have come from the
contract that called it.

View File

@ -17,7 +17,7 @@ the usual semantics known from C or JavaScript.
Solidity also supports exception handling in the form of ``try``/``catch``-statements,
but only for :ref:`external function calls <external-function-calls>` and
contract creation calls.
contract creation calls. Errors can be created using the :ref:`revert statement <revert-statement>`.
Parentheses can *not* be omitted for conditionals, but curly braces can be omitted
around single-statement bodies.
@ -551,7 +551,8 @@ state in the current call (and all its sub-calls) and
flags an error to the caller.
When exceptions happen in a sub-call, they "bubble up" (i.e.,
exceptions are rethrown) automatically. Exceptions to this rule are ``send``
exceptions are rethrown) automatically unless they are caught in
a ``try/catch`` statement. Exceptions to this rule are ``send``
and the low-level functions ``call``, ``delegatecall`` and
``staticcall``: they return ``false`` as their first return value in case
of an exception instead of "bubbling up".
@ -562,17 +563,11 @@ of an exception instead of "bubbling up".
if the account called is non-existent, as part of the design
of the EVM. Account existence must be checked prior to calling if needed.
Exceptions in external calls can be caught with the ``try``/``catch`` statement.
Exceptions can contain data that is passed back to the caller.
This data consists of a 4-byte selector and subsequent :ref:`ABI-encoded<abi>` data.
The selector is computed in the same way as a function selector, i.e.,
the first four bytes of the keccak256-hash of a function
signature - in this case an error signature.
Currently, Solidity supports two error signatures: ``Error(string)``
and ``Panic(uint256)``. The first ("error") is used for "regular" error conditions
while the second ("panic") is used for errors that should not be present in bug-free code.
Exceptions can contain error data that is passed back to the caller
in the form of :ref:`error instances <errors>`.
The built-in errors ``Error(string)`` and ``Panic(uint256)`` are
used by special functions, as explained below. ``Error`` is used for "regular" error conditions
while ``Panic`` is used for errors that should not be present in bug-free code.
Panic via ``assert`` and Error via ``require``
----------------------------------------------
@ -604,17 +599,24 @@ The error code supplied with the error data indicates the kind of panic.
#. 0x41: If you allocate too much memory or create an array that is too large.
#. 0x51: If you call a zero-initialized variable of internal function type.
The ``require`` function either creates an error of type ``Error(string)``
or an error without any error data and it
The ``require`` function either creates an error without any data or
an error of type ``Error(string)``. It
should be used to ensure valid conditions
that cannot be detected until execution time.
This includes conditions on inputs
or return values from calls to external contracts.
A ``Error(string)`` exception (or an exception without data) is generated
.. note::
It is currently not possible to use custom errors in combination
with ``require``. Please use ``if (!condition) revert CustomError();`` instead.
An ``Error(string)`` exception (or an exception without data) is generated
by the compiler
in the following situations:
#. Calling ``require`` with an argument that evaluates to ``false``.
#. Calling ``require(x)`` where ``x`` evaluates to ``false``.
#. If you use ``revert()`` or ``revert("description")``.
#. If you perform an external function call targeting a contract that contains no code.
#. If your contract receives Ether via a public function without
``payable`` modifier (including the constructor and the fallback function).
@ -679,22 +681,43 @@ the changes in the caller will always be reverted.
which consumed all gas available to the call.
Exceptions that use ``require`` used to consume all gas until before the Metropolis release.
.. _revert-statement:
``revert``
----------
The ``revert`` function is another way to trigger exceptions from within other code blocks to flag an error and
revert the current call. The function takes an optional string
message containing details about the error that is passed back to the caller
and it will create an ``Error(string)`` exception.
A direct revert can be triggered using the ``revert`` statement and the ``revert`` function.
The following example shows how to use an error string together with ``revert`` and the equivalent ``require``:
The ``revert`` statement takes a custom error as direct argument without parentheses:
revert CustomError(arg1, arg2);
For backards-compatibility reasons, there is also the ``revert()`` function, which uses parentheses
and accepts a string:
revert();
revert("description");
The error data will be passed back to the caller and can be caught there.
Using ``revert()`` causes a revert without any error data while ``revert("description")``
will create an ``Error(string)`` error.
Using a custom error instance will usually be much cheaper than a string description,
because you can use the name of the error to describe it, which is encoded in only
four bytes. A longer description can be supplied via NatSpec which does not incur
any costs.
The following example shows how to use an error string and a custom error instance
together with ``revert`` and the equivalent ``require``:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
pragma solidity ^0.8.4;
contract VendingMachine {
address owner;
error Unauthorized();
function buy(uint amount) public payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
@ -705,9 +728,17 @@ The following example shows how to use an error string together with ``revert``
);
// Perform the purchase.
}
function withdraw() public {
if (msg.sender != owner)
revert Unauthorized();
payable(msg.sender).transfer(address(this).balance);
}
}
If you provide the reason string directly, then the two syntax options are equivalent, it is the developer's preference which one to use.
The two ways ``if (!condition) revert(...);`` and ``require(condition, ...);`` are
equivalent as long as the arguments to ``revert`` and ``require`` do not have side-effects,
for example if they are just strings.
.. note::
The ``require`` function is evaluated just as any other function.

View File

@ -185,7 +185,7 @@ invalid bids.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
pragma solidity ^0.8.3;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;

View File

@ -83,7 +83,7 @@ registering with a username and password, all you need is an Ethereum keypair.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
pragma solidity ^0.8.3;
contract Coin {
// The keyword "public" makes variables
@ -112,6 +112,7 @@ registering with a username and password, all you need is an Ethereum keypair.
// Sends an amount of existing coins
// from any caller to an address
function send(address receiver, uint amount) public {
// TODO use an error here
require(amount <= balances[msg.sender], "Insufficient balance.");
balances[msg.sender] -= amount;
balances[receiver] += amount;

View File

@ -5,7 +5,7 @@ Layout of a Solidity Source File
Source files can contain an arbitrary number of
:ref:`contract definitions<contract_structure>`, import_ directives,
:ref:`pragma directives<pragma>` and
:ref:`struct<structs>`, :ref:`enum<enums>`, :ref:`function<functions>`
:ref:`struct<structs>`, :ref:`enum<enums>`, :ref:`function<functions>`, :ref:`error<errors>`
and :ref:`constant variable<constants>` definitions.
.. index:: ! license, spdx

View File

@ -8,7 +8,7 @@ Structure of a Contract
Contracts in Solidity are similar to classes in object-oriented languages.
Each contract can contain declarations of :ref:`structure-state-variables`, :ref:`structure-functions`,
:ref:`structure-function-modifiers`, :ref:`structure-events`, :ref:`structure-struct-types` and :ref:`structure-enum-types`.
:ref:`structure-function-modifiers`, :ref:`structure-events`, :ref:`structure-errors`, :ref:`structure-struct-types` and :ref:`structure-enum-types`.
Furthermore, contracts can inherit from other contracts.
There are also special kinds of contracts called :ref:`libraries<libraries>` and :ref:`interfaces<interfaces>`.
@ -126,6 +126,40 @@ Events are convenience interfaces with the EVM logging facilities.
See :ref:`events` in contracts section for information on how events are declared
and can be used from within a dapp.
.. _structure-errors:
Errors
======
Errors allow you to define descriptive names and data for failure situations.
Errors can be used in :ref:`revert statements <revert-statement>`.
In comparison to string descriptions, errors are much cheaper and allow you
to encode additional data. You can use NatSpec to describe the error to
the user.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
/// Not enough funds for transfer. Requested `requested`,
/// but only `available` available.
error NotEnoughFunds(uint requested, uint available);
contract Token {
mapping(address => uint) balances;
function transfer(address to, uint amount) public {
uint balance = balances[msg.sender];
if (balance < amount)
revert NotEnoughFunds(amount, balance);
balances[msg.sender] -= amount;
balances[to] += amount;
// ...
}
}
See :ref:`errors` in the contracts section for more information.
.. _structure-struct-types:
Struct Types