8、拍卖
这个拍卖核心类,与整个迷恋猫项目的关系不大,是相当独立的,其实任何ERC721 代币都可以使用这个拍卖核心类。
ClockAuctionBase .sol
/// @title 拍卖核心类 ,任何ERC721 代币都可以使用这个拍卖核心类
/// @dev 包含了模型、变量、以及拍卖的内部函数
contract ClockAuctionBase {
// 拍卖的结构体
struct Auction {
// 代币的拥有者
address seller;
// 起始拍卖价格
uint128 startingPrice;
// 限定最低的拍卖价格
uint128 endingPrice;
// 拍卖的存续期(拍卖时间,一天,一个月)
uint64 duration;
// 拍卖开始时间,startedAt为0时,标识拍卖结束了
uint64 startedAt;
}
// ERC721 接口类,
ERC721 public nonFungibleContract;
// 手续费百分比
uint256 public ownerCut;
// ERC721代币 对应 拍卖结构体
mapping (uint256 => Auction) tokenIdToAuction;
//拍卖创建事件
event AuctionCreated(uint256 tokenId, uint256 startingPrice, uint256 endingPrice, uint256 duration);
// 拍卖成功事件
event AuctionSuccessful(uint256 tokenId, uint256 totalPrice, address winner);
//拍卖移除事件
event AuctionCancelled(uint256 tokenId);
/// @dev 判断代币的拥有者是否为_claimant
/// @param _claimant 拥有者的地址
/// @param _tokenId 代币ID
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 {
// it will throw if transfer fails
nonFungibleContract.transfer(_receiver, _tokenId);
}
/// 增加了一个新的拍卖
/// @param _tokenId 要拍卖的代币的id
/// @param _auction 拍卖的结构体
function _addAuction(uint256 _tokenId, Auction _auction) internal {
// 拍卖的时间必须要大于1分钟
require(_auction.duration >= 1 minutes);
// 绑定 代币ID 和 拍卖
tokenIdToAuction[_tokenId] = _auction;
// 触发一个拍卖创建事件
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)
{
// 获取拍卖引用,通过代币_tokenId
Auction storage auction = tokenIdToAuction[_tokenId];
// 判断是否还在拍卖,如果为0 ,说明拍卖结束了
require(_isOnAuction(auction));
// 获取当前拍卖价格,拍卖价格是动态的,是随着时间的变化而减少的
uint256 price = _currentPrice(auction);
// 我的出价 > 当前的拍卖价格
require(_bidAmount >= price);
// 获取拍卖的卖家
address seller = auction.seller;
// 移除拍卖
_removeAuction(_tokenId);
// 当前拍卖价格 要 大于 0
if (price > 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;
}
}
ClockAuction.sol
///
contract ClockAuction is Pausable, ClockAuctionBase {
/// 接口验证,判断是否ERC721协议
bytes4 constant InterfaceSignature_ERC721 = bytes4(0x9a20483d);
/// 构造函数
/// @param _nftAddress - address of a deployed contract implementing
/// the Nonfungible Interface.
/// @param _cut - percent cut the owner takes on each auction, must be
/// between 0-10,000.
function ClockAuction(address _nftAddress, uint256 _cut) public {
//交易费 判断
require(_cut <= 10000);
ownerCut = _cut;
// 实例化 ERC721 接口,_nftAddress为ERC721协议接口实例的合约地址
ERC721 candidateContract = ERC721(_nftAddress);
// 代币是否满足规定的要求,拥有我想要的函数
require(candidateContract.supportsInterface(InterfaceSignature_ERC721));
// 赋值给ClockAuctionBase中的nonFungibleContract
nonFungibleContract = candidateContract;
}
/// 转账,将当前的拍卖合约的以太币转移给ERC721合约
function withdrawBalance() external {
// 把接口强转为 地址类型
address nftAddress = address(nonFungibleContract);
// 权限判断
require(
msg.sender == owner ||
msg.sender == nftAddress
);
//
bool res = nftAddress.send(this.balance);
}
/// @dev 创建一个新的拍卖
/// @param _tokenId - 代币的id
/// @param _startingPrice - 开始的价格
/// @param _endingPrice - 最低的价格
/// @param _duration - 合约的存取期
/// @param _seller - 拍卖着
function createAuction(
uint256 _tokenId,
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);
}
/// 完成拍卖
/// @param _tokenId - 代币的ID
function bid(uint256 _tokenId)
external
payable
whenNotPaused
{
// 处理金额,调用父合约ClockAuctionBase 中的_bid
_bid(_tokenId, msg.value);
// 把代币的所有权转移给买家
_transfer(msg.sender, _tokenId);
}
/// 退出拍卖,外部函数
/// @param _tokenId - 代币的ID
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时,可以补救
/// @param _tokenId - 代币的ID
function cancelAuctionWhenPaused(uint256 _tokenId)
whenPaused
onlyOwner
external
{
Auction storage auction = tokenIdToAuction[_tokenId];
require(_isOnAuction(auction));
_cancelAuction(_tokenId, auction.seller);
}
/// 获取拍卖信息,外部,不花费gas的调用,可以再前端查询
/// @param _tokenId - 代币的 ID
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
);
}
/// 获取代币的当前价格
/// @param _tokenId - 代币的ID
function getCurrentPrice(uint256 _tokenId)
external
view
returns (uint256)
{
Auction storage auction = tokenIdToAuction[_tokenId];
// 游戏必须在运行
require(_isOnAuction(auction));
return _currentPrice(auction);
}
}
Pausable.sol
/**
* 游戏是否可以暂停
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* 修饰器 没有暂停
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* 修饰器 暂停
*/
modifier whenPaused {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused returns (bool) {
paused = true;
Pause();
return true;
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused returns (bool) {
paused = false;
Unpause();
return true;
}
}
网友评论