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


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

    先看一个简单的公开拍卖。

    contract SimpleAuction {
    
        //拍卖的受益人
        address payable public beneficiary;
        //拍卖的结束时间
        uint public auctionEndTime;
    
        // 最高出价的人
        address public highestBidder;
        // 最高出价的价格
        uint public highestBid;
    
        // 这个map用来存放出价的人以及对应的出价,便于拍卖结束后退还
        mapping(address => uint) pendingReturns;
    
        //标识拍卖结束了,一旦结束就不能改了
        bool ended;
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    payable表示这个地址可以接收以太币。因为存放的是受益人的地址当然是可以接受以太币的。uctionEndTime是一个时间戳变量,表示拍卖的结束时间。

    // 用来记录当前出价最高的事件
    event HighestBidIncreased(address bidder, uint amount);
    // 用来记录拍卖结束后
    event AuctionEnded(address winner, uint amount);
    
    • 1
    • 2
    • 3
    • 4

    这里是定义两个事件,用来记录状态的变更。

    /// 拍卖已经结束
    error AuctionAlreadyEnded();
        
    /// 已经有更高的出价者了
    error BidNotHighEnough(uint highestBid);
        
    /// 拍卖还未结束
    error AuctionNotYetEnded();
       
    /// auctionEnd 方法已经被调用了
    error AuctionEndAlreadyCalled();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里有几个之前没讲过的知识点,首先是注释用/// 三斜杠,这样的注释可以用来生成文档(natSpec)。这里只是一个简单的示例,还可以定义的很复杂,比如:

    /// @title A simulator for trees
    /// @author Larry A. Gardner
    /// @notice You can use this contract for only the most basic simulation
    /// @dev All function calls are currently implemented without side effects
    /// @custom:experimental This is an experimental contract.
    contract Tree {
        /// @notice Calculate tree age in years, rounded up, for live trees
        /// @dev The Alexandr N. Tetearing algorithm could increase precision
        /// @param rings The number of rings from dendrochronological sample
        /// @return Age in years, rounded up for partial years
        function age(uint256 rings) external virtual pure returns (uint256) {
            return rings + 1;
        }
    
        /// @notice Returns the amount of leaves the tree has.
        /// @dev Returns only a fixed number.
        function leaves() external virtual pure returns(uint256) {
            return 2;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    另外就是error关键字,我们可以用error来定义一个错误,然后当某个条件满足时,我们再用revert关键字报告一个错误,同时错误背后的原因通过natSpec做了解释。后面的代码会看到调用。

    // 通过构造函数初始化受益人和拍卖的结束时间
    constructor(
            uint biddingTime,
            address payable beneficiaryAddress
    ) {
        beneficiary = beneficiaryAddress;
        auctionEndTime = block.timestamp + biddingTime;
    }
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    block.timestamp 向合约提供当前区块的时间戳。

    function bid() external payable {
    
            if (block.timestamp > auctionEndTime)
                revert AuctionAlreadyEnded();
    
            if (msg.value <= highestBid)
                revert BidNotHighEnough(highestBid);
    
            if (highestBid != 0) {
    
                pendingReturns[highestBidder] += highestBid;
            }
            highestBidder = msg.sender;
            highestBid = msg.value;
            emit HighestBidIncreased(msg.sender, msg.value);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    拍卖的核心流程就是这个方法。同样payable表示这个方法可以接收以太币。external则表示这个方法是在外部被调用的,也就是被合约的用户通过接口调用。逻辑上也不复杂,pendingReturns会记录所有出价成功的人(注意不是拍卖成功)和他们的总出价。

    同时,当前最高的出价人和出价会被当成日志记录在以太坊的区块链上,对这个日志感兴趣的人可以监听这个日志做一些事情。

      function withdraw() external returns (bool) {
            uint amount = pendingReturns[msg.sender];
            if (amount > 0) {
                pendingReturns[msg.sender] = 0;
    
                if (!payable(msg.sender).send(amount)) {
                    // No need to call throw here, just reset the amount owing
                    pendingReturns[msg.sender] = amount;
                    return false;
                }
            }
            return true;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    拍卖结束后,没有赢得最终拍卖的那些出价者需要有一个接口进行退款,就是这个withdraw方法。payable(msg.sender).send这句的意思是将以太币发送回调用者的地址。用payable修饰表示这个地址可以接收以太币。

        function auctionEnd() external {
    
            if (block.timestamp < auctionEndTime)
                revert AuctionNotYetEnded();
            if (ended)
                revert AuctionEndAlreadyCalled();
    
            ended = true;
            emit AuctionEnded(highestBidder, highestBid);
            beneficiary.transfer(highestBid);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    调用这个方法可以结束拍卖。前面先进行检查,看是否满足结束的条件。如果满足就更新状态并且记录日志。最后就是把拍卖的钱转给受益人。

    我们把这段程序放在remix运行下,看看效果。

    首先传入一个结束时间和受益人地址进行初始化。

    在这里插入图片描述

    我这里设置的受益人地址说:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

    获取下变量的初始值看看,

    在这里插入图片描述

    看着都没啥问题。我们现在模拟一次出价,点击bid方法,然后value那里我们先默认用0,点击后发现报错:

    [vm]from: 0x5B3...eddC4to: SimpleAuction.bid() 0xD7A...F771Bvalue: 0 weidata: 0x199...8aeeflogs: 0hash: 0x9b7...a19d1
    transact to SimpleAuction.bid errored: VM error: revert.
    
    revert
    	The transaction has been reverted to the initial state.
    Error provided by the contract:
    BidNotHighEnough : 已经有更高的出价者了
    Parameters:
    {
     "highestBid": {
      "value": "0"
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    有没有注意到,我们通过注释的写的error的描述,在错误出现的时候打印出来了。这样验证了我们前面的内容。

    我么首先让用户a(地址是:0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)出价11 wei,然后让用户b (地址是:0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db)出价 20 wei,你会看到出价成功后对应的地址上的余额就会减少(同时也会扣除gas fee)。

    点击拍卖结束后,受益人的地址上余额就会增加。同时我们可以选择对应的没有拍卖成功的出价人的地址,然后点击withdraw,他的出价会返回来。


    参考:

    • https://docs.soliditylang.org/en/v0.8.10/solidity-by-example.html
  • 相关阅读:
    第P8周—YOLOv5-C3模块实现
    Hive增量查询Hudi表
    安捷伦E9321A射频传感器
    cesium 自定义气泡 类
    Aspose.Diagram for .NET 22.11.0 Crack
    【学习笔记】《Python深度学习》第三章:神经网络入门
    python学习笔记12:小数类型的角度到度分秒的转换
    [游戏开发][Unity]安卓出包报错记录
    Docker从零到实战
    c++异常详解
  • 原文地址:https://blog.csdn.net/pony_maggie/article/details/126376165