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))。利用这一点,只有合约知道这次出价的实际价格,而其他竞价者看到的或许只是这笔价格的最高价。

你可能感兴趣的:(智能合约开发)