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