solidity入門——拍賣案例二(暗價拍賣)

前言

當下,開發以太坊智能合約使用最多的語言是solidity。這門語言最早由以太坊創世團隊的Gavin Hood設計,後由以太坊技術團隊開發維護。其特性結合了JavaScript、Java、Python等的特點。隨着版本更迭,如今正走在更安全更嚴謹的道路上。

學習智能合約開發,可以從官方文檔開始。戳這裏:solidity官方文檔。文檔的第三章《Solidity by Example》提供了幾個很不錯的案例,這裏我挑有意思的分析註解一下。


背景

這裏分析的第二個合約是《Blind Auction》,該合約不同於上一個拍賣合約。它的意圖是競價者在不知道對手出價多少的情況下拍賣,這種形式有點對拍賣品定價的意思,由定價最高者獲勝。這個合約最大的亮點在於區塊鏈上的轉賬是透明可見的,而合約作者通過一套特殊機制使競拍者出價存在了有真有假的狀態,因此實現了一種特殊的“暗價”拍賣。就如原文所言:

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.

意思是說:

一大挑戰就是在保證拍賣價格的確定性和不可見性。爲了避免獲勝者獲勝後不轉賬,唯一的辦法是在競價時就轉出資金,但以太坊中的轉賬操作都是任何人可見的。

後文的合約解決了這一問題,合約會接受所有比最高價更高的出價,在最後的揭曉階段會驗證這些出價,有些價格可能是假的。這麼做其實是故意設計的(我們還專門加入一個標識來代表假的高價),這樣競價者就可以用一些或高或低的無效價格攪局了。

按照這種拍賣法就可能出現這種情況:A、B是兩個競價者,對於特定拍賣品;

  • 第一輪,A喊200元真價,B喊250元真價;
  • 第二輪,A喊300元真價,B喊400元假價;
  • 第三輪,A喊500元假價,B喊600元假價。

雙方都不叫價後,賣品判給A所有。這樣雙方未能確定對方所出價格的真假,只能憑猜測出價,從而達到了“暗價”拍賣的效果。

代碼

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;

    // 注意:這裏map的值是Bid數組,記錄了出價者的所有出價
    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);

    /// 修改器類似於python的裝飾器,可以改變原函數的內容。
    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;
    }

    /// 一條出價的生成規則: `_blindedBid` = keccak256(abi.encodePacked(value, fake, secret)).
    /// 發送的以太幣只有在揭曉階段被驗證成功纔可以被退回
    /// 出價只有"fake"爲假且調用bid()函數時發送的以太幣大於等於"value"時視爲有效
    /// 置"fake"爲真,並在出價時發送不準確的以太幣可以很好地掩蓋真實出價,同時還能真實價格所需的存款
    /// 同一賬戶可以多次出價
    function bid(bytes32 _blindedBid) public payable
        onlyBefore(biddingEnd)
    {
        bids[msg.sender].push(Bid({
            // 哈希值,匹配則打開報價
            blindedBid: _blindedBid,
            // 報價存進合約賬戶
            deposit: msg.value
        }));
    }

    /// 在揭曉階段,你會說到所有通過驗證且無效的報價
    /// 還有沒能最終獲勝的出價的退款
    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);
    }

    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;
    }

    /// 撤銷被超過了的價格
    function withdraw() public {
        uint amount = pendingReturns[msg.sender];
        if (amount > 0) {
            // (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);
    }
}

總結

本案例的亮點在於奇特的競價模式以及爲了掩蓋出價的真實情況而設計的驗證機制,_blindedBid = keccak256(abi.encodePacked(value, fake, secret))。利用這一點,只有合約知道這次出價的實際價格,而其他競價者看到的或許只是這筆價格的最高價。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章