[solidity]盲拍合約

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

/**
BlindAuction合約是基于上一份SampleAuction的擴(kuò)展,在公開(kāi)競(jìng)標(biāo)的基礎(chǔ),增加隱藏競(jìng)標(biāo)價(jià)格的功能
由于合約是公開(kāi)透明的,并且每個(gè)人每次發(fā)送競(jìng)標(biāo)‘金額’都可以通過(guò)區(qū)塊鏈直接查看value金額,那怎么做到可以隱藏大家的‘競(jìng)標(biāo)價(jià)’?
隱藏競(jìng)標(biāo)價(jià),則代表隱藏了調(diào)用合約競(jìng)標(biāo)時(shí)發(fā)送的value,那么怎么確保他們贏得拍賣之后付款?
解決此問(wèn)題使用了“加密付款金額”和“訂金”的模式
調(diào)用者發(fā)起競(jìng)拍時(shí),先把自己的實(shí)際競(jìng)投“金額”使用hash加密,用blindedBid參數(shù)記錄,然后實(shí)際發(fā)送到合約的eth則作為“訂金”
在競(jìng)投結(jié)束時(shí),用戶則發(fā)送解密的valueString、和密鑰,如果blindedBid = hash('valueString','密鑰')則代表valueString是真實(shí)的‘競(jìng)拍價(jià)‘
最后,如果贏得拍賣,可退回金額 = 訂金 - 競(jìng)拍價(jià),競(jìng)拍失敗的用戶,則全款可退回
部署地址 https://rinkeby.etherscan.io/tx/0x9c023537bb1038ba2f7e639ec4184010c9e32067ca4734d2e33bf5c063d3fff9
 */
contract BlindAuction {

    // 用戶的競(jìng)標(biāo)結(jié)構(gòu)體
    struct Bid {
        bytes32 blindedBid; // 加密后的競(jìng)標(biāo)價(jià)
        uint deposit; // 訂金
    }

    address payable public beneficiary; // 受益人
    uint public biddingEnd; // 拍賣結(jié)束時(shí)間
    uint public revealEnd; // 拍賣結(jié)束后,公開(kāi)實(shí)際競(jìng)標(biāo)金額的時(shí)間
    bool public ended; // 拍賣時(shí)候結(jié)束

    mapping(address => Bid[]) public bids; // 通過(guò)用戶address映射他的出價(jià),出價(jià)用數(shù)組表示,可以出價(jià)多次

    address public highestBidder; // 最高出價(jià)人的address
    uint public highestBid; // 最高出價(jià)

    mapping(address => uint) pendingReturns; // 通過(guò)用戶address映射他的可退金額

    event AuctionEnded(address winner, uint amount); // 通知外部,整場(chǎng)競(jìng)拍活動(dòng)結(jié)束

    // 通過(guò)下列Error返回,告知調(diào)用者執(zhí)行出錯(cuò)
    /// 函數(shù)末開(kāi)放調(diào)用,請(qǐng)?jiān)赻time`之后在嘗試
    error TooEarly(uint time);
    /// 函數(shù)不允許在`time`之后調(diào)用
    error TooLate(uint time);
    /// 拍賣已結(jié)束
    error AuctionEndAlreadyCalled();

    // Modify修飾符是驗(yàn)證函數(shù)輸入的便捷方法
    // 可以理解為先驗(yàn)證Modify里面的條件,通過(guò)之后,再執(zhí)行舊函數(shù)的代碼塊,’_‘就代表舊函數(shù)的代碼塊

    // 檢查block.timestamp當(dāng)前時(shí)間是否在`time`之前
    modifier onlyBefore(uint time) {
        if (block.timestamp >= time) revert TooLate(time);
        _;
    }
    // 檢查block.timestamp當(dāng)前時(shí)間是否在`time`之后
    modifier onlyAfter(uint time) {
        if (block.timestamp <= time) revert TooEarly(time);
        _;
    }

    // 
    constructor (
        uint biddingTime, // 拍賣持續(xù)時(shí)間(秒為單位)
        uint revealTime, // 拍賣結(jié)束后,有多小時(shí)間可以解謎自己的實(shí)際競(jìng)投金額(秒為單位)
        address payable beneficiaryAddress // 結(jié)束后,受益人地址
    ) {
        beneficiary = beneficiaryAddress;
        biddingEnd = block.timestamp + biddingTime;
        revealEnd = biddingEnd + revealTime;
    }

    /// 競(jìng)拍時(shí)使用加密后的盲價(jià),`blindedBid` = keccak256(abi.encodePacked(value, fake, secret)).
    /// 只有在拍賣公開(kāi)大家競(jìng)標(biāo)價(jià)的reveal階段,正確的輸入以上加密的值(包括value, fake, secret),解謎成功,才能發(fā)起’提現(xiàn)‘退款
    /// 由于每個(gè)競(jìng)拍者都可以發(fā)起多次競(jìng)拍,只有當(dāng)fake是true的時(shí)候,才會(huì)當(dāng)作有效出價(jià)
    /// fake作用是給調(diào)用者多次出價(jià),從而可以隱藏自己的真實(shí)出價(jià)
    function bid(bytes32 blindedBid) 
        external
        payable
        onlyBefore(biddingEnd) // Modify修飾符
    {
        bids[msg.sender].push(Bid({
            blindedBid: blindedBid, // 加密后的實(shí)際競(jìng)投金額
            deposit: msg.value // 實(shí)際收到的ETH則當(dāng)做訂金
        }));
    }

    /// 公開(kāi)你的真實(shí)競(jìng)拍價(jià),對(duì)于所有正確驗(yàn)證,但無(wú)效的出價(jià),都會(huì)獲得退款
    function revael(
        uint[] calldata values, // 多次出價(jià)的實(shí)際value數(shù)組
        bool[] calldata fakes, // 多次出價(jià)的fake數(shù)組,fake表示那次出價(jià)有效
        bytes32[] calldata secrets // 解謎密鑰數(shù)組
    )  
        external
        onlyAfter(biddingEnd) // Modify修飾符 biddingEnd時(shí)間之后
        onlyBefore(revealEnd) // Modify修飾符 revealEnd時(shí)間之前
    {
        // length是用戶出價(jià)次數(shù)
        uint length = bids[msg.sender].length;
        // 校驗(yàn)數(shù)組和出價(jià)次數(shù)是否匹配
        require(values.length == length);
        require(fakes.length == length);
        require(secrets.length == length);

        // 可退回金額
        uint refund;
        
        // 遍歷,校驗(yàn)拍賣階段加密過(guò)的出價(jià),和解密后的值是否匹配,匹配則出價(jià)有效
        // 并且計(jì)算可退回金額refund
        for (uint i =0; i < length; i++) {
            // 獲取用戶在bids數(shù)組的歷史競(jìng)價(jià)bid對(duì)象出來(lái)
            Bid storage bidToCheck = bids[msg.sender][i]; 
            // 獲取對(duì)應(yīng)index的值
            (uint value, bool fake, bytes32 secret) = (values[i], fakes[i], secrets[i]);
            
            // 校驗(yàn)加密的blindedBid和用戶提供的元素加密后是否匹配
            if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
                 // 如果不正確則無(wú)法退回訂金,進(jìn)行下一輪校驗(yàn)
                 continue;
            }

            // 來(lái)到這里 - 代表校驗(yàn)通過(guò)
            // 記錄退款額度為當(dāng)初預(yù)繳的訂金
            refund += bidToCheck.deposit;
            // 判斷預(yù)繳訂金,是否比value出價(jià)高,并且fake為true,則代表真實(shí)出價(jià)
            if (!fake && bidToCheck.deposit > value) {
                // 判斷是否最高出價(jià)
                if (placeBid(msg.sender, value)) {
                    // 則訂金 - 投標(biāo)價(jià)
                    refund -= value;
                }
            }
            // 把盲拍金額數(shù)據(jù)清0,防止重復(fù)提款
            bidToCheck.blindedBid = bytes32(0);
        }
        // 退回?zé)o效出價(jià)
        payable(msg.sender).transfer(refund);
    }

    /// 曾經(jīng)成為最高價(jià)的競(jìng)拍者,但是最后落選,可以通過(guò)此函數(shù)取出競(jìng)拍金額
    function withdraw() external {
        uint amount = pendingReturns[msg.sender];
        if (amount > 0) {
            
            // 先將可回退金額設(shè)置為0,防止在send()過(guò)程中,用戶重復(fù)提款,又進(jìn)入到此提款判斷
            // 記住應(yīng)該遵從 1.條件判斷 -> 2.修改狀態(tài) 3.與合約交互or轉(zhuǎn)錢
            pendingReturns[msg.sender] = 0;

            payable(msg.sender).transfer(amount);
        }
    }

    /// 結(jié)束整個(gè)競(jìng)拍活動(dòng),并向受益人轉(zhuǎn)賬
    function auctionEnd() 
        external
        onlyAfter(revealEnd) 
    {
        // 判斷整個(gè)競(jìng)拍活動(dòng)是否已結(jié)束
        if (ended) revert AuctionEndAlreadyCalled();
        // 告知獲勝競(jìng)拍人address和amount
        emit AuctionEnded(highestBidder, highestBid);
        // 修改狀態(tài)
        ended = true;
        // 向受益人轉(zhuǎn)賬
        beneficiary.transfer(highestBid);
    }

    // ’internal’關(guān)鍵字
    // 意思是,這個(gè)函數(shù)屬于內(nèi)部可以訪問(wèn)的函數(shù),即只能本合約(或繼承的合約)調(diào)用
    // 此函數(shù)是對(duì)比當(dāng)前最高價(jià),并記錄
    function placeBid(address bidder, uint value) internal returns (bool success){
        
        // 對(duì)比最高價(jià)
        if (value < highestBid) {
            return false;
        }
        // 之前知否有最高出價(jià)人
        if (highestBidder != address(0)) {
            // 有則把之前最高出價(jià)人的address和amount記錄在pendingReturns,到時(shí)“提現(xiàn)“時(shí)使用
            pendingReturns[highestBidder] += highestBid;
        }
        // 記錄最高價(jià)和最高出價(jià)人address
        highestBidder = bidder;
        highestBid = value;
        return true;
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容