{"author":{"address":null,"user":"https://learnblockchain.cn/people/18158"},"content":{"body":"# 前言\r\n\u003e 本文通过借助openzeppelin和solidity编写一个荷兰拍卖的合约，合约主要实现了设置拍买开始时间，拍卖并铸造，提现，实时获取拍卖价格等相关功能\r\n# 荷兰拍买以及说明\r\n#### 荷兰拍买概念：\r\n一种特殊的拍卖形式，也称“减价拍卖”。其特点是拍卖标的的竞价由高到低依次递减，直到第一个竞买人应价（达到或超过底价）时击槌成交；\u003cbr\u003e\r\n####  荷兰拍买特点：\r\n1.  **价格递减**：拍卖从一个较高的起始价开始，然后逐渐降低，直到有竞买人接受当前价格。\r\n2.  **迅速成交**：这种拍卖方式成交迅速，适用于需要快速交易的物品。\r\n3.  **信息流和物流同步**：在拍卖过程中，信息流和物流同步运行，确保拍卖的高效性。\r\n#### 荷兰拍买优缺点：\r\n**优点**：\r\n\r\n-   **价格效率**：荷兰式拍卖有效地发现价格，因为价格不断下降，直到投标人同意。\r\n-   **公平市场价值**：最终接受的价格代表该商品的公平市场价值。\r\n-   **多轮招标**：多轮投标可以实现最佳价格发现。\r\n\r\n**缺点**：\r\n\r\n-   **胜利者的诅咒**：中标者支付的价格可能高于该商品的实际价值，由于竞争性招标。\r\n-   **共谋**：投标人可能会合作操纵最终价格，影响公平性。\r\n-   **投标人参与**：成功的价格发现需要足够多的活跃投标人\r\n\r\n# 开发合约\r\n#### 合约说明：本合约主要对1000个nft进行拍卖，起拍价为为1eth，拍卖时间10分钟，每1分钟价格衰减1次\r\n#### 功能说明：手动设置开拍时间，拍卖并铸造，提现，获取实时价格，设置BaseURI等功能\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.21;\r\n\r\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport {ERC721} from \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\r\nimport \"hardhat/console.sol\";\r\ncontract DutchAuction is Ownable, ERC721 {\r\n    uint256 public constant COLLECTION_SIZE = 10000; // NFT总数\r\n    uint256 public constant AUCTION_START_PRICE = 1 ether; // 起拍价\r\n    uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 结束价（最低价）\r\n    uint256 public constant AUCTION_TIME = 10 minutes; // 拍卖时间，设为10分钟\r\n    uint256 public constant AUCTION_DROP_INTERVAL = 1 minutes; // 每过多久时间，价格衰减一次\r\n    uint256 public constant AUCTION_DROP_PER_STEP =\r\n        (AUCTION_START_PRICE - AUCTION_END_PRICE) /\r\n        (AUCTION_TIME / AUCTION_DROP_INTERVAL); // 每次价格衰减步长\r\n    \r\n    uint256 public auctionStartTime; // 拍卖开始时间戳\r\n    string private _baseTokenURI;   // metadata URI\r\n    uint256[] private _allTokens; // 记录所有存在的tokenId \r\n\r\n    //设定拍卖起始时间：我们在构造函数中会声明当前区块时间为起始时间，项目方也可以通过`setAuctionStartTime(uint32)`函数来调整\r\n    constructor() Ownable(msg.sender) ERC721(\"BTF Dutch Auction\", \"BTF Dutch Auction\") {\r\n        auctionStartTime = block.timestamp;\r\n    }\r\n\r\n    /**\r\n     * ERC721Enumerable中totalSupply函数的实现\r\n     */\r\n    function totalSupply() public view virtual returns (uint256) {\r\n        return _allTokens.length;\r\n    }\r\n\r\n    /**\r\n     * Private函数，在_allTokens中添加一个新的token\r\n     */\r\n    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {\r\n        _allTokens.push(tokenId);\r\n    }\r\n\r\n    // 拍卖mint函数\r\n    function auctionMint(uint256 quantity) external payable{\r\n        uint256 _saleStartTime = uint256(auctionStartTime); // 建立local变量，减少gas花费\r\n        require(\r\n        _saleStartTime != 0 \u0026\u0026 block.timestamp \u003e= _saleStartTime,\r\n        \"sale has not started yet\"\r\n        ); // 检查是否设置起拍时间，拍卖是否开始\r\n        require(\r\n        totalSupply() + quantity \u003c= COLLECTION_SIZE,\r\n        \"not enough remaining reserved for auction to support desired mint amount\"\r\n        ); // 检查是否超过NFT上限\r\n        \r\n        uint256 totalCost = getAuctionPrice() * quantity; // 计算mint成本\r\n        // console.log(msg.value \u003e= totalCost);\r\n        // console.log(msg.value);\r\n        // console.log(totalCost);\r\n        require(msg.value \u003e= totalCost, \"Need to send more ETH.\"); // 检查用户是否支付足够ETH\r\n        \r\n        // Mint NFT\r\n        for(uint256 i = 0; i \u003c quantity; i++) {\r\n            uint256 mintIndex = totalSupply();\r\n            _mint(msg.sender, mintIndex);\r\n            _addTokenToAllTokensEnumeration(mintIndex);\r\n        }\r\n        // 多余ETH退款\r\n        if (msg.value \u003e totalCost) {\r\n            payable(msg.sender).transfer(msg.value - totalCost); //注意一下这里是否有重入的风险\r\n        }\r\n    }\r\n\r\n    // 获取拍卖实时价格\r\n    function getAuctionPrice()\r\n        public\r\n        view\r\n        returns (uint256)\r\n    {\r\n        if (block.timestamp \u003c auctionStartTime) {\r\n        return AUCTION_START_PRICE;\r\n        }else if (block.timestamp - auctionStartTime \u003e= AUCTION_TIME) {\r\n        return AUCTION_END_PRICE;\r\n        } else {\r\n        uint256 steps = (block.timestamp - auctionStartTime) /\r\n            AUCTION_DROP_INTERVAL;\r\n        return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);\r\n        }\r\n    }\r\n\r\n    // auctionStartTime setter函数，onlyOwner\r\n    function setAuctionStartTime(uint32 timestamp) external onlyOwner {\r\n        auctionStartTime = timestamp;\r\n    }\r\n\r\n    // BaseURI\r\n    function _baseURI() internal view virtual override returns (string memory) {\r\n        return _baseTokenURI;\r\n    }\r\n    // BaseURI setter函数, onlyOwner\r\n    function setBaseURI(string calldata baseURI) external onlyOwner {\r\n        _baseTokenURI = baseURI;\r\n    }\r\n    // 提款函数，onlyOwner\r\n    function withdrawMoney() external onlyOwner {\r\n        (bool success, ) = msg.sender.call{value: address(this).balance}(\"\");\r\n        require(success, \"Transfer failed.\");\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n# 测试合约\r\n**说明**：关于设置开拍实际需要使用Unix时间格式，可以通过[Unix在线网站获取](https://tool.chinaz.com/tools/unixtime.aspx)\r\n```\r\nconst {ethers,getNamedAccounts,deployments} = require(\"hardhat\");\r\nconst { assert,expect } = require(\"chai\");\r\ndescribe(\"荷兰拍卖\",function(){\r\n    let DutchAuction;//合约\r\n    let firstAccount//第一个账户\r\n    let secondAccount//第二个账户\r\n    let addr1;\r\n    let addr2;\r\n    beforeEach(async function(){\r\n        await deployments.fixture([\"DutchAuction\"]);\r\n        [addr1,addr2] = await ethers.getSigners();//这样获取的账号可以进行转账操作\r\n        firstAccount=(await getNamedAccounts()).firstAccount;\r\n        secondAccount=(await getNamedAccounts()).secondAccount;\r\n        const DutchAuctionDeployment = await deployments.get(\"DutchAuction\");\r\n        DutchAuction = await ethers.getContractAt(\"DutchAuction\",DutchAuctionDeployment.address);//已经部署的合约交互\r\n    })\r\n    describe(\"DutchAuction\",function(){\r\n        it(\"DutchAuction 交易\", async function () {\r\n            \r\n            const balance = await ethers.provider.getBalance(firstAccount);\r\n            // 将余额转换为ETH单位\r\n            const balanceInEth = ethers.utils.formatEther(balance);\r\n            console.log(`Owner balance 未提款前: ${balanceInEth} ETH`);\r\n            //可以使用https://tool.chinaz.com/tools/unixtime.aspx网站获取Unix时间戳\r\n            //设置起拍时间\r\n            let timestamp = \"1736499660\";\r\n            let startTimemap=await DutchAuction.setAuctionStartTime(1736499660);\r\n            // console.log(\"设置起拍时间\",startTimemap);\r\n            const date = new Date(timestamp * 1000)\r\n            let startTime=date.toLocaleString()\r\n            console.log(\"起拍时间Unix格式转日期\",startTime);\r\n            // 获取总量\r\n            const totalSupply = await DutchAuction.totalSupply();//代币1的余额\r\n            console.log(\"当前总量\",totalSupply)\r\n            //获取当前起拍价\r\n            let AuctionPrice=await DutchAuction.getAuctionPrice()\r\n            console.log(\"价格\",`${ethers.utils.formatEther(AuctionPrice)} eth`)\r\n            //通过链接账号addr1铸造一个nft 给账号1eth 多的就退会原账号\r\n            await DutchAuction.connect(addr1).auctionMint(1,{value:ethers.utils.parseEther(\"1\")})\r\n            console.log('当前总量',await DutchAuction.totalSupply())\r\n            //通过链接账号addr2铸造一个nft 给账号2eth 多的就退会原账号\r\n            await DutchAuction.connect(addr2).auctionMint(1,{value:ethers.utils.parseEther(\"1\")})\r\n            console.log('当前总量',await DutchAuction.totalSupply())\r\n            //把拍卖行的提现\r\n            await DutchAuction.withdrawMoney();\r\n            console.log(`Owner balance 提款后: ${balanceInEth} ETH`);\r\n        })\r\n    })\r\n})\r\n# 测试指令\r\n# npx hardhat test ./test/xxx.js\r\n```\r\n# 部署合约\r\n```\r\nmodule.exports = async function ({getNamedAccounts,deployments}) {\r\n  const  firstAccount= (await getNamedAccounts()).firstAccount;\r\n  const {deploy,log} = deployments;\r\n  const DutchAuction=await deploy(\"DutchAuction\",{\r\n    from:firstAccount,\r\n    args: [],//参数 \r\n    log: true,\r\n  })\r\n  console.log('荷兰拍卖合约地址',DutchAuction.address)\r\n\r\n};\r\nmodule.exports.tags = [\"all\", \"DutchAuction\"];\r\n```\r\n# 总结\r\n以上就是荷兰拍卖智能合约从开发到测试再到部署的全部过程，主要借助了工具在线的unix在线工具，以及openzeppelin的库，注意的是在测试auctionMint方法时需要链接账号后，对此合约发送对应的代币才可以测试通过。","title":"快速实现一个荷兰拍卖（Dutch Auction）合约"},"history":null,"timestamp":1740724283,"version":1}