Use errors in examples.

This commit is contained in:
chriseth 2021-02-08 18:13:28 +01:00
parent 0c1be06cba
commit 786ae2ceec
4 changed files with 128 additions and 63 deletions

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

@ -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.8.3; 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.8.3; pragma solidity ^0.8.4;
contract Coin { contract Coin {
// The keyword "public" makes variables // The keyword "public" makes variables
@ -109,11 +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 {
// TODO use an error here if (amount > balances[msg.sender])
require(amount <= balances[msg.sender], "Insufficient balance."); 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);
@ -201,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