mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10937 from ethereum/customErrorDocumentation
Custom error documentation
This commit is contained in:
commit
d710e0b3af
@ -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``.
|
||||||
|
@ -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"
|
||||||
|
@ -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."
|
|
||||||
);
|
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
82
docs/contracts/errors.rst
Normal 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.
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user