前言:
所谓秘密拍卖,就是在拍卖期间别人无法知道你的出价,因此拍卖的所有出价都是由自己决定,不受他人影响。在一定程度上,这样的秘密拍卖更能体现商品的真实价值,因为它避免了真实拍卖中抬价的现象。
秘密拍卖合约:
在solidity官方文档中,给出了一个秘密拍卖合约的代码。由于拍卖的过程需要进行交易,而交易的记录是死死记录在链上的,这不就无法秘密拍卖了吗?
它这里把交易的钱和承诺拍卖的钱进行了区分,也就是说,光从交易的钱中是无法看出真实的叫价。而真实的叫价,连同一个bool值和一个bytes32的秘密值以keccak的形式输入到合约中。因此,外人是无法知道你的真实叫价的。当且仅当拍卖结束,进入公布阶段时,所有人的拍卖才能被揭示。
需要注意的是,所有给了交易钱的人必须执行reveal函数,否则它的钱将永远无法被退回。当所有的人都执行了reveal(揭示)函数,合约会锁定最高价,然后将其他合法的叫价如数退还。倘若你忘记了你的一组(真实叫价,bool,bytes32 secret)值,那你该次的交易金额将永远锁定在合约中。揭示时间一旦过去,佛祖也回天乏力。
由于代码的复杂程度太高,我这里就不做测试了,代码如下:
pragma solidity >&#61;0.7.0 <0.9.0;contract BlindAuction {struct Bid {bytes32 blindedBid;uint deposit;}address payable public beneficiary;uint public biddingEnd;uint public revealEnd;bool public ended;mapping(address &#61;> Bid[]) public bids;address public highestBidder;uint public highestBid;mapping(address &#61;> uint) pendingReturns;event AuctionEnded(address winner, uint highestBid);modifier onlyBefore(uint _time) { require(block.timestamp < _time); _; }modifier onlyAfter(uint _time) { require(block.timestamp > _time); _; }constructor(uint _biddingTime,uint _revealTime,address payable _beneficiary) public {beneficiary &#61; _beneficiary;biddingEnd &#61; block.timestamp &#43; _biddingTime;revealEnd &#61; biddingEnd &#43; _revealTime;}function bid(bytes32 _blindedBid)publicpayableonlyBefore(biddingEnd){bids[msg.sender].push(Bid({blindedBid: _blindedBid,deposit: msg.value}));}function reveal(uint[] memory _values,bool[] memory _fake,bytes32[] memory _secret)publiconlyAfter(biddingEnd)onlyBefore(revealEnd){uint length &#61; bids[msg.sender].length;require(_values.length &#61;&#61; length);require(_fake.length &#61;&#61; length);require(_secret.length &#61;&#61; length);uint refund;for (uint i &#61; 0; i < length; i&#43;&#43;) {Bid storage bid &#61; bids[msg.sender][i];(uint value, bool fake, bytes32 secret) &#61;(_values[i], _fake[i], _secret[i]);if (bid.blindedBid !&#61; keccak256(abi.encodePacked(value, fake, secret))){continue;}refund &#43;&#61; bid.deposit;if (!fake && bid.deposit >&#61; value) {if (placeBid(msg.sender, value))refund -&#61; value;}bid.blindedBid &#61; bytes32(0);}msg.sender.transfer(refund);}function placeBid(address bidder, uint value) internalreturns (bool success){if (value <&#61; highestBid) {return false;}if (highestBidder !&#61; address(0)) {pendingReturns[highestBidder] &#43;&#61; highestBid;}highestBid &#61; value;highestBidder &#61; bidder;return true;}function withdraw() public {uint amount &#61; pendingReturns[msg.sender];if (amount > 0) {pendingReturns[msg.sender] &#61; 0;msg.sender.transfer(amount);}}function auctionEnd()publiconlyAfter(revealEnd){require(!ended);emit AuctionEnded(highestBidder, highestBid);ended &#61; true;beneficiary.transfer(highestBid);}
}
如上图&#xff0c;其实大家可能在很多地方都见过这段代码&#xff0c;但真的理解了吗&#xff1f;如果理解了&#xff0c;真的在remix上进行过测试了吗&#xff1f;只有亲手测试&#xff0c;才能看出一些问题。
我在这里提醒几个点
1. 在本地进行keccak256(value, fake, secret)测试时&#xff0c;要知道这里的value是以wei为单位的&#xff0c;我们一般都用ether进行测试&#xff0c;因为这样可以忽略掉gas。因此这里的value若以ether为单位&#xff0c;则要进行换算&#xff1a;1ether &#61; 10^18 wei
2. 在本地做测试的时候&#xff0c;biddingtime和revealtime一定要调整好&#xff0c;如果太短则没有充足的时间测试&#xff0c;如果太长则需要等待一大段时间出结果
接下来&#xff0c;我们还是说一下这个合约中函数的功能
constructor&#xff1a;
构造函数&#xff0c;设定竞拍时间和揭示时间
bid:
规定在竞拍结束前&#xff0c;每个人可以多次竞拍&#xff0c;每次竞拍存入一个哈希和交易值
reveal&#xff1a;
规定在竞拍结束后&#xff0c;揭示结束前&#xff0c;每个人一般情况下必须进行揭示。倘若不进行揭示非但自己的叫价不会被记录&#xff0c;更重要的是自己的钱绝对拿不回来&#xff01;
你需要输入的是你n次竞拍的真实信息&#xff0c;每次竞拍包括value&#xff0c;fake&#xff0c;secret&#xff0c;当且仅当你的这三个信息的keccak256与你之前输入的keccak256完全一致&#xff0c;你才可能会有退钱。同时&#xff0c;倘若你出的是最高价&#xff0c;将不会退钱。
这里其实是有点问题的&#xff0c;这就需要在所有人执行reveal两次。第一次是选取最高价&#xff0c;第二次是保证所有人都能退钱&#xff0c;可是&#xff0c;倘若有节点一直等待到最后一时刻才进行第一次reveal&#xff0c;那么它这种行为可能就会坑到人。
比如说&#xff0c;当前的最高价为99&#xff0c;出价人为A。然后&#xff0c;等到揭示的最后一时刻&#xff0c;出价人B第一次执行reveal&#xff0c;它的出价为100&#xff0c;那么B将成为最高出价人&#xff0c;同时&#xff0c;揭示阶段结束。这时候&#xff0c;A欲哭无泪&#xff0c;因为它既没有拍到&#xff0c;也取不出钱了。因此最稳妥的做法就是在reveal期间不停地调用reveal函数&#xff0c;防止被人坑。然而&#xff0c;这是很浪费资源的。
placeBid&#xff1a;
reveal调用地internal函数&#xff0c;负责记录了最高价以及出价人&#xff0c;同时将非最高价的钱转移到pendingReturn三种
withdraw&#xff1a;
无时间限制&#xff0c;可以任何时候取回自己失效的资金。前提是要通过reveal进行。并且&#xff0c;一定是曾经做过最高价的人才会有机会出动pendingRuturns&#xff0c;否则它的钱在reveal阶段就已经返还了。
autionEnd&#xff1a;
当揭示阶段结束&#xff0c;该函数负责将最高价者出的钱转移到受益人的账户上
总结&#xff1a;
这个办法的确做到了隐藏竞拍价格的目的&#xff0c;但reveal阶段非常麻烦&#xff0c;需要频繁reveal保证自己的曾经的最高资金在被别人盖掉后还有机会落入pendingreturns。这点可能是开发团队疏忽的。
大家如果有什么想法或者问题&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流&#xff0c;共同进步&#xff01;