forked from dragonfly-xyz/useful-solidity-patterns
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNftReceiveHooksAuction.sol
142 lines (132 loc) · 4.78 KB
/
NftReceiveHooksAuction.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// A simple ERC721 auction house that uses the `onERC721Received()` hook
// to create auctions with a single transfer transaction.
contract NftReceiveHooksAuction {
struct Auction {
address payable seller;
IERC721 token;
uint256 tokenId;
uint256 topBid;
address payable topBidder;
uint256 endTime;
}
event AuctionCreated(
bytes32 id,
address seller,
IERC721 token,
uint256 tokenId,
uint256 reservePrice,
uint256 endTime
);
event Bid(bytes32 auctionId, address bidder, uint256 amount);
event Settled(bytes32 auctionId, address topBidder, uint256 topBid);
mapping (bytes32 => Auction) public auctionsById;
// The ERC721 receive hook, called by the token contract when
// the token is transferred to us. Creates an auction with properly
// encoded `data`.
function onERC721Received(
address /* operator */,
address payable from,
uint256 tokenId,
bytes memory data
)
external
returns (bytes4)
{
// The caller will always be the
// `data` should be encoded as
// `abi.encode(uint256(reservePrice), uint256(duration))`.
(uint256 reservePrice, uint256 duration) =
abi.decode(data, (uint256, uint256));
_createListing(from, IERC721(msg.sender), tokenId, reservePrice, duration);
return this.onERC721Received.selector;
}
// Bid on an auction.
function bid(bytes32 auctionId) external payable {
Auction storage auction = auctionsById[auctionId];
{
// Make sure the auction is not expired. This will
// also enforce that the auction exists.
uint256 endTime = auction.endTime;
require(endTime > block.timestamp, 'auction over');
}
address payable topBidder = auction.topBidder;
uint256 topBid = auction.topBid;
require(address(topBidder) != msg.sender, 'already top bidder');
require(topBid < msg.value, 'bid too low');
auction.topBid = msg.value;
auction.topBidder = payable(msg.sender);
// Transfer the last top bid to the last top bidder.
_pay(topBidder, topBid);
emit Bid(auctionId, msg.sender, msg.value);
}
// Settle an expired or won auction.
function settle(bytes32 auctionId) external {
Auction memory auction = auctionsById[auctionId];
// Auction must exist and be over.
require(auction.seller != address(0), 'invalid auction');
require(auction.endTime <= block.timestamp, 'not over');
// Clear the auction.
delete auctionsById[auctionId];
if (auction.topBidder == address(0)) {
// No one bid. Return NFT to seller.
_sendNft(auction.seller, auction.token, auction.tokenId);
} else { // We have a winner.
// Pay the seller.
_pay(auction.seller, auction.topBid);
// Transfer the NFT to the top bidder.
_sendNft(auction.topBidder, auction.token, auction.tokenId);
}
emit Settled(
auctionId,
auction.topBidder,
auction.topBidder == address(0) ? 0 : auction.topBid
);
}
function _createListing(
address payable owner,
IERC721 token,
uint256 tokenId,
uint256 reservePrice,
uint256 duration
)
private
{
require(reservePrice > 0, 'invalid reserve price');
require(duration > 0, 'invalid duration');
bytes32 id = keccak256(abi.encode(token, tokenId));
uint256 endTime = duration + block.timestamp;
auctionsById[id] = Auction({
seller: owner,
token: token,
tokenId: tokenId,
topBid: reservePrice - 1,
topBidder: payable(0),
endTime: endTime
});
emit AuctionCreated(
id,
owner,
token,
tokenId,
reservePrice,
endTime
);
}
function _pay(address payable to, uint256 amount) private {
// TODO: Properly handle failure and wrap to WETH or defer to
// a withdraw() function if so.
!!to.send(amount);
}
function _sendNft(address to, IERC721 token, uint256 tokenId) private {
// TODO: Use `safeTransferFrom()` instead and handle failure,
// possibly deferring to a withdrawNft() function if so.
token.transferFrom(address(this), to, tokenId);
}
}
// Minimal ERC721 interface.
interface IERC721 {
function safeTransferFrom(address owner, address to, uint256 tokenId, bytes memory data) external;
function transferFrom(address owner, address to, uint256 tokenId) external;
}