forked from dragonfly-xyz/useful-solidity-patterns
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathOffChainAuction.sol
135 lines (126 loc) · 5.14 KB
/
OffChainAuction.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Creates auctions for an ERC721 NFT with off-chain storage for auction state.
contract OffChainAuction {
// Per-auction state that will be kept off-chain.
struct AuctionState {
// ERC721 token contract of the NFT being auctioned.
IERC721 token;
// ERC721 token ID of the NFT being auctioned.
uint256 tokenId;
// Who created the auction.
address payable owner;
// When the auction was created.
uint256 created;
// When the first bid was placed.
uint256 started;
// How long the auction has before it expires (no bids) and how long
// to extend it once someone places the first bid.
uint256 duration;
// The current top bid. Also the minimum bid if topBidder is 0.
uint256 topBid;
// The current top bidder. 0 if no bidders.
address payable topBidder;
}
event Created(uint256 auctionId, AuctionState state);
event Bid(uint256 auctionId, AuctionState newState);
event Settled(uint256 auctionId, uint256 topBid);
event Expired(uint256 auctionId);
// Maps an auction ID to its current state hash (hash of AuctionState).
mapping (uint256 => bytes32) public auctionStateHashes;
uint256 _lastAuctionId;
// Requires the auction state to match an auction identified by `auctionId`.
modifier onlyValidAuction(uint256 auctionId, AuctionState memory state) {
require(auctionStateHashes[auctionId] == _hashAuctionState(state), 'invalid auction');
_;
}
// Create a new auction.
function createAuction(IERC721 token, uint256 tokenId, uint256 minBid, uint256 duration)
external
returns (uint256 auctionId, AuctionState memory state)
{
require(minBid != 0, 'invalid minimum bid');
// Take custody of the NFT.
token.transferFrom(msg.sender, address(this), tokenId);
// Create the initial state for this auction.
state.token = token;
state.tokenId = tokenId;
state.owner = payable(msg.sender);
state.created = block.timestamp;
state.topBid = minBid;
state.duration = duration;
auctionId = _lastAuctionId++;
// Store ONLY the hash of the initial state for this auction on-chain.
auctionStateHashes[auctionId] = _hashAuctionState(state);
emit Created(auctionId, state);
}
// Place a bid on an active auction.
function bid(uint256 auctionId, AuctionState memory state)
external
payable
onlyValidAuction(auctionId, state)
returns (AuctionState memory)
{
if (state.started == 0) {
require(state.created + state.duration > block.timestamp, 'expired');
} else {
require(state.started + state.duration > block.timestamp, 'concluded');
}
uint256 currTopBid = state.topBid;
address payable currTopBidder = state.topBidder;
if (currTopBidder == address(0)) {
// Auction hasn't started yet (no bids).
require(msg.value >= currTopBid, 'bid too low');
state.started = block.timestamp;
} else {
// Auction is in progress.
require(msg.value > currTopBid, 'bid too low');
}
state.topBid = msg.value;
state.topBidder = payable(msg.sender);
// Update the on-chain state hash to reflect the new state values.
auctionStateHashes[auctionId] = _hashAuctionState(state);
// If there was a previous bidder, refund them their bid.
if (currTopBidder != address(0)) {
!!currTopBidder.send(currTopBid);
}
// Emit and return the updated state for future interactions.
emit Bid(auctionId, state);
return state;
}
// Settle an auction that is either expired or concluded.
function settle(uint256 auctionId, AuctionState calldata state)
external
onlyValidAuction(auctionId, state)
{
// Clear state hash to prevent reentrancy.
delete auctionStateHashes[auctionId];
if (state.started != 0) {
// Auction completed.
require(state.started + state.duration <= block.timestamp, 'not concluded');
// Send top bid to auction creator.
!!state.owner.send(state.topBid);
// Send NFT to auction top bidder.
state.token.transferFrom(address(this), state.topBidder, state.tokenId);
emit Settled(auctionId, state.topBid);
return;
}
// Auction expired (no bids).
require(state.created + state.duration <= block.timestamp, 'not expired');
// Return NFT to auction creator.
state.token.transferFrom(address(this), state.owner, state.tokenId);
emit Expired(auctionId);
}
// Compute the hash of an auction state object.
function _hashAuctionState(AuctionState memory state)
private
pure
returns (bytes32 hash)
{
return keccak256(abi.encode(state));
}
}
// Minimal ERC721 interface.
interface IERC721 {
function transferFrom(address owner, address to, uint256 tokenId) external;
}