在本節(jié)中,我們將展示在以太坊上創(chuàng)建一個完全盲目的拍賣合約是多么容易。我們將從公開拍賣開始,每個人都可以看到出價,然后將此合同擴(kuò)展到盲拍,直到投標(biāo)期結(jié)束才能看到實(shí)際出價。
以下簡單拍賣合約的總體思路是,每個人都可以在一個投標(biāo)期間發(fā)送他們的投標(biāo)。出價已經(jīng)包括匯款/以太幣,以便將投標(biāo)人綁定到他們的出價。如果提出最高出價,則先前的最高出價者將收回他們的錢。投標(biāo)期結(jié)束后,必須手動調(diào)用合同讓受益人收到他們的錢——合同無法自行激活。
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; 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); // 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 /// beneficiary address `beneficiaryAddress`. constructor( uint biddingTime, address payable beneficiaryAddress ) { beneficiary = beneficiaryAddress; auctionEndTime = block.timestamp + 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() external 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. if (block.timestamp > auctionEndTime) revert AuctionAlreadyEnded(); // If the bid is not higher, send the // money back (the revert statement // will revert all changes in this // function execution including // it having received the money). if (msg.value <= highestBid) revert BidNotHighEnough(highestBid); 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() external 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; // msg.sender is not of type `address payable` and must be // explicitly converted using `payable(msg.sender)` in order // use the member function `send()`. if (!payable(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() external { // 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 if (block.timestamp < auctionEndTime) revert AuctionNotYetEnded(); if (ended) revert AuctionEndAlreadyCalled(); // 2. Effects ended = true; emit AuctionEnded(highestBidder, highestBid); // 3. Interaction beneficiary.transfer(highestBid); } }
下面將之前的公開拍賣擴(kuò)展到盲拍。盲拍的優(yōu)點(diǎn)是在投標(biāo)期結(jié)束時沒有時間壓力。在透明的計算平臺上創(chuàng)建盲目拍賣可能聽起來很矛盾,但密碼學(xué)可以解決這個問題。
在投標(biāo)期間,投標(biāo)人實(shí)際上并沒有發(fā)送他們的投標(biāo),而只是一個經(jīng)過哈希處理的版本。由于目前認(rèn)為實(shí)際上不可能找到兩個(足夠長的)哈希值相等的值,因此投標(biāo)人承諾以此投標(biāo)。投標(biāo)期結(jié)束后,投標(biāo)人必須公開他們的投標(biāo):他們發(fā)送未加密的值,并且合約檢查哈希值是否與投標(biāo)期間提供的值相同。
另一個挑戰(zhàn)是如何同時使拍賣具有約束力和盲目性:防止投標(biāo)人在贏得拍賣后不發(fā)送資金的唯一方法是讓他們與投標(biāo)一起發(fā)送。由于以太坊中的價值轉(zhuǎn)移不能被蒙蔽,任何人都可以看到價值。
下面的合約通過接受任何大于最高出價的值來解決這個問題。由于這當(dāng)然只能在顯示階段進(jìn)行檢查,因此某些出價可能是無效的,這是故意的(它甚至提供了一個明確的標(biāo)志來放置具有高價值轉(zhuǎn)移的無效出價):投標(biāo)人可以通過放置幾個高或低無效出價。
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; 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); // 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, uint revealTime, address payable beneficiaryAddress ) { beneficiary = beneficiaryAddress; biddingEnd = block.timestamp + 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) external 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[] calldata values, bool[] calldata fakes, bytes32[] calldata secrets ) external onlyAfter(biddingEnd) onlyBefore(revealEnd) { uint length = bids[msg.sender].length; require(values.length == length); require(fakes.length == length); require(secrets.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], fakes[i], secrets[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); } payable(msg.sender).transfer(refund); } /// Withdraw a bid that was overbid. function withdraw() external { 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; payable(msg.sender).transfer(amount); } } /// End the auction and send the highest bid /// to the beneficiary. function auctionEnd() external onlyAfter(revealEnd) { if (ended) revert AuctionEndAlreadyCalled(); emit AuctionEnded(highestBidder, highestBid); ended = true; beneficiary.transfer(highestBid); } // 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; } }
更多建議: