前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何编写一个拍卖的智能合约-续

如何编写一个拍卖的智能合约-续

作者头像
用户7634691
发布2023-02-24 10:38:51
3150
发布2023-02-24 10:38:51
举报

拍卖的方式有几种,其中有两种概念你需要先了解下,一种是公开拍卖(open auction),一种叫盲拍(blind auction)。简单来讲就是,前一种拍卖大家都能互相看到对方的出价,而后一种则看不到。

上一篇文章我们实现了一个简单的open auction,本篇我们来讨论下如何实现一个blind auction

盲拍有个核心问题就是如何保证数据的安全性,而区块链的加密特性正是解决该问题的关键。

我们实现的思路是这样的,在拍卖期间,竞拍者并不会真正的发送自己的竞价,而是发送一个本次竞价的哈希值版本。因为哈希值本身基本不会重复,所以就可以唯一代表一次竞拍。等待拍卖结束时,在reveal阶段才会公开他们的竞拍。

盲拍另一个需要解决的问题是怎样保证约束力。就是如何防止竞拍人在赢得拍卖后不发送他们的货币,也就是防止他们乱喊价。在公开拍卖的场景是不存在这个问题的,因为公开拍卖是真实的以太币转移,在区块链上是公开的,不可篡改也没法抵赖。

下面这个示例给出了一种解决方案,就是每个人可以多次竞价,同时发送价格和哈希值,哈希值的输入包括一个fake字段,如果fake是false表示这次有效的喊价(当然不一定是最高喊价),fake是true表示本次喊价无效。通过这种方法,即使每次交易都在链上公开了,别人也不知道你哪次竞价有效。

来看下示例代码。

代码语言:javascript
复制
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;

    // 拍卖结束后根据这个map的数据退换其他竞拍者的出价
    mapping(address => uint) pendingReturns;

    event AuctionEnded(address winner, uint highestBid);

接下来定义几个事件,

代码语言:javascript
复制
///调用某个方法太早了,也就是还没到可以调用的时间
error TooEarly(uint time);

///调用某个方法太迟了
error TooLate(uint time);

/// 拍卖结束的方法已经被调用了
error AuctionEndAlreadyCalled();

继续看,

代码语言:javascript
复制
 modifier onlyBefore(uint time) {
        if (block.timestamp >= time) revert TooLate(time);
        _;
    }
    modifier onlyAfter(uint time) {
        if (block.timestamp <= time) revert TooEarly(time);
        _;
    }

modifier是个关键字,我们可以用这个关键字自定义修饰符,修饰符就是类似external,payable这种可以加到变量或者方法前面的关键字。修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。

比如这里的onlyBefore表示传入的时间不能早于当前区块链的时间。下面会看到具体的应用例子。

代码语言:javascript
复制
constructor(
        uint biddingTime,
        uint revealTime,
        address payable beneficiaryAddress
    ) {
        beneficiary = beneficiaryAddress;
        biddingEnd = block.timestamp + biddingTime;
        revealEnd = biddingEnd + revealTime;
    }

这个是构造函数,比较好理解。revealTime指的是最终披露竞价结果的时间。

代码语言:javascript
复制
function bid(bytes32 blindedBid)
        external
        payable
        onlyBefore(biddingEnd)
    {
        bids[msg.sender].push(Bid({
            blindedBid: blindedBid,
            deposit: msg.value
        }));
    }

竞价的核心方法,入参是一个哈希,就是我们前面讲的,盲拍是不公开真正的出价,而是根据出价计算一个哈希结果代替出价。计算方法是:

代码语言:javascript
复制
keccak256(abi.encodePacked(value, fake, secret))

注意这里的fake字段,前面有解释。

方法的逻辑很简单,把出价放入map就可以了。

代码语言:javascript
复制
function reveal(
        uint[] calldata values,
        bool[] calldata fakes,
        bytes32[] calldata secrets
    )
        external
        onlyAfter(biddingEnd)
        onlyBefore(revealEnd)
    {
        uint length = bids[msg.sender].length;
        require(values.length == length);
        require(fakes.length == length);
        require(secrets.length == length);

        uint refund;
        for (uint i = ; i < length; i++) {
            Bid storage bidToCheck = bids[msg.sender][i];
            (uint value, bool fake, bytes32 secret) =
                    (values[i], fakes[i], secrets[i]);
            if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
                // Bid was not actually revealed.
                // Do not refund deposit.
                continue;
            }
            refund += bidToCheck.deposit;
            if (!fake && bidToCheck.deposit >= value) {
                if (placeBid(msg.sender, value))
                    refund -= value;
            }
            // Make it impossible for the sender to re-claim
            // the same deposit.
            bidToCheck.blindedBid = bytes32();
        }
        payable(msg.sender).transfer(refund);
    }

reveal方法是实现盲拍的核心,它是最终披露竞拍结果的方法,这个方法首先有约束时间不能早于竞拍结束的时间,又同时不能晚于披露的时间。这里有个新的东西叫calldata,它表示一个只读的数据入参数,这个好处是我们不用担心这个数据在外部会被修改,在函数内部就可以直接便利数据而不用先复制到内存里。

方法的开始是一段参数检查,调用者传过来的披露数据是三组数组,每组数组的长度必须要和自己在盲拍阶段的出价次数一样(每个人可以出价多次)。也就是说你要揭露的竞价要和之前盲拍阶段喊价的次数一致。

然后是执行一段循环,循环的逻辑其实就是我前面讲的,就是每个人可以多次竞价,需要判断哪次的出价是有效的,如果是有效的再去看看是否是最高出价(placeBid),不是有效的出价要退还给出价的人。

这里用到了一个内部方法,如下:

代码语言:javascript
复制
function placeBid(address bidder, uint value) internal
            returns (bool success)
    {
        if (value <= highestBid) {
            return false;
        }
        if (highestBidder != address()) {
            // Refund the previously highest bidder.
            pendingReturns[highestBidder] += highestBid;
        }
        highestBid = value;
        highestBidder = bidder;
        return true;
    }

这个方法用来判断一个人的出价是否可以作为有效的一次竞拍,如果有效就更新当前的出价信息到highestBid和highestBidder。同时为了能在竞拍结束后退款,也会更新pendingReturns。

代码语言:javascript
复制
    function withdraw() external {
        uint amount = pendingReturns[msg.sender];
        if (amount > ) {
            pendingReturns[msg.sender] = ;

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

退款的方法,这个其实上一篇公开拍卖也讲过,拍卖结束后要把没有赢得竞拍的钱退还回去。

代码语言:javascript
复制
    function auctionEnd()
        external
        onlyAfter(revealEnd)
    {
        if (ended) revert AuctionEndAlreadyCalled();
        emit AuctionEnded(highestBidder, highestBid);
        ended = true;
        beneficiary.transfer(highestBid);
    }

这个也很简单,拍卖结束了,给一些状态置位,把钱拍卖的收益转给受益人。

微信公众号:犀牛的技术笔记


参考:

  • https://docs.soliditylang.org/en/v0.8.10/solidity-by-example.html
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 犀牛的技术笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
区块链
云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档