diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 63a203592..e508e3a57 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -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 `) or ``"fallback"`` (the :ref:`"default" 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" diff --git a/docs/contracts.rst b/docs/contracts.rst index 1426bfd16..aee26c95a 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -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 diff --git a/docs/contracts/errors.rst b/docs/contracts/errors.rst new file mode 100644 index 000000000..3ae9a57f1 --- /dev/null +++ b/docs/contracts/errors.rst @@ -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 ` +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 `. +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 ` 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` 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. \ No newline at end of file diff --git a/docs/control-structures.rst b/docs/control-structures.rst index ed19dd663..213cb3c05 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -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 ` and -contract creation calls. +contract creation calls. Errors can be created using the :ref:`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` 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 `. +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. diff --git a/docs/examples/blind-auction.rst b/docs/examples/blind-auction.rst index 2513a275b..2e0d57c0e 100644 --- a/docs/examples/blind-auction.rst +++ b/docs/examples/blind-auction.rst @@ -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; diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index ad6503ba1..24189a9c3 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -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; diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index 202ba1d9b..07c5c7ad5 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -5,7 +5,7 @@ Layout of a Solidity Source File Source files can contain an arbitrary number of :ref:`contract definitions`, import_ directives, :ref:`pragma directives` and -:ref:`struct`, :ref:`enum`, :ref:`function` +:ref:`struct`, :ref:`enum`, :ref:`function`, :ref:`error` and :ref:`constant variable` definitions. .. index:: ! license, spdx diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index e7af9a811..ae6f41e02 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -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` and :ref:`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 `. +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