盲拍智能合约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

posted @ 2022-03-30 09:40  豆豆是只乖狗狗  阅读(239)  评论(0编辑  收藏  举报