英式拍卖
本章讲解在 Solidity
中,什么是英式拍卖,以及英式拍卖合约的算法和实现。
拍卖是一种通过竞价来出售商品或服务的交易方式,最终会以最高的出价成交。
1. 拍卖分类
拍卖可以分为多种类别,常见以下三种方式:
1)英式拍卖
英式拍卖,英文为 English Auction。这是一种最为常见的拍卖形式,参与者通过竞价不断提高价格,拍卖会以最高价出售商品或服务。
2)荷兰拍卖
荷兰拍卖,英文为 Dutch Auction。它与英式拍卖相反,是从一个较高的价格开始,然后逐渐降低,第一个愿意接受的出价即成交。
3)封闭式拍卖
封闭式拍卖,英文为 Sealed-Bid Auction。参与者在不知道其他人出价的情况下,私下提交出价,最高价者获胜。
2. 英式拍卖算法
英式拍卖是一种开放式拍卖。在英式拍卖中,拍卖师会逐渐提高价格,参与者可以选择是否继续竞标。竞价最高的人将以其出价获得拍卖物品或服务。
英式拍卖因其竞争性和公开性而受到欢迎,常用于各种拍卖场景,包括艺术品、房地产、古董、珠宝等。它提供了一个公平且透明的方式,让参与者根据市场需求和供给情况来确定价格。
英式拍卖的主要算法是逐步提高价格,直到只有一个竞标者愿意出更高的价格,然后以该价格成交。
我们把英式拍卖结合区块链技术,并根据实际情况,编写成一个智能合约,算法可以分为以下步骤:
1)起拍
拍卖者首先确定一个起拍价,参与者的投标不能低于这个价格。
2)提价
参与者根据自己的意愿提高价格,进行投标,每次提价不小于一定的幅度。
3)结束
每次拍卖都限定时间,一旦到达这个时间,投标结束。
4)成交
投标结束后,与最后一个有效的投标者成交,他也是本次拍卖出价最高的竞标者。
3. 英式拍卖合约
英式拍卖合约包括 3 个函数:开始拍卖函数 startAuction
、结束拍卖函数 endAuction
,以及竞价函数 bid
。
其中,开始拍卖和结束拍卖函数,只有合约拥有者有权调用, 而竞价函数 bid
可以由任何人调用。
在英式拍卖合约中,结束拍卖必须由人工操作,它无法做到自动结束,而荷兰拍卖是可以做到自动结束的。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; contract EnglishAuction is Ownable { uint constant START_PRICE = 1 ether; // 起拍价 uint constant DURATION = 60 seconds; // 拍卖持续时间 uint constant MIN_INCREMENT = 0.1 ether; // 最小竞价幅度 uint public startTime; // 拍卖开始时间 address public highestBidder; // 当前最高出价者 uint public highestBid; // 当前最高出价 // 拍卖开始事件 event AuctionStarted(uint startPrice, uint startTime); // 拍卖结束事件 event AuctionEnded(address winner, uint winningBid); // 竞拍事件 event Bid(address bidder, uint bidAmount); // 退款事件 event Refund(address bidder, uint bidAmount, bool success); // 构造函数,设置合约拥有者 constructor() Ownable(msg.sender) { } // 开始拍卖,仅合约拥有者可调用 function startAuction() public onlyOwner { // 确保拍卖还未开始 require(startTime == 0, "auction already started"); // 记录拍卖开始时间为当前时间戳 startTime = block.timestamp; // 将最高出价者清零 highestBidder = address(0); // 最高出价初始化为起拍价 highestBid = START_PRICE; // 触发拍卖开始事件,传入起拍价和开始时间 emit AuctionStarted(START_PRICE, block.timestamp); } // 竞拍出价 function bid() public payable { // 当前竞拍者 address bidder = msg.sender; // 当前竞拍出价 uint amount = msg.value; // 确保处于拍卖有效期:拍卖已经开始且未结束 require(startTime > 0 && block.timestamp < startTime + DURATION, "invalid auction time"); // 出价必须高于当前最高出价,且加价不小于最小幅度 require(amount > highestBid && amount - highestBid >= MIN_INCREMENT, "invalid auction bid"); if (highestBidder != address(0)) { // 退还之前最高出价者的款项 bool sent = payable(highestBidder).send(highestBid); // 触发 Refund 事件,记录退款是否成功 emit Refund(highestBidder, highestBid, sent); } // 更新最高出价者为当前竞拍者 highestBidder = bidder; // 更新最高出价为竞拍出价 highestBid = amount; // 触发出价事件 emit Bid(msg.sender, msg.value); } // 结束拍卖,仅合约拥有者可调用 function endAuction() public onlyOwner { // 确保超过拍卖有效期 require(startTime > 0 && block.timestamp >= startTime + DURATION, "invalid auction time"); // 触发拍卖结束事件 emit AuctionEnded(highestBidder, highestBid); // 拍卖开始时间清零 startTime = 0; // 将合约余额转给合约拥有者 uint amount = address(this).balance; if (amount > 0) { payable(owner()).transfer(amount); } //这里可以加入对竞拍成功者的任意操作 //..... } }
注意:这个合约 bid
函数代码中有一句:
bool sent = payable(highestBidder).send(highestBid);
为什么使用了send
转账,而不是 tranfer
呢?这是为了避免被 DOS
拒绝服务攻击。
有关智能合约安全的内容,可以参照我已经发布的安全系列教程。
4. 部署和测试
我们可以把上面编写的英式拍卖合约,复制到 Remix
里进行编译,然后部署到区块链上。
点击 startAuction
开始竞标,我们可以通过 highestBid
,查看当前所需的最低投标额。
按照我们设定参数,拍卖时长为 60 秒,拍卖起始价为 1 ETH
,每次加价不小于 0.1 ETH
。
我们可以在上方的 Value
处填写出价,点击 bid
进行投标。
拍卖时间到达后,合约拥有者可以通过 endAuction
结束本次拍卖,合约中的资金会自动转入合约拥有者的账户。