diff options
Diffstat (limited to 'docs/examples/blind-auction.rst')
-rw-r--r-- | docs/examples/blind-auction.rst | 339 |
1 files changed, 339 insertions, 0 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 |