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

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

在这里插入图片描述

1 介绍

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

2 主要功能

  1. 拍卖订单信息的查询功能
  2. 相应拍卖订单的出价信息的查询
  3. 平台佣金可配置
  4. 拍卖订单的创建
  5. 对相应拍品进行出价
  6. 拍品成交
  7. 撤销拍卖
  8. 销毁合约

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。

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Solidity是一种智能合约语言,它是以太坊平台上的主要编程语言之一。以下是Solidity教程: 1. 智能合约的基础结构 Solidity智能合约由两个部分组成:状态变量和函数。状态变量是合约内部存储的数据,而函数是合约内部的代码,用于处理状态变量或执行其他操作。 2. Solidity的数据类型 Solidity支持各种数据类型,包括布尔值、整型、地址、字符串、数组、结构体等。例如: - 布尔值:bool - 整型:int、uint - 地址:address - 字符串:string - 数组:array - 结构体:struct 3. Solidity的函数 函数是Solidity合约中最重要的部分之一。函数可以接受参数,也可以返回值。例如: ``` function add(uint a, uint b) public returns (uint) { return a + b; } ``` 这个函数接受两个整数作为参数,并返回它们的和。 4. Solidity的控制流 Solidity支持各种控制流结构,包括if、for、while、do-while等。例如: ``` function isEven(uint x) public returns (bool) { if (x % 2 == 0) { return true; } else { return false; } } ``` 这个函数接受一个整数作为参数,并返回它是否为偶数。 5. Solidity的事件 事件是Solidity合约中的一种通知机制,它可以向外部应用程序发送消息。例如: ``` event Transfer(address indexed _from, address indexed _to, uint _value); ``` 这个事件表示在合约中发生了一次转账操作,它包含了发送方地址、接收方地址和转账金额。 6. Solidity的继承 Solidity支持继承,一个合约可以从另一个合约中继承状态变量和函数。例如: ``` contract A { uint public x = 1; } contract B is A { uint public y = 2; } ``` 在这个例子中,合约B从合约A中继承了状态变量x,并且定义了自己的状态变量y。 以上就是Solidity的基础教程,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值