拍卖的方式有几种,其中有两种概念你需要先了解下,一种是公开拍卖(open auction),一种叫盲拍(blind auction)。简单来讲就是,前一种拍卖大家都能互相看到对方的出价,而后一种则看不到。
先看一个简单的公开拍卖。
代码语言:javascript复制contract SimpleAuction {
//拍卖的受益人
address payable public beneficiary;
//拍卖的结束时间
uint public auctionEndTime;
// 最高出价的人
address public highestBidder;
// 最高出价的价格
uint public highestBid;
// 这个map用来存放出价的人以及对应的出价,便于拍卖结束后退还
mapping(address => uint) pendingReturns;
//标识拍卖结束了,一旦结束就不能改了
bool ended;
payable表示这个地址可以接收以太币。因为存放的是受益人的地址当然是可以接受以太币的。uctionEndTime是一个时间戳变量,表示拍卖的结束时间。
代码语言:javascript复制// 用来记录当前出价最高的事件
event HighestBidIncreased(address bidder, uint amount);
// 用来记录拍卖结束后
event AuctionEnded(address winner, uint amount);
这里是定义两个事件,用来记录状态的变更。
代码语言:javascript复制/// 拍卖已经结束
error AuctionAlreadyEnded();
/// 已经有更高的出价者了
error BidNotHighEnough(uint highestBid);
/// 拍卖还未结束
error AuctionNotYetEnded();
/// auctionEnd 方法已经被调用了
error AuctionEndAlreadyCalled();
这里有几个之前没讲过的知识点,首先是注释用///
三斜杠,这样的注释可以用来生成文档(natSpec)。这里只是一个简单的示例,还可以定义的很复杂,比如:
/// @title A simulator for trees
/// @author Larry A. Gardner
/// @notice You can use this contract for only the most basic simulation
/// @dev All function calls are currently implemented without side effects
/// @custom:experimental This is an experimental contract.
contract Tree {
/// @notice Calculate tree age in years, rounded up, for live trees
/// @dev The Alexandr N. Tetearing algorithm could increase precision
/// @param rings The number of rings from dendrochronological sample
/// @return Age in years, rounded up for partial years
function age(uint256 rings) external virtual pure returns (uint256) {
return rings ;
}
/// @notice Returns the amount of leaves the tree has.
/// @dev Returns only a fixed number.
function leaves() external virtual pure returns(uint256) {
return ;
}
}
另外就是error
关键字,我们可以用error来定义一个错误,然后当某个条件满足时,我们再用revert
关键字报告一个错误,同时错误背后的原因通过natSpec做了解释。后面的代码会看到调用。
// 通过构造函数初始化受益人和拍卖的结束时间
constructor(
uint biddingTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress;
auctionEndTime = block.timestamp biddingTime;
}
block.timestamp
向合约提供当前区块的时间戳。
function bid() external payable {
if (block.timestamp > auctionEndTime)
revert AuctionAlreadyEnded();
if (msg.value <= highestBid)
revert BidNotHighEnough(highestBid);
if (highestBid != ) {
pendingReturns[highestBidder] = highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
拍卖的核心流程就是这个方法。同样payable
表示这个方法可以接收以太币。external
则表示这个方法是在外部被调用的,也就是被合约的用户通过接口调用。逻辑上也不复杂,pendingReturns会记录所有出价成功的人(注意不是拍卖成功)和他们的总出价。
同时,当前最高的出价人和出价会被当成日志记录在以太坊的区块链上,对这个日志感兴趣的人可以监听这个日志做一些事情。
代码语言:javascript复制 function withdraw() external returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > ) {
pendingReturns[msg.sender] = ;
if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
拍卖结束后,没有赢得最终拍卖的那些出价者需要有一个接口进行退款,就是这个withdraw
方法。payable(msg.sender).send
这句的意思是将以太币发送回调用者的地址。用payable修饰表示这个地址可以接收以太币。
function auctionEnd() external {
if (block.timestamp < auctionEndTime)
revert AuctionNotYetEnded();
if (ended)
revert AuctionEndAlreadyCalled();
ended = true;
emit AuctionEnded(highestBidder, highestBid);
beneficiary.transfer(highestBid);
}
调用这个方法可以结束拍卖。前面先进行检查,看是否满足结束的条件。如果满足就更新状态并且记录日志。最后就是把拍卖的钱转给受益人。
我们把这段程序放在remix运行下,看看效果。
首先传入一个结束时间和受益人地址进行初始化。
我这里设置的受益人地址说:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
获取下变量的初始值看看,
看着都没啥问题。我们现在模拟一次出价,点击bid
方法,然后value那里我们先默认用0,点击后发现报错:
[vm]from: 0x5B3...eddC4to: SimpleAuction.bid() 0xD7A...F771Bvalue: weidata: 0x199...8aeeflogs: hash: 0x9b7...a19d1
transact to SimpleAuction.bid errored: VM error: revert.
revert
The transaction has been reverted to the initial state.
Error provided by the contract:
BidNotHighEnough : 已经有更高的出价者了
Parameters:
{
"highestBid": {
"value": "0"
}
}
有没有注意到,我们通过注释的写的error的描述,在错误出现的时候打印出来了。这样验证了我们前面的内容。
我么首先让用户a(地址是:0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)出价11 wei,然后让用户b (地址是:0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db)出价 20 wei,你会看到出价成功后对应的地址上的余额就会减少(同时也会扣除gas fee)。
点击拍卖结束后,受益人的地址上余额就会增加。同时我们可以选择对应的没有拍卖成功的出价人的地址,然后点击withdraw,他的出价会返回来。
参考:
- https://docs.soliditylang.org/en/v0.8.10/solidity-by-example.html