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

View File

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

View File

@ -26,7 +26,7 @@ you can use state machine-like constructs inside a contract.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
pragma solidity ^0.8.4;
contract Purchase {
uint public value;
address payable public seller;
@ -41,27 +41,30 @@ you can use state machine-like constructs inside a contract.
_;
}
/// Only the buyer can call this function.
error OnlyBuyer();
/// Only the seller can call this function.
error OnlySeller();
/// The function cannot be called at the current state.
error InvalidState();
/// The provided value has to be even.
error ValueNotEven();
modifier onlyBuyer() {
require(
msg.sender == buyer,
"Only buyer can call this."
);
if (msg.sender != buyer)
revert OnlyBuyer();
_;
}
modifier onlySeller() {
require(
msg.sender == seller,
"Only seller can call this."
);
if (msg.sender != seller)
revert OnlySeller();
_;
}
modifier inState(State _state) {
require(
state == _state,
"Invalid state."
);
if (state != _state)
revert InvalidState();
_;
}
@ -76,7 +79,8 @@ you can use state machine-like constructs inside a contract.
constructor() payable {
seller = payable(msg.sender);
value = msg.value / 2;
require((2 * value) == msg.value, "Value has to be even.");
if ((2 * value) != msg.value)
revert ValueNotEven();
}
/// Abort the purchase and reclaim the ether.

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
pragma solidity ^0.8.3;
pragma solidity ^0.8.4;
contract Coin {
// 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;
}
// Errors allow you to provide information about
// why an operation failed. They are returned
// to the caller of the function.
error InsufficientBalance(uint requested, uint available);
// Sends an amount of existing coins
// from any caller to an address
function send(address receiver, uint amount) public {
// TODO use an error here
require(amount <= balances[msg.sender], "Insufficient balance.");
if (amount > balances[msg.sender])
revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
@ -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``,
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
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