1. 程式人生 > 其它 >盲拍智慧合約solidity程式碼

盲拍智慧合約solidity程式碼

盲拍:

盲拍分為競價階段和最終競價比較階段,在競價期間,競價者並不傳送他們的真實競價,而是一個雜湊資料(bytes32 blindedBid)。

競價期結束後,競價者必須出示他們的競價,即多個真假競價資訊陣列組合,雜湊資料用於幫助合約檢查雜湊值是否與競價期提供的雜湊值相同。

此外,為防止競價者在贏得拍賣後不給錢,唯一方法是讓競價者把錢和出價一起寄出去,由於ether轉賬在以太坊中不能被加密,任何人都可以看到競價。

下文的contract通過接受任何大於最高競價來解決這個問題。由於只能在披露階段進行檢查,一些出價可能是無效的,投標者可以通過放置幾個高或低的無效出價來混淆競爭。

下文程式碼在solidity官方英文文件基礎上新增很多註釋用於幫助加深理解。

pragma solidity ^0.8.4;

//盲拍,Remix編譯
contract BlindAuction{

    struct Bid{
        bytes32 blindedBid;
        uint deposit;
    }

    address payable public beneficiary;//競價受益人
    uint public biddingEnd;//盲拍規定週期
    uint public revealEnd;
    bool public ended;//競價狀態,預設值為false

    //一個競價者對應有多個競價資訊,用bids儲存,其中只有一個為真,其他都為假
    mapping(address => Bid[]) public bids;

    address public highestBidder;//最高競價者地址
    uint public highestBid;//最高競價

    //mapping類似於散列表和字典,只能宣告為狀態變數,不支援迭代,支援巢狀
    mapping(address => uint) pendingReturns;//儲存所有最高競價資訊

    //呼叫event可以將合約中某些內容的更改記錄到日誌(記錄在區塊鏈上),用關鍵字emit修飾呼叫
    //事件(event)和日誌不能在合約內部訪問,可以被子合約呼叫
    event AuctionEnded(address winner, uint highestBid);//記錄當前最高競價者

    error TooEarly(uint time);//標識盲拍週期還未開始
    error TooLate(uint time);//標識盲拍週期已結束
    error AuctionEndAlreadyCalled();//標識盲拍已進行

    //modifier類似於一個可以通用的函式供其他function重複呼叫,減少程式碼量
    //_;可以放在modifier結構體{}內的任何位置來執行呼叫modifier的function程式碼
    //當前時間已錯過盲拍週期
    modifier onlyBefore(uint time){
        if(block.timestamp >= time)
            revert TooLate(time);
        _;
    }

    //盲拍還未開始
    modifier onlyAfter(uint time){
        if(block.timestamp <= time)
            revert TooEarly(time);
            _;
    }

    //建構函式,僅在部署合約時呼叫一次,初始化盲拍受益者地址、盲拍競價週期、
    constructor(uint biddingTime,uint revealTime,address payable beneficiaryAddress){
        beneficiary = beneficiaryAddress;
        biddingEnd = block.timestamp + biddingTime;
        revealEnd = biddingEnd + revealEnd;
    }

    //先判斷當前時間是否在競拍週期內,再將競拍資訊寫入bids
    function bid(bytes32 blindedBid) external payable onlyBefore(biddingEnd){
        //注:msg.sender指呼叫該函式的地址,而不是呼叫整個合約的地址,.push是動態陣列方法
        bids[msg.sender].push(Bid({
            blindedBid:blindedBid,
            deposit:msg.value
        }));
    }

    //calldata用於儲存函式引數,是不可修改、非持久的函式引數儲存區域
    //先判斷該function是否處於正確的盲拍週期內,values[]儲存所有真假競價資訊
    //fakes[]標記values[]中對應資訊的真假,secrets[]僅用於輔助計算加密資訊,無實際意義
    //reveal()用於從多個真假競拍資訊中找出真的並進行轉賬
    function reveal(uint[] calldata values,bool[] calldata fakes,bytes32[] calldata secrets) external 
    onlyAfter(biddingEnd) onlyBefore(revealEnd){
        uint length = bids[msg.sender].length;//記錄呼叫該function的地址的競拍次數

        //require()中判斷條件為true則繼續,為false則退出該function,回退該function內所有更改
        require(values.length == length);//values[]、fakes[]、secrets[]的長度均相等
        require(fakes.length == length);//否則退出該function
        require(secrets.length == length);

        uint refund;//用於儲存真的那個競價資訊

        //keccak256是SHA-3成熟的一種加密演算法
        //遍歷呼叫者的所有競拍資訊,可有多個真的競價資訊
        for(uint i=0;i<length;i++){
            Bid storage bidToCheck = bids[msg.sender][i];//取出呼叫者的第i條競拍資訊
            (uint value,bool fake,bytes32 secret) = (values[i],fakes[i],secrets[i]);//calldata型別資料只讀,不能修改,故需賦值
            
            //abi.encodePacked(...) returns (bytes memory)
            //abi.encodePacked()是solidity中資料打包、連線方法
            //元素之間用逗號連線,支援多種不同資料型別(struct和巢狀陣列除外)
            //keccak256(bytes memory) returns (bytes32)用於加密資料
            if(bidToCheck.blindedBid != keccak256(abi.encodePacked(value,fake,secret))){
                continue;//跳出本次迴圈繼續下一次迴圈
            }
            
            refund += bidToCheck.deposit;//找到真的競價資訊就將競價金額deposit取出

            if(!fake && bidToCheck.deposit >= value){
                if(placeBid(msg.sender,value))
                    refund -= value;
            }//若該條競價資訊為真且該競價者的錢足夠支付,則呼叫placeBid()轉錢並置空refund
            bidToCheck.blindedBid = bytes32(0);//將此條競價標識置為0x00
        }
        //執行到此處refund==0,為防止for迴圈出錯,返還呼叫者競價金額
        payable(msg.sender).transfer(refund);//給呼叫此function的地址轉賬
    }

    //重置盲拍狀態
    function auctionEnd() external onlyAfter(revealEnd){
        if(ended)
            revert AuctionEndAlreadyCalled();//若成立則退出函式,回退已更改狀態
        emit AuctionEnded(highestBidder,highestBid);//記錄最高競價者、最高競價為事件於日誌上
        ended = true;
        beneficiary.transfer(highestBid);//給盲拍受益人轉賬
    }

    //退錢
    function withdraw() external{
        uint amount = pendingReturns[msg.sender];
        if(amount>0){
            pendingReturns[msg.sender] = 0;
            payable(msg.sender).transfer(amount);
        }
    }

    //用於轉帳
    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;
    }

}

來源(solidity官方英文文件0.8.13):https://docs.soliditylang.org/en/v0.8.13/solidity-by-example.html#id2

solidity官方中文文件0.8.0:https://learnblockchain.cn/docs/solidity/solidity-by-example.html#id5

keccak256()官方英文文件介紹:https://docs.soliditylang.org/en/v0.8.13/abi-spec.html#non-standard-packed-mode

abi.encodePacked()官方英文文件介紹:https://docs.soliditylang.org/en/v0.8.13/abi-spec.html#non-standard-packed-mode