diff --git a/Changelog.md b/Changelog.md index a3ce22a8e..039dffeda 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ Language Features: * Possibility to use ``bytes.concat`` with variable number of ``bytes`` and ``bytesNN`` arguments which behaves as a restricted version of `abi.encodePacked` with a more descriptive name. + * Support custom errors via the ``error`` keyword and introduce the ``revert`` statement. + Compiler Features: * Commandline Interface: Model checker option ``--model-checker-targets`` also accepts ``outOfBounds``. 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/common-patterns.rst b/docs/common-patterns.rst index d0f9e74e3..a7b276661 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -28,7 +28,7 @@ you receive the funds of the person who is now the richest. :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.0 <0.9.0; + pragma solidity ^0.8.4; contract WithdrawalContract { address public richest; @@ -36,13 +36,17 @@ you receive the funds of the person who is now the richest. mapping (address => uint) pendingWithdrawals; + /// The amount of Ether sent was not higher than + /// the currently highest amount. + error NotEnoughEther(); + constructor() payable { richest = msg.sender; mostSent = msg.value; } function becomeRichest() public payable { - require(msg.value > mostSent, "Not enough money sent."); + if (msg.value <= mostSent) revert NotEnoughEther(); pendingWithdrawals[richest] += msg.value; richest = msg.sender; mostSent = msg.value; @@ -62,19 +66,23 @@ This is as opposed to the more intuitive sending pattern: :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.0 <0.9.0; + pragma solidity ^0.8.4; contract SendContract { address payable public richest; uint public mostSent; + /// The amount of Ether sent was not higher than + /// the currently highest amount. + error NotEnoughEther(); + constructor() payable { richest = payable(msg.sender); mostSent = msg.value; } function becomeRichest() public payable { - require(msg.value > mostSent, "Not enough money sent."); + if (msg.value <= mostSent) revert NotEnoughEther(); // This line can cause problems (explained below). richest.transfer(msg.value); richest = payable(msg.sender); @@ -124,7 +132,7 @@ restrictions highly readable. :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.6.0 <0.9.0; + pragma solidity ^0.8.4; contract AccessRestriction { // These will be assigned at the construction @@ -133,6 +141,21 @@ restrictions highly readable. address public owner = msg.sender; uint public creationTime = block.timestamp; + // Now follows a list of errors that + // this contract can generate together + // with a textual explanation in special + // comments. + + /// Sender not authorized for this + /// operation. + error Unauthorized(); + + /// Function called too early. + error TooEarly(); + + /// Not enough Ether sent with function call. + error NotEnoughEther(); + // Modifiers can be used to change // the body of a function. // If this modifier is used, it will @@ -141,10 +164,8 @@ restrictions highly readable. // a certain address. modifier onlyBy(address _account) { - require( - msg.sender == _account, - "Sender not authorized." - ); + if (msg.sender != _account) + revert Unauthorized(); // Do not forget the "_;"! It will // be replaced by the actual function // body when the modifier is used. @@ -161,10 +182,8 @@ restrictions highly readable. } modifier onlyAfter(uint _time) { - require( - block.timestamp >= _time, - "Function called too early." - ); + if (block.timestamp < _time) + revert TooEarly(); _; } @@ -186,10 +205,9 @@ restrictions highly readable. // This was dangerous before Solidity version 0.4.0, // where it was possible to skip the part after `_;`. modifier costs(uint _amount) { - require( - msg.value >= _amount, - "Not enough Ether provided." - ); + if (msg.value < _amount) + revert NotEnoughEther(); + _; if (msg.value > _amount) payable(msg.sender).transfer(msg.value - _amount); @@ -277,7 +295,7 @@ function finishes. :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.4.22 <0.9.0; + pragma solidity ^0.8.4; contract StateMachine { enum Stages { @@ -287,6 +305,8 @@ function finishes. AreWeDoneYet, Finished } + /// Function cannot be called at this time. + error FunctionInvalidAtThisStage(); // This is the current stage. Stages public stage = Stages.AcceptingBlindedBids; @@ -294,10 +314,8 @@ function finishes. uint public creationTime = block.timestamp; modifier atStage(Stages _stage) { - require( - stage == _stage, - "Function cannot be called at this time." - ); + if (stage != _stage) + revert FunctionInvalidAtThisStage(); _; } 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..802b4d171 100644 --- a/docs/examples/blind-auction.rst +++ b/docs/examples/blind-auction.rst @@ -25,7 +25,7 @@ to receive their money - contracts cannot activate themselves. :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.0 <0.9.0; + pragma solidity ^0.8.4; contract SimpleAuction { // Parameters of the auction. Times are either // absolute unix timestamps (seconds since 1970-01-01) @@ -48,10 +48,21 @@ to receive their money - contracts cannot activate themselves. event HighestBidIncreased(address bidder, uint amount); event AuctionEnded(address winner, uint amount); - // The following is a so-called natspec comment, - // recognizable by the three slashes. - // It will be shown when the user is asked to - // confirm a transaction. + // Errors that describe failures. + + // The triple-slash comments are so-called natspec + // comments. They will be shown when the user + // is asked to confirm a transaction or + // when an error is displayed. + + /// The auction has already ended. + error AuctionAlreadyEnded(); + /// There is already a higher or equal bid. + error BidNotHighEnough(uint highestBid); + /// The auction has not ended yet. + error AuctionNotYetEnded(); + /// The function auctionEnd has already been called. + error AuctionEndAlreadyCalled(); /// Create a simple auction with `_biddingTime` /// seconds bidding time on behalf of the @@ -77,20 +88,16 @@ to receive their money - contracts cannot activate themselves. // Revert the call if the bidding // period is over. - require( - block.timestamp <= auctionEndTime, - "Auction already ended." - ); + if (block.timestamp > auctionEndTime) + revert AuctionAlreadyEnded(); // If the bid is not higher, send the - // money back (the failing require + // money back (the revert statement // will revert all changes in this // function execution including // it having received the money). - require( - msg.value > highestBid, - "There already is a higher bid." - ); + if (msg.value <= highestBid) + revert BidNotHighEnough(highestBid); if (highestBid != 0) { // Sending back the money by simply using @@ -140,8 +147,10 @@ to receive their money - contracts cannot activate themselves. // external contracts. // 1. Conditions - require(block.timestamp >= auctionEndTime, "Auction not yet ended."); - require(!ended, "auctionEnd has already been called."); + if (block.timestamp < auctionEndTime) + revert AuctionNotYetEnded(); + if (ended) + revert AuctionEndAlreadyCalled(); // 2. Effects ended = true; @@ -185,7 +194,7 @@ invalid bids. :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.0 <0.9.0; + pragma solidity ^0.8.4; contract BlindAuction { struct Bid { bytes32 blindedBid; @@ -207,12 +216,29 @@ invalid bids. event AuctionEnded(address winner, uint highestBid); - /// Modifiers are a convenient way to validate inputs to - /// functions. `onlyBefore` is applied to `bid` below: - /// The new function body is the modifier's body where - /// `_` is replaced by the old function body. - modifier onlyBefore(uint _time) { require(block.timestamp < _time); _; } - modifier onlyAfter(uint _time) { require(block.timestamp > _time); _; } + // Errors that describe failures. + + /// The function has been called too early. + /// Try again at `time`. + error TooEarly(uint time); + /// The function has been called too late. + /// It cannot be called after `time`. + error TooLate(uint time); + /// The function auctionEnd has already been called. + error AuctionEndAlreadyCalled(); + + // Modifiers are a convenient way to validate inputs to + // functions. `onlyBefore` is applied to `bid` below: + // The new function body is the modifier's body where + // `_` is replaced by the old function body. + modifier onlyBefore(uint _time) { + if (block.timestamp >= _time) revert TooLate(_time); + _; + } + modifier onlyAfter(uint _time) { + if (block.timestamp <= _time) revert TooEarly(_time); + _; + } constructor( uint _biddingTime, @@ -303,7 +329,7 @@ invalid bids. public onlyAfter(revealEnd) { - require(!ended); + if (ended) revert AuctionEndAlreadyCalled(); emit AuctionEnded(highestBidder, highestBid); ended = true; beneficiary.transfer(highestBid); diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst index 6b46007fd..5a24e5a8f 100644 --- a/docs/examples/safe-remote.rst +++ b/docs/examples/safe-remote.rst @@ -26,7 +26,7 @@ you can use state machine-like constructs inside a contract. :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.0 <0.9.0; + pragma solidity ^0.8.4; contract Purchase { uint public value; address payable public seller; @@ -41,27 +41,30 @@ you can use state machine-like constructs inside a contract. _; } + /// Only the buyer can call this function. + error OnlyBuyer(); + /// Only the seller can call this function. + error OnlySeller(); + /// The function cannot be called at the current state. + error InvalidState(); + /// The provided value has to be even. + error ValueNotEven(); + modifier onlyBuyer() { - require( - msg.sender == buyer, - "Only buyer can call this." - ); + if (msg.sender != buyer) + revert OnlyBuyer(); _; } modifier onlySeller() { - require( - msg.sender == seller, - "Only seller can call this." - ); + if (msg.sender != seller) + revert OnlySeller(); _; } modifier inState(State _state) { - require( - state == _state, - "Invalid state." - ); + if (state != _state) + revert InvalidState(); _; } @@ -76,7 +79,8 @@ you can use state machine-like constructs inside a contract. constructor() payable { seller = payable(msg.sender); value = msg.value / 2; - require((2 * value) == msg.value, "Value has to be even."); + if ((2 * value) != msg.value) + revert ValueNotEven(); } /// Abort the purchase and reclaim the ether. diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index ad6503ba1..ecc99812a 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.4; contract Coin { // The keyword "public" makes variables @@ -109,10 +109,20 @@ registering with a username and password, all you need is an Ethereum keypair. balances[receiver] += amount; } + // Errors allow you to provide information about + // why an operation failed. They are returned + // to the caller of the function. + error InsufficientBalance(uint requested, uint available); + // Sends an amount of existing coins // from any caller to an address function send(address receiver, uint amount) public { - require(amount <= balances[msg.sender], "Insufficient balance."); + if (amount > balances[msg.sender]) + revert InsufficientBalance({ + requested: amount, + available: balances[msg.sender] + }); + balances[msg.sender] -= amount; balances[receiver] += amount; emit Sent(msg.sender, receiver, amount); @@ -200,6 +210,14 @@ The :ref:`require ` function call defines conditions that re In this example, ``require(msg.sender == minter);`` ensures that only the creator of the contract can call ``mint``, and ``require(amount < 1e60);`` ensures a maximum amount of tokens. This ensures that there are no overflow errors in the future. +:ref:`Errors ` allow you to provide more information to the caller about +why a condition or operation failed. Errors are used together with the +:ref:`revert statement `. The revert statement unconditionally +aborts and reverts all changes similar to the ``require`` function, but it also +allows you to provide the name of an error and additional data which will be supplied to the caller +(and eventually to the front-end application or block explorer) so that +a failure can more easily be debugged or reacted upon. + The ``send`` function can be used by anyone (who already has some of these coins) to send coins to anyone else. If the sender does not have enough coins to send, the ``require`` call fails and provides the 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