荷兰式拍卖:
solidityproject/DutchAuction at master · XuHugo/solidityproject · GitHub
荷兰式拍卖,也称为公开降序拍卖,是一种拍卖类型,卖方首先设置起始价格、持续时间和折扣率。随着时间的推移,物品的价格会不断下降,直到预设的持续时间结束。例如,假设您想要一个无聊猿,但超出了您的预算。在那种情况下,随着时间的推移,这个无聊猿会变得越来越便宜;首先是 10% 的折扣,然后是 30%,然后是 50%,直到无聊猿便宜到足以让您购买为止。这就是荷兰式拍卖的概念。
项目方非常喜欢这种拍卖形式,主要有两个原因
- 荷兰拍卖的价格由最高慢慢下降,能让项目方获得最大的收入。
- 拍卖持续较长时间(通常6小时以上),可以避免
gas war
。
代码分析:
nft:要拍卖NFT的地址;seller:被拍卖nft的所有者;id:被拍卖nft的id;
AUCTIONTIME:拍卖持续时间;
startPrice:nft的起始价格;endPrice:nft最低价格,结束价格;
dropInterval:多长时间降低一次价格; dropStep:每次降低多少价格;
auctionStartTime:拍卖的起始时间;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
contract DutchAuction is Ownable {
IERC721 public immutable nft;
address public immutable seller;
uint256 private constant AUCTIONTIME = 10 minutes; // acution time
uint256 public immutable startPrice; // max price
uint256 public immutable endPrice; // min price
uint256 public immutable dropInterval; // how long will change price
uint256 public immutable dropStep; //
uint256 public auctionStartTime; //
uint256 public id; //NFT id
// constructor
constructor(
uint256 _startPrice,
uint256 _endPrice,
uint256 _dropInterval,
address _nft,
uint256 _id,
address _seller
) Ownable(msg.sender) {
require(
_startPrice > _endPrice,
"start price must bigger than end price!"
);
startPrice = _startPrice;
endPrice = _endPrice;
nft = IERC721(_nft);
auctionStartTime = block.timestamp;
id = _id;
seller = _seller;
dropInterval = _dropInterval;
dropStep = (_startPrice - _endPrice) / (AUCTIONTIME / _dropInterval);
}
初始化函数,dropStep是需要计算出来的,其实就是用起始价格差除以总共的时间段数;
// acution fun
function auction() external payable {
uint256 _saleStartTime = uint256(auctionStartTime); // new local varible, save gas
require(
_saleStartTime != 0 && block.timestamp >= _saleStartTime,
"sale has not started yet"
); // check auction time, if staring
uint256 price = getPrice(); // calc price
require(msg.value >= price, "Need to send more ETH.."); // check balances
// fransfer NFT
//nft._mint(msg.sender, id);
nft.safeTransferFrom(seller, msg.sender, id);
// refund eth
if (msg.value > price) {
payable(msg.sender).transfer(msg.value - price); //catch reentry
}
payable(seller).transfer(price);
}
拍卖函数,获取当前的价格,判断拍卖人是否有足够的余额支付nft;如果有,则转移nft;然后返回拍卖人剩余的钱,以及给nft原来的owner发送拍卖所得;
// get current price
function getPrice() public view returns (uint256) {
if (block.timestamp < auctionStartTime) {
return startPrice;
} else if (block.timestamp - auctionStartTime >= AUCTIONTIME) {
return endPrice;
} else {
uint256 steps = (block.timestamp - auctionStartTime) / dropInterval;
return startPrice - (steps * dropStep);
}
}
获取当前的nft价格,如果还没有开始拍卖,就返回最大值;如果结束了,就返回地板价;如果处于拍卖中,就计算一下当前的价格;公式也比较简单,就不做介绍了;
foundry测试
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.21;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {DutchAuction} from "../src/DutchAuction.sol";
contract DutchAuctionTest is Test {
DutchAuction public dauction;
NFT public nft;
address sellr = vm.addr(1);
address bob = vm.addr(2);
uint256 blockTime;
function setUp() public {
nft = new NFT();
dauction = new DutchAuction(
100 ether,
10 ether,
1 minutes,
address(nft),
1,
sellr
);
//check blocktime
blockTime = dauction.auctionStartTime();
console.log("blockTime:", blockTime);
//set 1 of nft of owner
nft.mint(sellr, 1);
vm.prank(sellr);
nft.approve(address(dauction), 1);
}
function test_getPrice() public {
//set blocktime
vm.warp(121);
uint256 price = dauction.getPrice();
assertEq(price, 82 ether);
}
function test_auction() public {
vm.deal(bob, 100 ether);
//set blocktime
vm.warp(121);
vm.prank(bob);
dauction.auction{value: 90 ether}();
assertEq(sellr.balance, 82 ether);
assertEq(nft.ownerOf(1), address(bob));
}
}
contract NFT is ERC721 {
constructor() ERC721("DA", "DA") {}
function mint(address to, uint256 tokenId) public {
_mint(to, tokenId);
}
}
NFT
这里需要自己提前写一个NFT的合约,我们直接使用openzeppelin来完成;
setUp
测试的时候,前置工作;分别创建一个NFT和DutchAuction合约;同时给sellr mint一个di为1的NFT;然后使用approve给dauction赋予id为1的NFT的转移权限,这样方便拍卖成功的时候,dauction帮忙转移NFT;
test_getPrice
测试获取价格,这里需要注意的就是,改变区块时间;
test_auction
拍卖,注意给拍卖人——bob足够的钱,以及修改区块时间;