Merge pull request #10937 from ethereum/customErrorDocumentation

Custom error documentation
This commit is contained in:
chriseth 2021-03-30 23:37:47 +02:00 committed by GitHub
commit d710e0b3af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 368 additions and 90 deletions

View File

@ -2,6 +2,8 @@
Language Features: 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. * 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: Compiler Features:
* Commandline Interface: Model checker option ``--model-checker-targets`` also accepts ``outOfBounds``. * Commandline Interface: Model checker option ``--model-checker-targets`` also accepts ``outOfBounds``.

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 efficient search and arbitrary legibility by defining events with two arguments — one
indexed, one not — intended to hold the same value. 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: .. _abi_json:
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: 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>`); - ``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``. - ``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, For example,
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0; pragma solidity ^0.8.4;
contract Test { contract Test {
constructor() { b = hex"12345678901234567890123456789012"; } constructor() { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b); event Event(uint indexed a, bytes32 b);
event Event2(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); } function foo(uint a) public { emit Event(a, b); }
bytes32 b; bytes32 b;
} }
@ -557,6 +615,10 @@ would result in the JSON:
.. code-block:: json .. code-block:: json
[{ [{
"type":"error",
"inputs": [{"name":"available","type":"uint256"},{"name":"required","type":"uint256"}],
"name":"InsufficientBalance"
}, {
"type":"event", "type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event" "name":"Event"

View File

@ -28,7 +28,7 @@ you receive the funds of the person who is now the richest.
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0; pragma solidity ^0.8.4;
contract WithdrawalContract { contract WithdrawalContract {
address public richest; address public richest;
@ -36,13 +36,17 @@ you receive the funds of the person who is now the richest.
mapping (address => uint) pendingWithdrawals; mapping (address => uint) pendingWithdrawals;
/// The amount of Ether sent was not higher than
/// the currently highest amount.
error NotEnoughEther();
constructor() payable { constructor() payable {
richest = msg.sender; richest = msg.sender;
mostSent = msg.value; mostSent = msg.value;
} }
function becomeRichest() public payable { function becomeRichest() public payable {
require(msg.value > mostSent, "Not enough money sent."); if (msg.value <= mostSent) revert NotEnoughEther();
pendingWithdrawals[richest] += msg.value; pendingWithdrawals[richest] += msg.value;
richest = msg.sender; richest = msg.sender;
mostSent = msg.value; mostSent = msg.value;
@ -62,19 +66,23 @@ This is as opposed to the more intuitive sending pattern:
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0; pragma solidity ^0.8.4;
contract SendContract { contract SendContract {
address payable public richest; address payable public richest;
uint public mostSent; uint public mostSent;
/// The amount of Ether sent was not higher than
/// the currently highest amount.
error NotEnoughEther();
constructor() payable { constructor() payable {
richest = payable(msg.sender); richest = payable(msg.sender);
mostSent = msg.value; mostSent = msg.value;
} }
function becomeRichest() public payable { 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). // This line can cause problems (explained below).
richest.transfer(msg.value); richest.transfer(msg.value);
richest = payable(msg.sender); richest = payable(msg.sender);
@ -124,7 +132,7 @@ restrictions highly readable.
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0; pragma solidity ^0.8.4;
contract AccessRestriction { contract AccessRestriction {
// These will be assigned at the construction // These will be assigned at the construction
@ -133,6 +141,21 @@ restrictions highly readable.
address public owner = msg.sender; address public owner = msg.sender;
uint public creationTime = block.timestamp; 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 // Modifiers can be used to change
// the body of a function. // the body of a function.
// If this modifier is used, it will // If this modifier is used, it will
@ -141,10 +164,8 @@ restrictions highly readable.
// a certain address. // a certain address.
modifier onlyBy(address _account) modifier onlyBy(address _account)
{ {
require( if (msg.sender != _account)
msg.sender == _account, revert Unauthorized();
"Sender not authorized."
);
// Do not forget the "_;"! It will // Do not forget the "_;"! It will
// be replaced by the actual function // be replaced by the actual function
// body when the modifier is used. // body when the modifier is used.
@ -161,10 +182,8 @@ restrictions highly readable.
} }
modifier onlyAfter(uint _time) { modifier onlyAfter(uint _time) {
require( if (block.timestamp < _time)
block.timestamp >= _time, revert TooEarly();
"Function called too early."
);
_; _;
} }
@ -186,10 +205,9 @@ restrictions highly readable.
// This was dangerous before Solidity version 0.4.0, // This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`. // where it was possible to skip the part after `_;`.
modifier costs(uint _amount) { modifier costs(uint _amount) {
require( if (msg.value < _amount)
msg.value >= _amount, revert NotEnoughEther();
"Not enough Ether provided."
);
_; _;
if (msg.value > _amount) if (msg.value > _amount)
payable(msg.sender).transfer(msg.value - _amount); payable(msg.sender).transfer(msg.value - _amount);
@ -277,7 +295,7 @@ function finishes.
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0; pragma solidity ^0.8.4;
contract StateMachine { contract StateMachine {
enum Stages { enum Stages {
@ -287,6 +305,8 @@ function finishes.
AreWeDoneYet, AreWeDoneYet,
Finished Finished
} }
/// Function cannot be called at this time.
error FunctionInvalidAtThisStage();
// This is the current stage. // This is the current stage.
Stages public stage = Stages.AcceptingBlindedBids; Stages public stage = Stages.AcceptingBlindedBids;
@ -294,10 +314,8 @@ function finishes.
uint public creationTime = block.timestamp; uint public creationTime = block.timestamp;
modifier atStage(Stages _stage) { modifier atStage(Stages _stage) {
require( if (stage != _stage)
stage == _stage, revert FunctionInvalidAtThisStage();
"Function cannot be called at this time."
);
_; _;
} }

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/functions.rst
.. include:: contracts/events.rst .. include:: contracts/events.rst
.. include:: contracts/errors.rst
.. include:: contracts/inheritance.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, Solidity also supports exception handling in the form of ``try``/``catch``-statements,
but only for :ref:`external function calls <external-function-calls>` and 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 Parentheses can *not* be omitted for conditionals, but curly braces can be omitted
around single-statement bodies. 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. flags an error to the caller.
When exceptions happen in a sub-call, they "bubble up" (i.e., 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 and the low-level functions ``call``, ``delegatecall`` and
``staticcall``: they return ``false`` as their first return value in case ``staticcall``: they return ``false`` as their first return value in case
of an exception instead of "bubbling up". 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 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. 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 error data that is passed back to the caller
in the form of :ref:`error instances <errors>`.
Exceptions can contain data that is passed back to the caller. The built-in errors ``Error(string)`` and ``Panic(uint256)`` are
This data consists of a 4-byte selector and subsequent :ref:`ABI-encoded<abi>` data. used by special functions, as explained below. ``Error`` is used for "regular" error conditions
The selector is computed in the same way as a function selector, i.e., while ``Panic`` is used for errors that should not be present in bug-free code.
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.
Panic via ``assert`` and Error via ``require`` 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. #. 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. #. 0x51: If you call a zero-initialized variable of internal function type.
The ``require`` function either creates an error of type ``Error(string)`` The ``require`` function either creates an error without any data or
or an error without any error data and it an error of type ``Error(string)``. It
should be used to ensure valid conditions should be used to ensure valid conditions
that cannot be detected until execution time. that cannot be detected until execution time.
This includes conditions on inputs This includes conditions on inputs
or return values from calls to external contracts. 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: 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 you perform an external function call targeting a contract that contains no code.
#. If your contract receives Ether via a public function without #. If your contract receives Ether via a public function without
``payable`` modifier (including the constructor and the fallback function). ``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. which consumed all gas available to the call.
Exceptions that use ``require`` used to consume all gas until before the Metropolis release. Exceptions that use ``require`` used to consume all gas until before the Metropolis release.
.. _revert-statement:
``revert`` ``revert``
---------- ----------
The ``revert`` function is another way to trigger exceptions from within other code blocks to flag an error and A direct revert can be triggered using the ``revert`` statement and the ``revert`` function.
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.
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 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0; pragma solidity ^0.8.4;
contract VendingMachine { contract VendingMachine {
address owner;
error Unauthorized();
function buy(uint amount) public payable { function buy(uint amount) public payable {
if (amount > msg.value / 2 ether) if (amount > msg.value / 2 ether)
revert("Not enough Ether provided."); 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. // 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:: .. note::
The ``require`` function is evaluated just as any other function. The ``require`` function is evaluated just as any other function.

View File

@ -25,7 +25,7 @@ to receive their money - contracts cannot activate themselves.
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0; pragma solidity ^0.8.4;
contract SimpleAuction { contract SimpleAuction {
// Parameters of the auction. Times are either // Parameters of the auction. Times are either
// absolute unix timestamps (seconds since 1970-01-01) // 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 HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount); event AuctionEnded(address winner, uint amount);
// The following is a so-called natspec comment, // Errors that describe failures.
// recognizable by the three slashes.
// It will be shown when the user is asked to // The triple-slash comments are so-called natspec
// confirm a transaction. // 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` /// Create a simple auction with `_biddingTime`
/// seconds bidding time on behalf of the /// 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 // Revert the call if the bidding
// period is over. // period is over.
require( if (block.timestamp > auctionEndTime)
block.timestamp <= auctionEndTime, revert AuctionAlreadyEnded();
"Auction already ended."
);
// If the bid is not higher, send the // If the bid is not higher, send the
// money back (the failing require // money back (the revert statement
// will revert all changes in this // will revert all changes in this
// function execution including // function execution including
// it having received the money). // it having received the money).
require( if (msg.value <= highestBid)
msg.value > highestBid, revert BidNotHighEnough(highestBid);
"There already is a higher bid."
);
if (highestBid != 0) { if (highestBid != 0) {
// Sending back the money by simply using // Sending back the money by simply using
@ -140,8 +147,10 @@ to receive their money - contracts cannot activate themselves.
// external contracts. // external contracts.
// 1. Conditions // 1. Conditions
require(block.timestamp >= auctionEndTime, "Auction not yet ended."); if (block.timestamp < auctionEndTime)
require(!ended, "auctionEnd has already been called."); revert AuctionNotYetEnded();
if (ended)
revert AuctionEndAlreadyCalled();
// 2. Effects // 2. Effects
ended = true; ended = true;
@ -185,7 +194,7 @@ invalid bids.
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0; pragma solidity ^0.8.4;
contract BlindAuction { contract BlindAuction {
struct Bid { struct Bid {
bytes32 blindedBid; bytes32 blindedBid;
@ -207,12 +216,29 @@ invalid bids.
event AuctionEnded(address winner, uint highestBid); event AuctionEnded(address winner, uint highestBid);
/// Modifiers are a convenient way to validate inputs to // Errors that describe failures.
/// functions. `onlyBefore` is applied to `bid` below:
/// The new function body is the modifier's body where /// The function has been called too early.
/// `_` is replaced by the old function body. /// Try again at `time`.
modifier onlyBefore(uint _time) { require(block.timestamp < _time); _; } error TooEarly(uint time);
modifier onlyAfter(uint _time) { require(block.timestamp > _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( constructor(
uint _biddingTime, uint _biddingTime,
@ -303,7 +329,7 @@ invalid bids.
public public
onlyAfter(revealEnd) onlyAfter(revealEnd)
{ {
require(!ended); if (ended) revert AuctionEndAlreadyCalled();
emit AuctionEnded(highestBidder, highestBid); emit AuctionEnded(highestBidder, highestBid);
ended = true; ended = true;
beneficiary.transfer(highestBid); beneficiary.transfer(highestBid);

View File

@ -26,7 +26,7 @@ you can use state machine-like constructs inside a contract.
:: ::
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0; pragma solidity ^0.8.4;
contract Purchase { contract Purchase {
uint public value; uint public value;
address payable public seller; 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() { modifier onlyBuyer() {
require( if (msg.sender != buyer)
msg.sender == buyer, revert OnlyBuyer();
"Only buyer can call this."
);
_; _;
} }
modifier onlySeller() { modifier onlySeller() {
require( if (msg.sender != seller)
msg.sender == seller, revert OnlySeller();
"Only seller can call this."
);
_; _;
} }
modifier inState(State _state) { modifier inState(State _state) {
require( if (state != _state)
state == _state, revert InvalidState();
"Invalid state."
);
_; _;
} }
@ -76,7 +79,8 @@ you can use state machine-like constructs inside a contract.
constructor() payable { constructor() payable {
seller = payable(msg.sender); seller = payable(msg.sender);
value = msg.value / 2; 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. /// Abort the purchase and reclaim the ether.

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 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0; pragma solidity ^0.8.4;
contract Coin { contract Coin {
// The keyword "public" makes variables // 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; 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 // Sends an amount of existing coins
// from any caller to an address // from any caller to an address
function send(address receiver, uint amount) public { 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[msg.sender] -= amount;
balances[receiver] += amount; balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount); emit Sent(msg.sender, receiver, amount);
@ -200,6 +210,14 @@ The :ref:`require <assert-and-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``, 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. and ``require(amount < 1e60);`` ensures a maximum amount of tokens. This ensures that there are no overflow errors in the future.
:ref:`Errors <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 <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 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 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 enough coins to send, the ``require`` call fails and provides the

View File

@ -5,7 +5,7 @@ Layout of a Solidity Source File
Source files can contain an arbitrary number of Source files can contain an arbitrary number of
:ref:`contract definitions<contract_structure>`, import_ directives, :ref:`contract definitions<contract_structure>`, import_ directives,
:ref:`pragma directives<pragma>` and :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. and :ref:`constant variable<constants>` definitions.
.. index:: ! license, spdx .. index:: ! license, spdx

View File

@ -8,7 +8,7 @@ Structure of a Contract
Contracts in Solidity are similar to classes in object-oriented languages. Contracts in Solidity are similar to classes in object-oriented languages.
Each contract can contain declarations of :ref:`structure-state-variables`, :ref:`structure-functions`, 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. Furthermore, contracts can inherit from other contracts.
There are also special kinds of contracts called :ref:`libraries<libraries>` and :ref:`interfaces<interfaces>`. 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 See :ref:`events` in contracts section for information on how events are declared
and can be used from within a dapp. 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: .. _structure-struct-types:
Struct Types Struct Types