前言:
所謂秘密拍賣,就是在拍賣期間別人無法知道你的出價,因此拍賣的所有出價都是由自己決定,不受他人影響,在一定程度上,這樣的秘密拍賣更能體現商品的真實價值,因為它避免了真實拍賣中抬價的現象,
秘密拍賣合約:
在solidity官方檔案中,給出了一個秘密拍賣合約的代碼,由于拍賣的程序需要進行交易,而交易的記錄是死死記錄在鏈上的,這不就無法秘密拍賣了嗎?
它這里把交易的錢和承諾拍賣的錢進行了區分,也就是說,光從交易的錢中是無法看出真實的叫價,而真實的叫價,連同一個bool值和一個bytes32的秘密值以keccak的形式輸入到合約中,因此,外人是無法知道你的真實叫價的,當且僅當拍賣結束,進入公布階段時,所有人的拍賣才能被揭示,
需要注意的是,所有給了交易錢的人必須執行reveal函式,否則它的錢將永遠無法被退回,當所有的人都執行了reveal(揭示)函式,合約會鎖定最高價,然后將其他合法的叫價如數退還,倘若你忘記了你的一組(真實叫價,bool,bytes32 secret)值,那你該次的交易金額將永遠鎖定在合約中,揭示時間一旦過去,佛祖也回天乏力,
由于代碼的復雜程度太高,我這里就不做測驗了,代碼如下:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=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 => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// 可以取回的之前的出價
mapping(address => uint) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
/// 使用 modifier 可以更便捷的校驗函式的入參,
/// `onlyBefore` 會被用于后面的 `bid` 函式:
/// 新的函式體是由 modifier 本身的函式體,并用原函式體替換 `_;` 陳述句來組成的,
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 = _beneficiary;
biddingEnd = block.timestamp + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
/// 可以通過 `_blindedBid` = keccak256(value, fake, secret)
/// 設定一個秘密競拍,
/// 只有在出價披露階段被正確披露,已發送的以太幣才會被退還,
/// 如果與出價一起發送的以太幣至少為 “value” 且 “fake” 不為真,則出價有效,
/// 將 “fake” 設定為 true ,然后發送滿足訂金金額但又不與出價相同的金額是隱藏實際出價的方法,
/// 同一個地址可以放置多個出價,
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 bid = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(_values[i], _fake[i], _secret[i]);
if (bid.blindedBid != keccak256(abi.encodePacked(value, fake, secret))){
// 出價未能正確披露
// 不返還訂金
continue;
}
refund += bid.deposit;
if (!fake && bid.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// 使發送者不可能再次認領同一筆訂金
bid.blindedBid = bytes32(0);
}
msg.sender.transfer(refund);
}
// 這是一個 "internal" 函式, 意味著它只能在本合約(或繼承合約)內被呼叫
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// 返還之前的最高出價
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
/// 取回出價(當該出價已被超越)
function withdraw() public {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// 這里很重要,首先要設零值,
// 因為,作為接收呼叫的一部分,
// 接收者可以在 `transfer` 回傳之前重新呼叫該函式,(可查看上面關于‘條件 -> 影響 -> 互動’的標注)
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
/// 結束拍賣,并把最高的出價發送給受益人
function auctionEnd()
public
onlyAfter(revealEnd)
{
require(!ended);
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
}
如上圖,其實大家可能在很多地方都見過這段代碼,但真的理解了嗎?如果理解了,真的在remix上進行過測驗了嗎?只有親手測驗,才能看出一些問題,
我在這里提醒幾個點
1. 在本地進行keccak256(value, fake, secret)測驗時,要知道這里的value是以wei為單位的,我們一般都用ether進行測驗,因為這樣可以忽略掉gas,因此這里的value若以ether為單位,則要進行換算:1ether = 10^18 wei
2. 在本地做測驗的時候,biddingtime和revealtime一定要調整好,如果太短則沒有充足的時間測驗,如果太長則需要等待一大段時間出結果
接下來,我們還是說一下這個合約中函式的功能
constructor:
建構式,設定競拍時間和揭示時間
bid:
規定在競拍結束前,每個人可以多次競拍,每次競拍存入一個哈希和交易值
reveal:
規定在競拍結束后,揭示結束前,每個人一般情況下必須進行揭示,倘若不進行揭示非但自己的叫價不會被記錄,更重要的是自己的錢絕對拿不回來!
你需要輸入的是你n次競拍的真實資訊,每次競拍包括value,fake,secret,當且僅當你的這三個資訊的keccak256與你之前輸入的keccak256完全一致,你才可能會有退錢,同時,倘若你出的是最高價,將不會退錢,
這里其實是有點問題的,這就需要在所有人執行reveal兩次,第一次是選取最高價,第二次是保證所有人都能退錢,可是,倘若有節點一直等待到最后一時刻才進行第一次reveal,那么它這種行為可能就會坑到人,
比如說,當前的最高價為99,出價人為A,然后,等到揭示的最后一時刻,出價人B第一次執行reveal,它的出價為100,那么B將成為最高出價人,同時,揭示階段結束,這時候,A欲哭無淚,因為它既沒有拍到,也取不出錢了,因此最穩妥的做法就是在reveal期間不停地呼叫reveal函式,防止被人坑,然而,這是很浪費資源的,
placeBid:
reveal呼叫地internal函式,負責記錄了最高價以及出價人,同時將非最高價的錢轉移到pendingReturn三種
withdraw:
無時間限制,可以任何時候取回自己失效的資金,前提是要通過reveal進行,并且,一定是曾經做過最高價的人才會有機會出動pendingRuturns,否則它的錢在reveal階段就已經返還了,
autionEnd:
當揭示階段結束,該函式負責將最高價者出的錢轉移到受益人的賬戶上
總結:
這個辦法的確做到了隱藏競拍價格的目的,但reveal階段非常麻煩,需要頻繁reveal保證自己的曾經的最高資金在被別人蓋掉后還有機會落入pendingreturns,這點可能是開發團隊疏忽的,
大家如果有什么想法或者問題,歡迎在評論區留言,我們一起交流,共同進步!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/274112.html
標籤:區塊鏈
