diff options
author | Chris Ward <chris.ward@ethereum.org> | 2019-01-14 16:36:47 +0800 |
---|---|---|
committer | Chris Ward <chris.ward@ethereum.org> | 2019-01-14 16:36:47 +0800 |
commit | 7bc1f1a4a3824156c3e315d420d5e6a2047b4822 (patch) | |
tree | 7a01bac7dc47bd886cd0522f517b960238bcecb3 | |
parent | 051df31924e7d10351e9a3abd4becd3631e83391 (diff) | |
download | dexon-solidity-7bc1f1a4a3824156c3e315d420d5e6a2047b4822.tar.gz dexon-solidity-7bc1f1a4a3824156c3e315d420d5e6a2047b4822.tar.zst dexon-solidity-7bc1f1a4a3824156c3e315d420d5e6a2047b4822.zip |
Split blind auction example into seperate doc
-rw-r--r-- | docs/examples/blind-auction.rst | 339 | ||||
-rw-r--r-- | docs/solidity-by-example.rst | 342 |
2 files changed, 340 insertions, 341 deletions
diff --git a/docs/examples/blind-auction.rst b/docs/examples/blind-auction.rst new file mode 100644 index 00000000..70d95ad6 --- /dev/null +++ b/docs/examples/blind-auction.rst @@ -0,0 +1,339 @@ +.. index:: auction;blind, auction;open, blind auction, open auction + +************* +Blind Auction +************* + +In this section, we will show how easy it is to create a +completely blind auction contract on Ethereum. +We will start with an open auction where everyone +can see the bids that are made and then extend this +contract into a blind auction where it is not +possible to see the actual bid until the bidding +period ends. + +.. _simple_auction: + +Simple Open Auction +=================== + +The general idea of the following simple auction contract +is that everyone can send their bids during +a bidding period. The bids already include sending +money / ether in order to bind the bidders to their +bid. If the highest bid is raised, the previously +highest bidder gets her money back. +After the end of the bidding period, the +contract has to be called manually for the +beneficiary to receive their money - contracts cannot +activate themselves. + +:: + + pragma solidity >=0.4.22 <0.6.0; + + contract SimpleAuction { + // Parameters of the auction. Times are either + // absolute unix timestamps (seconds since 1970-01-01) + // or time periods in seconds. + address payable public beneficiary; + uint public auctionEndTime; + + // Current state of the auction. + address public highestBidder; + uint public highestBid; + + // Allowed withdrawals of previous bids + mapping(address => uint) pendingReturns; + + // Set to true at the end, disallows any change. + // By default initialized to `false`. + bool ended; + + // Events that will be emitted on changes. + 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. + + /// Create a simple auction with `_biddingTime` + /// seconds bidding time on behalf of the + /// beneficiary address `_beneficiary`. + constructor( + uint _biddingTime, + address payable _beneficiary + ) public { + beneficiary = _beneficiary; + auctionEndTime = now + _biddingTime; + } + + /// Bid on the auction with the value sent + /// together with this transaction. + /// The value will only be refunded if the + /// auction is not won. + function bid() public payable { + // No arguments are necessary, all + // information is already part of + // the transaction. The keyword payable + // is required for the function to + // be able to receive Ether. + + // Revert the call if the bidding + // period is over. + require( + now <= auctionEndTime, + "Auction already ended." + ); + + // If the bid is not higher, send the + // money back. + require( + msg.value > highestBid, + "There already is a higher bid." + ); + + if (highestBid != 0) { + // Sending back the money by simply using + // highestBidder.send(highestBid) is a security risk + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. + pendingReturns[highestBidder] += highestBid; + } + highestBidder = msg.sender; + highestBid = msg.value; + emit HighestBidIncreased(msg.sender, msg.value); + } + + /// Withdraw a bid that was overbid. + function withdraw() public returns (bool) { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `send` returns. + pendingReturns[msg.sender] = 0; + + if (!msg.sender.send(amount)) { + // No need to call throw here, just reset the amount owing + pendingReturns[msg.sender] = amount; + return false; + } + } + return true; + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() public { + // It is a good guideline to structure functions that interact + // with other contracts (i.e. they call functions or send Ether) + // into three phases: + // 1. checking conditions + // 2. performing actions (potentially changing conditions) + // 3. interacting with other contracts + // If these phases are mixed up, the other contract could call + // back into the current contract and modify the state or cause + // effects (ether payout) to be performed multiple times. + // If functions called internally include interaction with external + // contracts, they also have to be considered interaction with + // external contracts. + + // 1. Conditions + require(now >= auctionEndTime, "Auction not yet ended."); + require(!ended, "auctionEnd has already been called."); + + // 2. Effects + ended = true; + emit AuctionEnded(highestBidder, highestBid); + + // 3. Interaction + beneficiary.transfer(highestBid); + } + } + +Blind Auction +============= + +The previous open auction is extended to a blind auction +in the following. The advantage of a blind auction is +that there is no time pressure towards the end of +the bidding period. Creating a blind auction on a +transparent computing platform might sound like a +contradiction, but cryptography comes to the rescue. + +During the **bidding period**, a bidder does not +actually send her bid, but only a hashed version of it. +Since it is currently considered practically impossible +to find two (sufficiently long) values whose hash +values are equal, the bidder commits to the bid by that. +After the end of the bidding period, the bidders have +to reveal their bids: They send their values +unencrypted and the contract checks that the hash value +is the same as the one provided during the bidding period. + +Another challenge is how to make the auction +**binding and blind** at the same time: The only way to +prevent the bidder from just not sending the money +after they won the auction is to make her send it +together with the bid. Since value transfers cannot +be blinded in Ethereum, anyone can see the value. + +The following contract solves this problem by +accepting any value that is larger than the highest +bid. Since this can of course only be checked during +the reveal phase, some bids might be **invalid**, and +this is on purpose (it even provides an explicit +flag to place invalid bids with high value transfers): +Bidders can confuse competition by placing several +high or low invalid bids. + + +:: + + pragma solidity >0.4.23 <0.6.0; + + contract BlindAuction { + struct Bid { + bytes32 blindedBid; + uint deposit; + } + + address payable public beneficiary; + uint public biddingEnd; + uint public revealEnd; + bool public ended; + + mapping(address => Bid[]) public bids; + + address public highestBidder; + uint public highestBid; + + // Allowed withdrawals of previous bids + mapping(address => uint) pendingReturns; + + 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(now < _time); _; } + modifier onlyAfter(uint _time) { require(now > _time); _; } + + constructor( + uint _biddingTime, + uint _revealTime, + address payable _beneficiary + ) public { + beneficiary = _beneficiary; + biddingEnd = now + _biddingTime; + revealEnd = biddingEnd + _revealTime; + } + + /// Place a blinded bid with `_blindedBid` = + /// keccak256(abi.encodePacked(value, fake, secret)). + /// The sent ether is only refunded if the bid is correctly + /// revealed in the revealing phase. The bid is valid if the + /// ether sent together with the bid is at least "value" and + /// "fake" is not true. Setting "fake" to true and sending + /// not the exact amount are ways to hide the real bid but + /// still make the required deposit. The same address can + /// place multiple bids. + function bid(bytes32 _blindedBid) + public + payable + onlyBefore(biddingEnd) + { + bids[msg.sender].push(Bid({ + blindedBid: _blindedBid, + deposit: msg.value + })); + } + + /// Reveal your blinded bids. You will get a refund for all + /// correctly blinded invalid bids and for all bids except for + /// the totally highest. + function reveal( + uint[] memory _values, + bool[] memory _fake, + bytes32[] memory _secret + ) + public + onlyAfter(biddingEnd) + onlyBefore(revealEnd) + { + uint length = bids[msg.sender].length; + require(_values.length == length); + require(_fake.length == length); + require(_secret.length == length); + + uint refund; + for (uint i = 0; i < length; i++) { + Bid storage bidToCheck = bids[msg.sender][i]; + (uint value, bool fake, bytes32 secret) = + (_values[i], _fake[i], _secret[i]); + if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) { + // Bid was not actually revealed. + // Do not refund deposit. + continue; + } + refund += bidToCheck.deposit; + if (!fake && bidToCheck.deposit >= value) { + if (placeBid(msg.sender, value)) + refund -= value; + } + // Make it impossible for the sender to re-claim + // the same deposit. + bidToCheck.blindedBid = bytes32(0); + } + msg.sender.transfer(refund); + } + + // This is an "internal" function which means that it + // can only be called from the contract itself (or from + // derived contracts). + function placeBid(address bidder, uint value) internal + returns (bool success) + { + if (value <= highestBid) { + return false; + } + if (highestBidder != address(0)) { + // Refund the previously highest bidder. + pendingReturns[highestBidder] += highestBid; + } + highestBid = value; + highestBidder = bidder; + return true; + } + + /// Withdraw a bid that was overbid. + function withdraw() public { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `transfer` returns (see the remark above about + // conditions -> effects -> interaction). + pendingReturns[msg.sender] = 0; + + msg.sender.transfer(amount); + } + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() + public + onlyAfter(revealEnd) + { + require(!ended); + emit AuctionEnded(highestBidder, highestBid); + ended = true; + beneficiary.transfer(highestBid); + } + }
\ No newline at end of file diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 0e7d507d..ddead4d5 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -3,347 +3,7 @@ Solidity by Example ################### .. include:: examples/voting.rst - -.. index:: auction;blind, auction;open, blind auction, open auction - -************* -Blind Auction -************* - -In this section, we will show how easy it is to create a -completely blind auction contract on Ethereum. -We will start with an open auction where everyone -can see the bids that are made and then extend this -contract into a blind auction where it is not -possible to see the actual bid until the bidding -period ends. - -.. _simple_auction: - -Simple Open Auction -=================== - -The general idea of the following simple auction contract -is that everyone can send their bids during -a bidding period. The bids already include sending -money / ether in order to bind the bidders to their -bid. If the highest bid is raised, the previously -highest bidder gets her money back. -After the end of the bidding period, the -contract has to be called manually for the -beneficiary to receive their money - contracts cannot -activate themselves. - -:: - - pragma solidity >=0.4.22 <0.6.0; - - contract SimpleAuction { - // Parameters of the auction. Times are either - // absolute unix timestamps (seconds since 1970-01-01) - // or time periods in seconds. - address payable public beneficiary; - uint public auctionEndTime; - - // Current state of the auction. - address public highestBidder; - uint public highestBid; - - // Allowed withdrawals of previous bids - mapping(address => uint) pendingReturns; - - // Set to true at the end, disallows any change. - // By default initialized to `false`. - bool ended; - - // Events that will be emitted on changes. - 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. - - /// Create a simple auction with `_biddingTime` - /// seconds bidding time on behalf of the - /// beneficiary address `_beneficiary`. - constructor( - uint _biddingTime, - address payable _beneficiary - ) public { - beneficiary = _beneficiary; - auctionEndTime = now + _biddingTime; - } - - /// Bid on the auction with the value sent - /// together with this transaction. - /// The value will only be refunded if the - /// auction is not won. - function bid() public payable { - // No arguments are necessary, all - // information is already part of - // the transaction. The keyword payable - // is required for the function to - // be able to receive Ether. - - // Revert the call if the bidding - // period is over. - require( - now <= auctionEndTime, - "Auction already ended." - ); - - // If the bid is not higher, send the - // money back. - require( - msg.value > highestBid, - "There already is a higher bid." - ); - - if (highestBid != 0) { - // Sending back the money by simply using - // highestBidder.send(highestBid) is a security risk - // because it could execute an untrusted contract. - // It is always safer to let the recipients - // withdraw their money themselves. - pendingReturns[highestBidder] += highestBid; - } - highestBidder = msg.sender; - highestBid = msg.value; - emit HighestBidIncreased(msg.sender, msg.value); - } - - /// Withdraw a bid that was overbid. - function withdraw() public returns (bool) { - uint amount = pendingReturns[msg.sender]; - if (amount > 0) { - // It is important to set this to zero because the recipient - // can call this function again as part of the receiving call - // before `send` returns. - pendingReturns[msg.sender] = 0; - - if (!msg.sender.send(amount)) { - // No need to call throw here, just reset the amount owing - pendingReturns[msg.sender] = amount; - return false; - } - } - return true; - } - - /// End the auction and send the highest bid - /// to the beneficiary. - function auctionEnd() public { - // It is a good guideline to structure functions that interact - // with other contracts (i.e. they call functions or send Ether) - // into three phases: - // 1. checking conditions - // 2. performing actions (potentially changing conditions) - // 3. interacting with other contracts - // If these phases are mixed up, the other contract could call - // back into the current contract and modify the state or cause - // effects (ether payout) to be performed multiple times. - // If functions called internally include interaction with external - // contracts, they also have to be considered interaction with - // external contracts. - - // 1. Conditions - require(now >= auctionEndTime, "Auction not yet ended."); - require(!ended, "auctionEnd has already been called."); - - // 2. Effects - ended = true; - emit AuctionEnded(highestBidder, highestBid); - - // 3. Interaction - beneficiary.transfer(highestBid); - } - } - -Blind Auction -============= - -The previous open auction is extended to a blind auction -in the following. The advantage of a blind auction is -that there is no time pressure towards the end of -the bidding period. Creating a blind auction on a -transparent computing platform might sound like a -contradiction, but cryptography comes to the rescue. - -During the **bidding period**, a bidder does not -actually send her bid, but only a hashed version of it. -Since it is currently considered practically impossible -to find two (sufficiently long) values whose hash -values are equal, the bidder commits to the bid by that. -After the end of the bidding period, the bidders have -to reveal their bids: They send their values -unencrypted and the contract checks that the hash value -is the same as the one provided during the bidding period. - -Another challenge is how to make the auction -**binding and blind** at the same time: The only way to -prevent the bidder from just not sending the money -after they won the auction is to make her send it -together with the bid. Since value transfers cannot -be blinded in Ethereum, anyone can see the value. - -The following contract solves this problem by -accepting any value that is larger than the highest -bid. Since this can of course only be checked during -the reveal phase, some bids might be **invalid**, and -this is on purpose (it even provides an explicit -flag to place invalid bids with high value transfers): -Bidders can confuse competition by placing several -high or low invalid bids. - - -:: - - pragma solidity >0.4.23 <0.6.0; - - contract BlindAuction { - struct Bid { - bytes32 blindedBid; - uint deposit; - } - - address payable public beneficiary; - uint public biddingEnd; - uint public revealEnd; - bool public ended; - - mapping(address => Bid[]) public bids; - - address public highestBidder; - uint public highestBid; - - // Allowed withdrawals of previous bids - mapping(address => uint) pendingReturns; - - 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(now < _time); _; } - modifier onlyAfter(uint _time) { require(now > _time); _; } - - constructor( - uint _biddingTime, - uint _revealTime, - address payable _beneficiary - ) public { - beneficiary = _beneficiary; - biddingEnd = now + _biddingTime; - revealEnd = biddingEnd + _revealTime; - } - - /// Place a blinded bid with `_blindedBid` = - /// keccak256(abi.encodePacked(value, fake, secret)). - /// The sent ether is only refunded if the bid is correctly - /// revealed in the revealing phase. The bid is valid if the - /// ether sent together with the bid is at least "value" and - /// "fake" is not true. Setting "fake" to true and sending - /// not the exact amount are ways to hide the real bid but - /// still make the required deposit. The same address can - /// place multiple bids. - function bid(bytes32 _blindedBid) - public - payable - onlyBefore(biddingEnd) - { - bids[msg.sender].push(Bid({ - blindedBid: _blindedBid, - deposit: msg.value - })); - } - - /// Reveal your blinded bids. You will get a refund for all - /// correctly blinded invalid bids and for all bids except for - /// the totally highest. - function reveal( - uint[] memory _values, - bool[] memory _fake, - bytes32[] memory _secret - ) - public - onlyAfter(biddingEnd) - onlyBefore(revealEnd) - { - uint length = bids[msg.sender].length; - require(_values.length == length); - require(_fake.length == length); - require(_secret.length == length); - - uint refund; - for (uint i = 0; i < length; i++) { - Bid storage bidToCheck = bids[msg.sender][i]; - (uint value, bool fake, bytes32 secret) = - (_values[i], _fake[i], _secret[i]); - if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) { - // Bid was not actually revealed. - // Do not refund deposit. - continue; - } - refund += bidToCheck.deposit; - if (!fake && bidToCheck.deposit >= value) { - if (placeBid(msg.sender, value)) - refund -= value; - } - // Make it impossible for the sender to re-claim - // the same deposit. - bidToCheck.blindedBid = bytes32(0); - } - msg.sender.transfer(refund); - } - - // This is an "internal" function which means that it - // can only be called from the contract itself (or from - // derived contracts). - function placeBid(address bidder, uint value) internal - returns (bool success) - { - if (value <= highestBid) { - return false; - } - if (highestBidder != address(0)) { - // Refund the previously highest bidder. - pendingReturns[highestBidder] += highestBid; - } - highestBid = value; - highestBidder = bidder; - return true; - } - - /// Withdraw a bid that was overbid. - function withdraw() public { - uint amount = pendingReturns[msg.sender]; - if (amount > 0) { - // It is important to set this to zero because the recipient - // can call this function again as part of the receiving call - // before `transfer` returns (see the remark above about - // conditions -> effects -> interaction). - pendingReturns[msg.sender] = 0; - - msg.sender.transfer(amount); - } - } - - /// End the auction and send the highest bid - /// to the beneficiary. - function auctionEnd() - public - onlyAfter(revealEnd) - { - require(!ended); - emit AuctionEnded(highestBidder, highestBid); - ended = true; - beneficiary.transfer(highestBid); - } - } - +.. include:: examples/blind-auction.rst .. index:: purchase, remote purchase, escrow |