猛戳订阅学习专栏🍁🍁 👉 solidity系列合约源码+解析 👈 🍁🍁
1 介绍拍卖作为历史悠久的交易方式,具有规范化、市场化的特点,在经济活动中扮演着重要角色,以其公开、公平、公正的价格发现功能,极大助力了资源流通及配置的实现。
随着区块链技术和智能合约的发展,使拍卖这一传统的交易方式有了新的定义,
以下为完整的拍卖流程的合约代码(仅供学习参考使用):
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
contract NFTStoreAuction {
string public name;
IERC20 public currToken;
IERC721 public itemToken;
address private owner;
//拍卖订单的结构体
struct Auction {
address seller; //出售者
uint128 price; //价格
uint256 tokenId; //拍卖的tokenId
uint256 startTime; //拍卖开始时间
uint256 endTime; //拍卖结束时间
uint256 highestBid; //最高价
address highestBidder; //出最高价用户的地址
bool finished; //拍卖是否完成
bool active; //拍卖是否进行中
}
//出价的结构体
struct Bidder {
address addr; //出价者地址
uint256 amount; //出价金额
uint256 bidAt; //出价时间
}
uint256 public totalHoldings = 0; //当前共有多少个拍卖
uint256 public platformFee = 5; //平台佣金费率
uint256 constant feePercentage = 100; //费率百分比
Auction[] public auctions; //所有的拍卖信息
Bidder[] public bidders; //所有的出价信息
mapping(uint256 => Auction) public tokenIdToAuction; //tokenId => 拍卖信息
mapping(uint256 => uint256) public tokenIdToIndex; //tokenId =>
mapping(address => Auction[]) public auctionOwner; //当前地址发布的所有拍卖
mapping(uint256 => Bidder[]) public tokenIdToBidder; //当前TokenId的所有出价
//合约部署初始化
constructor() {
// currToken = IERC20(_currTokenAddress);
//itemToken = IERC721(_itemTokenAddress);
name = "NFTStoreAuction"; //拍卖合约的名称
owner = msg.sender; //拍卖合约的拥有者
}
function setItemToken(address itemTokenAddress_) external {
require(msg.sender == owner, "Only Owner Function!");
itemToken = IERC721(itemTokenAddress_); //设置要拍卖的NFT的合约地址
}
function setCurrToken(address currTokenAddress_) external {
require(msg.sender == owner, "Only Owner Function!");
currToken = IERC20(currTokenAddress_); //设置要拍卖的ERC20代币的合约地址
}
//设置提现到的地址
function setWithDraw(address withDrawTo_) external {
require(msg.sender == owner, "Only Owner Function!");
withdrawAddress = withDrawTo_;
}
//设置接收者的地址
function setRecipAddr(address recipAddr_) external {
require(msg.sender == owner, "Only Owner Function!");
recipientAddr = recipAddr_;
}
//设置平台佣金费率
function setPlatformFee(uint256 platformFee_) external {
require(msg.sender == owner, "Only Owner Function!");
platformFee = platformFee_;
}
//拍卖状态改变时的event事件
event AuctionStatusChange(
uint256 tokenID,
bytes32 status,
address indexed poster,
uint256 price,
address indexed buyer,
uint256 startTime,
uint256 endTime
);
//拍卖创建时候的event事件
event AuctionCreated(
uint256 _tokenId,
address indexed _seller,
uint256 _value
);
//拍卖撤销时候的event事件
event AuctionCanceled(uint256 _tokenId);
//拍卖有人出价时候的event事件
event AuctionBidden(
uint256 _tokenId,
address indexed _bidder,
uint256 _amount
);
//拍卖完成时候的event事件
event AuctionFinished(
uint256 _tokenId,
address indexed _awarder,
uint256 price
);
address public withdrawAddress; //从合约中提现时候的地址
address public recipientAddr; //平台佣金接收者的地址
//创建拍卖
function createAuction(
uint256 _tokenId,
uint128 _price,
uint256 _startTime,
uint256 _endTime
) public {
require(
msg.sender == itemToken.ownerOf(_tokenId),
"Should be the owner of token"
);
require(_startTime >= block.timestamp);
require(_endTime >= block.timestamp);
require(_endTime > _startTime);
//创建拍卖时候把出售者的NFT转到这个拍卖合约中
itemToken.transferFrom(msg.sender, address(this), _tokenId);
//组合拍卖信息
Auction memory auction = Auction({
seller: msg.sender,
price: _price,
tokenId: _tokenId,
startTime: _startTime,
endTime: _endTime,
highestBid: 0,
highestBidder: address(0x0),
finished: false,
active: true
});
//把NFT的ID信息和拍卖的信息进行绑定存储
tokenIdToAuction[_tokenId] = auction;
//往所有拍卖信息中放入拍卖信息
auctions.push(auction);
//为发起拍卖信息放入当前用户所有的拍卖集合中去
auctionOwner[msg.sender].push(auction);
//触发拍卖创建事件
//emit AuctionCreated(_tokenId,msg.sender,_price);
emit AuctionStatusChange(
_tokenId,
"Create",
msg.sender,
_price,
address(this),
_startTime,
_endTime
);
}
//拍卖出价
function bidAuction(uint256 _tokenId) public payable {
/**
TODO
1.判断当前拍卖的状态(是否已经撤销)
2.判断当前拍卖的状态(是否已经完成)
*/
require(isBidValid(_tokenId, msg.value));
Auction memory auction = tokenIdToAuction[_tokenId];
if (block.timestamp > auction.endTime) revert(); //超过拍卖时间就回滚本次交易
require(msg.sender != auction.seller, "Owner can't bid"); //不能对自己的拍卖出价
uint256 highestBid = auction.highestBid; //本次出价前最高价
address highestBidder = auction.highestBidder; //本次出价前最高价出价者地址
require(msg.value > auction.highestBid); //当前出价要高于当前所有出价的最高价
//require(balanceOf(msg.sender) >=msg.value, "insufficient balance");
//判断出价者余额
require(
payable(msg.sender).balance >= msg.value,
"insufficient balance"
);
if (msg.value > highestBid) {
tokenIdToAuction[_tokenId].highestBid = msg.value; //设置为当前最高价
tokenIdToAuction[_tokenId].highestBidder = msg.sender; //设置为当前最高价出价者
//require(currToken.approve(address(this), price), "Approve has failed");
//currToken.transferFrom(msg.sender,address(this),price);
// refund the last bidder
if (highestBid > 0) {
//require(currToken.approve(address(this), highestBid), "Approve has failed");
//currToken.transferFrom(address(this),highestBidder, highestBid);
payable(highestBidder).transfer(highestBid); //给上一个最高价出价者返还出价费用
}
Bidder memory bidder = Bidder({
addr: msg.sender,
amount: msg.value,
bidAt: block.timestamp
});
//把当前的出价放入当前拍卖的出价数组中
tokenIdToBidder[_tokenId].push(bidder);
//触发拍卖状态改变事件(有新的出价)
//emit AuctionBidden(_tokenId, msg.sender, msg.value);
emit AuctionStatusChange(
_tokenId,
"Bid",
address(this),
msg.value,
msg.sender,
block.timestamp,
block.timestamp
);
}
}
//完成本次拍卖
function finishAuction(uint256 _tokenId) public payable {
/**
TODO
1.判断当前拍卖是否已经完成
2.判断当前拍卖是否已经撤销
3.判断当前拍卖是否还在进行中
*/
Auction memory auction = tokenIdToAuction[_tokenId];
require(
msg.sender == auction.seller,
"Should only be called by the seller"
);
require(block.timestamp >= auction.endTime);
uint256 _bidAmount = auction.highestBid;
address _bider = auction.highestBidder;
if (_bidAmount == 0) {
//如果当前出价为0,则撤销拍卖
cancelAuction(_tokenId);
} else {
//require(currToken.approve(address(this), _bidAmount), "Approve has failed");
uint256 receipientAmount = (_bidAmount * platformFee) /
feePercentage; //计算出平台佣金
uint256 sellerAmount = _bidAmount - receipientAmount; //计算拍卖者可以拿到的金额
//currToken.transferFrom(address(this),recipientAddr, receipientAmount);
//currToken.transferFrom(address(this),auction.seller,sellerAmount);
payable(recipientAddr).transfer(receipientAmount); //把佣金转到相应地址
payable(auction.seller).transfer(sellerAmount); //把拍卖得到的金额转给拍卖者
//把NFT转给最高价出价者
itemToken.transferFrom(address(this), _bider, _tokenId);
//当前拍卖标记为完成
tokenIdToAuction[_tokenId].finished = true;
//当前拍卖是否进行中标记为否
tokenIdToAuction[_tokenId].active = false;
//删除当前拍卖的所有出价信息
delete tokenIdToBidder[_tokenId];
//触发拍卖状态改变事件(完成)
//emit AuctionFinished(_tokenId, _bider,_bidAmount);
emit AuctionStatusChange(
_tokenId,
"Finish",
address(this),
_bidAmount,
_bider,
block.timestamp,
block.timestamp
);
}
}
//判断本次出价是否合法
function isBidValid(uint256 _tokenId, uint256 _bidAmount)
internal
view
returns (bool)
{
Auction memory auction = tokenIdToAuction[_tokenId]; //当前拍卖的信息
uint256 startTime = auction.startTime;
uint256 endTime = auction.endTime;
address seller = auction.seller;
uint128 price = auction.price;
bool withinTime = block.timestamp >= startTime &&
block.timestamp <= endTime; //判断当前出价是否在拍卖的规定时间
bool bidAmountValid = _bidAmount >= price; //判断当前出价是否比规定价格高
bool sellerValid = seller != address(0); //当前售卖者是否合法
return withinTime && bidAmountValid && sellerValid;
}
//获取tokenId的拍卖信息
function getAuction(uint256 _tokenId)
public
view
returns (
address,
uint128,
uint256,
uint256,
uint256,
uint256,
address,
bool,
bool
)
{
Auction memory auction = tokenIdToAuction[_tokenId];
return (
auction.seller,
auction.price,
auction.tokenId,
auction.startTime,
auction.endTime,
auction.highestBid,
auction.highestBidder,
auction.finished,
auction.active
);
}
//获取tokenId的拍卖的所有出价
function getBidders(uint256 _tokenId)
public
view
returns (Bidder[] memory)
{
Bidder[] memory biddersOfToken = tokenIdToBidder[_tokenId];
return (biddersOfToken);
}
//撤销拍卖
function cancelAuction(uint256 _tokenId) public {
/**
TODO
1.判断当前拍卖状态(是否在进行中)
2.判断当前拍卖状态是否已经撤销
*/
Auction memory auction = tokenIdToAuction[_tokenId];
require(
msg.sender == auction.seller,
"Auction can be cancelled only by seller."
);
uint256 amount = auction.highestBid;
address bidder = auction.highestBidder;
// require(block.timestamp <= auction.endTime, "Only be canceled before end");
//把当前的NFT转给拍卖发起者
itemToken.transferFrom(address(this), msg.sender, _tokenId);
//refund losers funds
// if there are bids refund the last bid
if (amount > 0) {
//require(currToken.approve(address(this), amount), "Approve has failed");
//currToken.transferFrom(address(this),bidder,amount);
//把最后一位出价者的钱进行返还
payable(bidder).transfer(amount);
}
//拍卖状态设置为停止
tokenIdToAuction[_tokenId].active = false;
//删除当前的拍卖的所有出价信息
delete tokenIdToBidder[_tokenId];
//emit AuctionCanceled(_tokenId);
//触发当前拍卖状态改变事件(撤销)
emit AuctionStatusChange(
_tokenId,
"Cancel",
address(this),
0,
auction.seller,
block.timestamp,
block.timestamp
);
}
/**
* @dev 获取当前拍卖的最新出价信息
* @param _tokenId 拍卖的tokenId
* @return amount uint256, address of last bidder
*/
function getCurrentBid(uint256 _tokenId)
public
view
returns (
address,
uint256,
uint256
)
{
uint256 bidsLength = tokenIdToBidder[_tokenId].length;
// if there are bids refund the last bid
if (bidsLength > 0) {
Bidder memory lastBid = tokenIdToBidder[_tokenId][bidsLength - 1];
return (lastBid.addr, lastBid.amount, lastBid.bidAt);
}
return (address(0), uint256(0), uint256(0));
}
/**
* @dev 获取用户的所有拍卖
* @param _owner address of the auction owner
*/
function getAuctionsOf(address _owner)
public
view
returns (Auction[] memory)
{
Auction[] memory ownedAuctions = auctionOwner[_owner];
return ownedAuctions;
}
/**
* @dev 获取用户总共有多少拍卖
* @param _owner address of the owner
* @return uint total number of auctions
*/
function getAuctionsCountOfOwner(address _owner)
public
view
returns (uint256)
{
return auctionOwner[_owner].length;
}
/**
* @dev 获取一共有多少拍卖
* @return uint representing the auction count
*/
function getCount() public view returns (uint256) {
return auctions.length;
}
/**
* @dev 获取拍卖一共有多少条出价
* @param _tokenId uint ID of the auction
*/
function getBidsCount(uint256 _tokenId) public view returns (uint256) {
return tokenIdToBidder[_tokenId].length;
}
/**
*获取当前合约中一共有多少余额
*/
function totalBalance() external view returns (uint256) {
//return currToken.balanceOf(address(this));
return payable(address(this)).balance;
}
//提取合约中的所有余额
function withdrawFunds() external withdrawAddressOnly {
//require(currToken.approve(address(this), this.totalBalance()), "Approve has failed");
//currToken.transferFrom(address(this),msg.sender, this.totalBalance());
payable(msg.sender).transfer(this.totalBalance());
}
modifier withdrawAddressOnly() {
require(msg.sender == withdrawAddress, "only withdrawer can call this");
_;
}
//销毁合约
function destroy() public virtual {
/**
TODO
1.销毁前要先把合约中的余额转出
*/
require(
msg.sender == owner,
"Only the owner of this Contract could destroy It!"
);
if (msg.sender == owner) selfdestruct(payable(owner));
}
}
4 部署测试
接下来我们就先拿一个ERC721类型NFT去做1个拍卖流程测试,其他种类的NFT流程都类似:
上面我们先部署好了该拍卖合约和一个ERC721代币合约,并给5cb2结尾的地址mint了一个TokenId为1的NFT,接下来我们去给拍卖合约做相应的初始化配置:
接下来我们使用5cb2结尾的地址创建一个拍卖订单:
先把tokenId为1的NFT授权给拍卖合约:
创建拍卖订单并查看该TokenId的拍卖订单信息:
接下来使用ddc4结尾的地址来进行出价
接下来完成本次拍卖:
查看tokenId为1的NFT已经成功转移到了ddc4结尾的地址,5cb2结尾的地址也得到了相应出价的ETH。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)