快速实现一个荷兰拍卖(Dutch Auction)合约

  • 木西
  • 发布于 2025-02-28 14:31
  • 阅读 168

前言本文通过借助openzeppelin和solidity编写一个荷兰拍卖的合约,合约主要实现了设置拍买开始时间,拍卖并铸造,提现,实时获取拍卖价格等相关功能荷兰拍买以及说明荷兰拍买概念:一种特殊的拍卖形式,也称“减价拍卖”。其特点是拍卖标的的竞价由高到低依次递减,直到第一个竞买人应价(

前言

本文通过借助openzeppelin和solidity编写一个荷兰拍卖的合约,合约主要实现了设置拍买开始时间,拍卖并铸造,提现,实时获取拍卖价格等相关功能

荷兰拍买以及说明

荷兰拍买概念:

一种特殊的拍卖形式,也称“减价拍卖”。其特点是拍卖标的的竞价由高到低依次递减,直到第一个竞买人应价(达到或超过底价)时击槌成交;<br>

荷兰拍买特点:

  1. 价格递减:拍卖从一个较高的起始价开始,然后逐渐降低,直到有竞买人接受当前价格。
  2. 迅速成交:这种拍卖方式成交迅速,适用于需要快速交易的物品。
  3. 信息流和物流同步:在拍卖过程中,信息流和物流同步运行,确保拍卖的高效性。

    荷兰拍买优缺点:

    优点

  • 价格效率:荷兰式拍卖有效地发现价格,因为价格不断下降,直到投标人同意。
  • 公平市场价值:最终接受的价格代表该商品的公平市场价值。
  • 多轮招标:多轮投标可以实现最佳价格发现。

缺点

  • 胜利者的诅咒:中标者支付的价格可能高于该商品的实际价值,由于竞争性招标。
  • 共谋:投标人可能会合作操纵最终价格,影响公平性。
  • 投标人参与:成功的价格发现需要足够多的活跃投标人

开发合约

合约说明:本合约主要对1000个nft进行拍卖,起拍价为为1eth,拍卖时间10分钟,每1分钟价格衰减1次

功能说明:手动设置开拍时间,拍卖并铸造,提现,获取实时价格,设置BaseURI等功能

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "hardhat/console.sol";
contract DutchAuction is Ownable, ERC721 {
    uint256 public constant COLLECTION_SIZE = 10000; // NFT总数
    uint256 public constant AUCTION_START_PRICE = 1 ether; // 起拍价
    uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 结束价(最低价)
    uint256 public constant AUCTION_TIME = 10 minutes; // 拍卖时间,设为10分钟
    uint256 public constant AUCTION_DROP_INTERVAL = 1 minutes; // 每过多久时间,价格衰减一次
    uint256 public constant AUCTION_DROP_PER_STEP =
        (AUCTION_START_PRICE - AUCTION_END_PRICE) /
        (AUCTION_TIME / AUCTION_DROP_INTERVAL); // 每次价格衰减步长

    uint256 public auctionStartTime; // 拍卖开始时间戳
    string private _baseTokenURI;   // metadata URI
    uint256[] private _allTokens; // 记录所有存在的tokenId 

    //设定拍卖起始时间:我们在构造函数中会声明当前区块时间为起始时间,项目方也可以通过`setAuctionStartTime(uint32)`函数来调整
    constructor() Ownable(msg.sender) ERC721("BTF Dutch Auction", "BTF Dutch Auction") {
        auctionStartTime = block.timestamp;
    }

    /**
     * ERC721Enumerable中totalSupply函数的实现
     */
    function totalSupply() public view virtual returns (uint256) {
        return _allTokens.length;
    }

    /**
     * Private函数,在_allTokens中添加一个新的token
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokens.push(tokenId);
    }

    // 拍卖mint函数
    function auctionMint(uint256 quantity) external payable{
        uint256 _saleStartTime = uint256(auctionStartTime); // 建立local变量,减少gas花费
        require(
        _saleStartTime != 0 && block.timestamp >= _saleStartTime,
        "sale has not started yet"
        ); // 检查是否设置起拍时间,拍卖是否开始
        require(
        totalSupply() + quantity &lt;= COLLECTION_SIZE,
        "not enough remaining reserved for auction to support desired mint amount"
        ); // 检查是否超过NFT上限

        uint256 totalCost = getAuctionPrice() * quantity; // 计算mint成本
        // console.log(msg.value >= totalCost);
        // console.log(msg.value);
        // console.log(totalCost);
        require(msg.value >= totalCost, "Need to send more ETH."); // 检查用户是否支付足够ETH

        // Mint NFT
        for(uint256 i = 0; i &lt; quantity; i++) {
            uint256 mintIndex = totalSupply();
            _mint(msg.sender, mintIndex);
            _addTokenToAllTokensEnumeration(mintIndex);
        }
        // 多余ETH退款
        if (msg.value > totalCost) {
            payable(msg.sender).transfer(msg.value - totalCost); //注意一下这里是否有重入的风险
        }
    }

    // 获取拍卖实时价格
    function getAuctionPrice()
        public
        view
        returns (uint256)
    {
        if (block.timestamp &lt; auctionStartTime) {
        return AUCTION_START_PRICE;
        }else if (block.timestamp - auctionStartTime >= AUCTION_TIME) {
        return AUCTION_END_PRICE;
        } else {
        uint256 steps = (block.timestamp - auctionStartTime) /
            AUCTION_DROP_INTERVAL;
        return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);
        }
    }

    // auctionStartTime setter函数,onlyOwner
    function setAuctionStartTime(uint32 timestamp) external onlyOwner {
        auctionStartTime = timestamp;
    }

    // BaseURI
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
    }
    // BaseURI setter函数, onlyOwner
    function setBaseURI(string calldata baseURI) external onlyOwner {
        _baseTokenURI = baseURI;
    }
    // 提款函数,onlyOwner
    function withdrawMoney() external onlyOwner {
        (bool success, ) = msg.sender.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }
}
# 编译指令
# npx hardhat compile

测试合约

说明:关于设置开拍实际需要使用Unix时间格式,可以通过Unix在线网站获取

const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("荷兰拍卖",function(){
    let DutchAuction;//合约
    let firstAccount//第一个账户
    let secondAccount//第二个账户
    let addr1;
    let addr2;
    beforeEach(async function(){
        await deployments.fixture(["DutchAuction"]);
        [addr1,addr2] = await ethers.getSigners();//这样获取的账号可以进行转账操作
        firstAccount=(await getNamedAccounts()).firstAccount;
        secondAccount=(await getNamedAccounts()).secondAccount;
        const DutchAuctionDeployment = await deployments.get("DutchAuction");
        DutchAuction = await ethers.getContractAt("DutchAuction",DutchAuctionDeployment.address);//已经部署的合约交互
    })
    describe("DutchAuction",function(){
        it("DutchAuction 交易", async function () {

            const balance = await ethers.provider.getBalance(firstAccount);
            // 将余额转换为ETH单位
            const balanceInEth = ethers.utils.formatEther(balance);
            console.log(`Owner balance 未提款前: ${balanceInEth} ETH`);
            //可以使用https://tool.chinaz.com/tools/unixtime.aspx网站获取Unix时间戳
            //设置起拍时间
            let timestamp = "1736499660";
            let startTimemap=await DutchAuction.setAuctionStartTime(1736499660);
            // console.log("设置起拍时间",startTimemap);
            const date = new Date(timestamp * 1000)
            let startTime=date.toLocaleString()
            console.log("起拍时间Unix格式转日期",startTime);
            // 获取总量
            const totalSupply = await DutchAuction.totalSupply();//代币1的余额
            console.log("当前总量",totalSupply)
            //获取当前起拍价
            let AuctionPrice=await DutchAuction.getAuctionPrice()
            console.log("价格",`${ethers.utils.formatEther(AuctionPrice)} eth`)
            //通过链接账号addr1铸造一个nft 给账号1eth 多的就退会原账号
            await DutchAuction.connect(addr1).auctionMint(1,{value:ethers.utils.parseEther("1")})
            console.log('当前总量',await DutchAuction.totalSupply())
            //通过链接账号addr2铸造一个nft 给账号2eth 多的就退会原账号
            await DutchAuction.connect(addr2).auctionMint(1,{value:ethers.utils.parseEther("1")})
            console.log('当前总量',await DutchAuction.totalSupply())
            //把拍卖行的提现
            await DutchAuction.withdrawMoney();
            console.log(`Owner balance 提款后: ${balanceInEth} ETH`);
        })
    })
})
# 测试指令
# npx hardhat test ./test/xxx.js

部署合约

module.exports = async function ({getNamedAccounts,deployments}) {
  const  firstAccount= (await getNamedAccounts()).firstAccount;
  const {deploy,log} = deployments;
  const DutchAuction=await deploy("DutchAuction",{
    from:firstAccount,
    args: [],//参数 
    log: true,
  })
  console.log('荷兰拍卖合约地址',DutchAuction.address)

};
module.exports.tags = ["all", "DutchAuction"];

总结

以上就是荷兰拍卖智能合约从开发到测试再到部署的全部过程,主要借助了工具在线的unix在线工具,以及openzeppelin的库,注意的是在测试auctionMint方法时需要链接账号后,对此合约发送对应的代币才可以测试通过。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
江湖只有他的大名,没有他的介绍。