solidity实现智能合约教程(5)-NFT拍卖合约

solidity实现智能合约教程(5)-NFT拍卖合约,第1张

文章目录 1 介绍2 主要功能3 代码示例4 部署测试

猛戳订阅学习专栏🍁🍁 👉 solidity系列合约源码+解析 👈 🍁🍁

1 介绍

拍卖作为历史悠久的交易方式,具有规范化、市场化的特点,在经济活动中扮演着重要角色,以其公开、公平、公正的价格发现功能,极大助力了资源流通及配置的实现。
随着区块链技术和智能合约的发展,使拍卖这一传统的交易方式有了新的定义,

2 主要功能 拍卖订单信息的查询功能相应拍卖订单的出价信息的查询平台佣金可配置拍卖订单的创建对相应拍品进行出价拍品成交撤销拍卖销毁合约 3 代码示例

以下为完整的拍卖流程的合约代码(仅供学习参考使用):

// 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。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/2991780.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存