以太猫合约之拍卖(四)

由于以太猫合约遵循的是ERC721标准的合约协议,所以每只猫的价值都不一样。在以太猫的游戏中,每只猫的价值是通过拍卖来体现的。在日常生活中,拍卖都是从从低价到最高价拍卖的,而在以太猫的游戏 合约中,拍卖限定了最高价、最低价和拍卖时间,竞拍者是从高价到低价进行竞拍的,这样节约了由于大量低价竞拍最后没有成交造成gas浪费和网络堵塞。接下来就重点来分析相关代码。

拍卖合约(基础接口)

// 拍卖合约,包含了模型、变量、以及拍卖的内部函数
contract ClockAuctionBase {
   // 拍卖的结构体
    struct Auction {
        address seller;             // 代币的拥有者
        uint128 startingPrice;      // 起始拍卖价格    
        uint128 endingPrice;        // 限定最低的拍卖价格       
        uint64 duration;            // 拍卖的存续期(拍卖时间,一天,一个月)    
        uint64 startedAt;           // 拍卖开始时间,startedAt为0时,标识拍卖结束了
    }

    ERC721 public nonFungibleContract;   // ERC721 接口类
    uint256 public ownerCut;             // 手续费百分比,0%-100%

    mapping (uint256 => Auction) tokenIdToAuction;  // ERC721代币对应的拍卖结构体

    event AuctionCreated(uint256 tokenId, uint256 startingPrice, uint256 endingPrice, uint256 duration);   // 拍卖创建事件
    event AuctionSuccessful(uint256 tokenId, uint256 totalPrice, address winner);                          // 拍卖成功事件
    event AuctionCancelled(uint256 tokenId);                                                               // 拍卖移除事件 

    // 判断代币的拥有者是否为_claimant
    function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
        return (nonFungibleContract.ownerOf(_tokenId) == _claimant);
    }

    
    function _escrow(address _owner, uint256 _tokenId) internal {
        // 相当于先把我的代币转移给了本合约,由本合约先管理我的代币(托管)
        nonFungibleContract.transferFrom(_owner, this, _tokenId);
    }

    // 通过调用ERC721接口中的transfer方法,实现了代币的转移
    function _transfer(address _receiver, uint256 _tokenId) internal {
        nonFungibleContract.transfer(_receiver, _tokenId);
    }

    // 增加了一个新的拍卖
    function _addAuction(uint256 _tokenId, Auction _auction) internal {  
        // _tokenId  要拍卖的代币的id;_auction 拍卖的结构体
        require(_auction.duration >= 1 minutes);   // 拍卖的时间必须要大于1分钟
        tokenIdToAuction[_tokenId] = _auction;     // 绑定 代币ID 和 拍卖 

        AuctionCreated(                            // 触发一个拍卖创建事件
            uint256(_tokenId),
            uint256(_auction.startingPrice),
            uint256(_auction.endingPrice),
            uint256(_auction.duration)
        );
    }

    // 退出拍卖
    function _cancelAuction(uint256 _tokenId, address _seller) internal {
        _removeAuction(_tokenId);       // 移除拍卖
        _transfer(_seller, _tokenId);   // 将代币返回给拍卖者,之前是托管在合约
        AuctionCancelled(_tokenId);     // 触发移除拍卖事件
    }

    // 拍卖完成,出_bidAmount金额购买_tokenId代币
    function _bid(uint256 _tokenId, uint256 _bidAmount)
        internal
        returns (uint256)
    {
        Auction storage auction = tokenIdToAuction[_tokenId];  // 获取拍卖引用,通过代币_tokenId
        require(_isOnAuction(auction));                        // 判断是否还在拍卖,如果为0 ,说明拍卖结束了
        uint256 price = _currentPrice(auction);                // 获取当前拍卖价格,拍卖价格是动态的,是随着时间的变化而减少的
        require(_bidAmount >= price);                          // 出价 >  当前的拍卖价格
 
       
        address seller = auction.seller;    // 获取拍卖的卖家
        _removeAuction(_tokenId);           // 移除拍卖

        if (price > 0) {                    // 当前拍卖价格 要 大于 0 
            uint256 auctioneerCut = _computeCut(price);      // 计算手续费
            uint256 sellerProceeds = price - auctioneerCut;  // 卖家可以获得的金额 =  最终价格 - 手续费
            seller.transfer(sellerProceeds);                 // 转账到卖家的账户中
        }

        
        uint256 bidExcess = _bidAmount - price;          // 计算多余的金额
        msg.sender.transfer(bidExcess);                  // 返还多余的 
        AuctionSuccessful(_tokenId, price, msg.sender);  // 触发拍卖成功事件

        return price;
    }

    // 从mapping中移除拍卖
    function _removeAuction(uint256 _tokenId) internal {
        delete tokenIdToAuction[_tokenId];
    }

    // 判断是否还在拍卖,如果startedAt 起始事件 为 0 ,说明已经拍卖结束
    function _isOnAuction(Auction storage _auction) internal view returns (bool) {
        return (_auction.startedAt > 0);
    }

    // 获取当前拍卖价格
    function _currentPrice(Auction storage _auction)
        internal
        view
        returns (uint256)
    {
        uint256 secondsPassed = 0;

        if (now > _auction.startedAt) {                // 当前时间要大于拍卖开始时间
            secondsPassed = now - _auction.startedAt;  //计算拍卖开始多少时间了
        }

        return _computeCurrentPrice(
            _auction.startingPrice,
            _auction.endingPrice,
            _auction.duration,
            secondsPassed
        );
    }

    // 计算当前的拍卖价格
    // 拍卖的价格是随着时间而逐渐降低的,如果没有人购买的话
    function _computeCurrentPrice(
        uint256 _startingPrice,
        uint256 _endingPrice,
        uint256 _duration,
        uint256 _secondsPassed
    )
        internal
        pure
        returns (uint256)
    {
        // 拍卖已经开始了多少时间  >   拍卖存续期 ,说明拍卖已经结束,返回最终价格
        if (_secondsPassed >= _duration) {
            return _endingPrice;
        } else {
             //  当前的价格,totalPriceChange 实际上是负数,因为_endingPrice 永远小于 _startingPrice
            int256 totalPriceChange = int256(_endingPrice) - int256(_startingPrice);

            // 随着时间的流逝,currentPriceChange(负数) 会越来越小,
            // 最小  =  拍卖限定最低价   -  拍卖起始价格
            int256 currentPriceChange = totalPriceChange * int256(_secondsPassed) / int256(_duration);

            // 当前价格  = 起始价格 + currentPriceChange(负数)
            // 当前价格最低等于 限定最低价
            int256 currentPrice = int256(_startingPrice) + currentPriceChange;
            return uint256(currentPrice);
        }
    }

    // 计算手续费
    function _computeCut(uint256 _price) internal view returns (uint256) {
        // ownerCut  为 0 到 10000的数,代表的是万分只几的手续费率
        return _price * ownerCut / 10000;
    }

}

从上述的合约上可以看到,随着拍卖时间的推移,程序限定的当前价格会逐渐减少。再次期间,只要竞拍者的出价大于当前价格即可成交。由于合约部署在主链上,而主链上的数据是透明的,我们可以通过抓取一个发起拍卖的交易,从交易数据中获取拍卖的最高价和最低价,以及拍卖起始时间,我们就可以通过代码中的算法换算出当前的竞拍价格。好在开发人员意识到这一点,当目前的竞拍价大于定价时,会将多余的钱给竞拍者退回账户。平台的盈利就是获取拍卖的手续费。

拍卖合约

contract ClockAuction is Pausable, ClockAuctionBase {
    // 接口验证,判断是否ERC721协议
    bytes4 constant InterfaceSignature_ERC721 = bytes4(0x9a20483d);

    // 构造函数
    function ClockAuction(address _nftAddress, uint256 _cut) public {
        require(_cut <= 10000);  // 交易费判断
        ownerCut = _cut;

        ERC721 candidateContract = ERC721(_nftAddress);                           // 实例化 ERC721 接口,_nftAddress为ERC721协议接口实例的合约地址
        require(candidateContract.supportsInterface(InterfaceSignature_ERC721));  // 代币是否满足规定的要求,拥有我想要的函数
        nonFungibleContract = candidateContract;                                  // 赋值给ClockAuctionBase中的nonFungibleContract
    }

    // 转账,将当前的拍卖合约的以太币转移给ERC721合约
    function withdrawBalance() external {
        address nftAddress = address(nonFungibleContract);   // 把接口强转为地址类型

        require(   // 权限判断
            msg.sender == owner ||
            msg.sender == nftAddress
        );
        // 进行转账 
        bool res = nftAddress.send(this.balance);
    }

    // 创建一个新的拍卖
    function createAuction(
        uint256 _tokenId,          // 代币的id
        uint256 _startingPrice,    // 开始的价格
        uint256 _endingPrice,      // 最低的价格
        uint256 _duration,         // 拍卖的存取期
        address _seller            // 拍卖者 
    )
        external
        whenNotPaused
    {
        // 限制_startingPrice,_endingPrice,_duration 都不能超过所指定的范围
        require(_startingPrice == uint256(uint128(_startingPrice)));
        require(_endingPrice == uint256(uint128(_endingPrice)));
        require(_duration == uint256(uint64(_duration)));

        require(_owns(msg.sender, _tokenId));  // 合约的调用者 必须 是代币的拥有者
        _escrow(msg.sender, _tokenId);         // 托管(合约的调用者的)代币到合约中
        Auction memory auction = Auction(      // 构造拍卖的结构体
            _seller,
            uint128(_startingPrice),
            uint128(_endingPrice),
            uint64(_duration),
            uint64(now)
        );
        _addAuction(_tokenId, auction);      // 增加了一个新的拍卖
    }

    // 完成拍卖
    function bid(uint256 _tokenId)
        external
        payable
        whenNotPaused
    {
        _bid(_tokenId, msg.value);         // 处理金额,调用父合约ClockAuctionBase 中的_bid
        _transfer(msg.sender, _tokenId);   // 把代币的所有权转移给买家
    }

    // 退出拍卖,外部函数
    function cancelAuction(uint256 _tokenId) external
    {
        Auction storage auction = tokenIdToAuction[_tokenId];   // 拍卖的引用
        require(_isOnAuction(auction));                         // 判断是否在交易期
        address seller = auction.seller;                        // 获取拍卖人的地址
        require(msg.sender == seller);                          // 判断合约的调用者必须是拍卖人
        _cancelAuction(_tokenId, seller);                       // 退出拍卖
    }

    // 游戏暂停时调用,只有拍卖的拥有者才能调用(防止程序遇到bug时,可以补救) 
    function cancelAuctionWhenPaused(uint256 _tokenId) whenPaused onlyOwner external
    {
        Auction storage auction = tokenIdToAuction[_tokenId];    // 拍卖的引用
        require(_isOnAuction(auction));                          // 判断是否在交易期
        _cancelAuction(_tokenId, auction.seller);                // 退出拍卖
    }

    // 获取拍卖信息,外部,不花费gas的调用,可以再前端查询
    function getAuction(uint256 _tokenId) external view
        returns
    (
        address seller,
        uint256 startingPrice,
        uint256 endingPrice,
        uint256 duration,
        uint256 startedAt
    ) {
        Auction storage auction = tokenIdToAuction[_tokenId];  // 拍卖的引用
        require(_isOnAuction(auction));                        // 判断是否在交易期
        return (
            auction.seller,
            auction.startingPrice,
            auction.endingPrice,
            auction.duration,
            auction.startedAt
        );
    }

    // 获取代币的当前价格
    function getCurrentPrice(uint256 _tokenId)
        external
        view
        returns (uint256)
    { 
        Auction storage auction = tokenIdToAuction[_tokenId];  // 拍卖的引用
        require(_isOnAuction(auction));                        // 游戏必须在运行
        return _currentPrice(auction);
    }

}

ClockAuction 合约继承了ClockAuctionBase 合约。其实ClockAuctionBase 是低层具体功能的实现;ClockAuction 是基于ClockAuctionBase 的二次封装。

游戏启停控制合约

contract Pausable is Ownable {
  // 事件 
  event Pause();  
  event Unpause();

  bool public paused = false;


  // 函数修饰器,没有暂停
  modifier whenNotPaused() {
    require(!paused);
    _;
  }

  // 函数修饰器,暂停
  modifier whenPaused {
    require(paused);
    _;
  }

  function pause() onlyOwner whenNotPaused returns (bool) {
    paused = true;
    Pause();
    return true;
  }
  
  function unpause() onlyOwner whenPaused returns (bool) {
    paused = false;
    Unpause();
    return true;
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值