-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathNightMarket.sol
More file actions
294 lines (256 loc) · 8.95 KB
/
NightMarket.sol
File metadata and controls
294 lines (256 loc) · 8.95 KB
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
// Zk verifiers
import {IVerifier as IListVerifier} from "./ListVerifier.sol";
import {IVerifier as ISaleVerifier} from "./SaleVerifier.sol";
// DF interface imports
import {WithStorage, SnarkConstants, GameConstants} from "./darkforest/LibStorage.sol";
// DF type imports
import {RevealedCoords, PlanetExtendedInfo, PlanetData} from "./darkforest/DFTypes.sol";
// A (reduced) interface for DFGetterFacet
import {IGetter} from "./darkforest/GetterInterface.sol";
/**
* @dev Converts boolean to int
*/
function boolToUInt(bool x) pure returns (uint256 r) {
assembly {
r := x
}
}
/**
* @title NightMarket
* @author @0xSage
* @notice
* @dev
* @custom:experimental
*/
contract NightMarket is ReentrancyGuard {
using SafeMath for uint256;
struct Order {
address payable buyer;
uint256 expectedSharedKeyHash;
uint256 created;
bool isActive;
}
struct Listing {
address payable seller;
uint256 keyCommitment;
uint256 price;
uint256 escrowTime;
uint256 numOrders;
bool isActive;
mapping(uint256 => Order) orders;
}
uint256 public numListings;
mapping(uint256 => Listing) public listings;
// DF storage getter
IGetter public immutable df;
// Game Constants
SnarkConstants public zkConstants;
// Verifiers
IListVerifier public immutable listVerifier;
ISaleVerifier public immutable saleVerifier;
// Events
event Contract(uint256 planetHash, uint256 SpacetypeHash);
event Listed(
address indexed seller,
uint256 indexed listingId,
uint256 indexed locationId,
uint256 biombase,
uint256 nonce
);
event Delisted(address indexed seller, uint256 indexed listingId);
event Asked(address indexed buyer, uint256 indexed listingId);
event Sold(uint256 indexed listingId, uint256 orderId, uint256 nonce);
event Refunded(uint256 indexed listingId, uint256 orderId);
/**
* @notice Checks planet is in game and not revealed yet
* @param locationId The hash of xy coordinates
*/
modifier validPlanet(uint256 locationId) {
require(
df.planetsExtendedInfo(locationId).isInitialized,
"Planet doesn't exit or is not initialized"
);
require(
df.revealedCoords(locationId).locationId != locationId,
"Planet coordinates have already been revealed"
);
_;
}
/**
* @dev The constructor
* @param _listVerifier the address of SNARK List Verifier for this contract
* @param _saleVerifier the address of SNARK Sale Verifier for this contract
* @param _gameContract the address of the Dark Forest game
*/
constructor(
IListVerifier _listVerifier,
ISaleVerifier _saleVerifier,
address _gameContract
) {
listVerifier = _listVerifier;
saleVerifier = _saleVerifier;
df = IGetter(_gameContract);
zkConstants = df.getSnarkConstants();
emit Contract(zkConstants.PLANETHASH_KEY, zkConstants.SPACETYPE_KEY);
}
/**
* @notice Seller can list a secret Dark Forest coordinate for sale
* @dev Seller generates `_proof` offchain in `list.circom`.
* @param _proof The listing_id proof from seller
* @param _coordEncryption The pre-encrypted coordinates from seller
* @return listingId Enumerates
*/
function list(
uint256[8] memory _proof,
uint256[4] memory _coordEncryption,
uint256 _nonce,
uint256 _keyCommitment,
uint256 _locationId,
uint256 _biomebase,
uint256 _price,
uint256 _escrowTime
) external validPlanet(_locationId) returns (uint256 listingId) {
require(_nonce < 2**128, "Nonce must be smaller than 2^128");
uint256[15] memory publicInputs = [
zkConstants.PLANETHASH_KEY,
zkConstants.BIOMEBASE_KEY,
zkConstants.SPACETYPE_KEY,
zkConstants.PERLIN_LENGTH_SCALE,
boolToUInt(zkConstants.PERLIN_MIRROR_X),
boolToUInt(zkConstants.PERLIN_MIRROR_Y),
_coordEncryption[0],
_coordEncryption[1],
_coordEncryption[2],
_coordEncryption[3],
_nonce,
_keyCommitment,
_locationId,
_biomebase,
uint256(uint160(address(msg.sender)))
];
require(
listVerifier.verify(_proof, publicInputs),
"Seller list coordinates: invalid proof"
);
listingId = numListings++;
Listing storage l = listings[listingId];
l.seller = payable(msg.sender);
l.keyCommitment = _keyCommitment;
l.price = _price;
l.escrowTime = _escrowTime;
l.isActive = true;
emit Listed(msg.sender, listingId, _locationId, _biomebase, _nonce);
}
/**
* @notice Seller can delist an active listing
* @dev Sellers who care about reputation will use this fn, otherwise, unlikely
* @param _listingId the ID from list() step
*/
function delist(uint256 _listingId) external {
Listing storage l = listings[_listingId];
require(l.isActive, "Listing is already inactive");
require(msg.sender == l.seller, "Only seller can delist their listing");
l.isActive = false;
emit Delisted(msg.sender, _listingId);
}
/**
* @notice Buyer can ask for order(s) from active listings
* @dev A listing can have multiple orders from same buyer
* @param _expectedSharedKeyHash A H(ecdh(buyerPrivKey, sellerPubKey)) computed by Buyer
* @return orderId Buyer keeps this for future refunds
*/
function ask(uint256 _listingId, uint256 _expectedSharedKeyHash)
external
payable
returns (uint256 orderId)
{
Listing storage l = listings[_listingId];
require(l.isActive, "Listing is no longer active");
require(msg.value == l.price, "Payment is incorrect");
l.orders[l.numOrders++] = Order({
buyer: payable(msg.sender),
expectedSharedKeyHash: _expectedSharedKeyHash,
created: block.number,
isActive: true
});
emit Asked(msg.sender, _listingId);
return l.numOrders;
}
/**
* @notice Seller can submit a proof of sale.
* @dev Seller generates `_proof` offchain in `sale.circom`
* @dev Seller ensures he can derive buyer's `sharedKeyCommitment`
* before submitting the sale trx
*/
function sale(
uint256[8] memory _proof,
uint256[4] memory _keyEncryption,
uint256 _nonce,
uint256 _listingId,
uint256 _orderId
) external nonReentrant {
Listing storage l = listings[_listingId];
require(l.seller == msg.sender, "Only seller can close sale");
require(l.isActive, "Listing is inactive");
require(_nonce < 2 ** 218, "Nonce must be smaller than 2^218");
Order storage o = l.orders[_orderId];
require(o.isActive, "Order is inactive");
uint256[7] memory publicInputs = [
_keyEncryption[0],
_keyEncryption[1],
_keyEncryption[2],
_keyEncryption[3],
_nonce,
l.keyCommitment,
o.expectedSharedKeyHash
];
require(
saleVerifier.verify(_proof, publicInputs),
"sale proof invalid"
);
o.isActive = false;
l.seller.transfer(l.price);
emit Sold(_listingId, _orderId, _nonce);
}
/**
* @notice Anyone can request a refund on a qualifying order
* @dev Seller can also force refund (spam) buyers who submitted invalid expectedSharedKeyHash
* @param _listingId the ID from list() step
* @param _orderId the ID from ask() step
*/
function refund(uint256 _listingId, uint256 _orderId) public nonReentrant {
//orderid, callable by anyone? or just buyer... becareful of contract buyers
Listing storage l = listings[_listingId];
Order storage o = l.orders[_orderId];
require(o.isActive, "Order previously refunded");
require(
_escrowExpired(o.created, l.escrowTime) || !l.isActive,
"Order not refundable at this time"
);
o.isActive = false;
o.buyer.transfer(l.price);
emit Refunded(_listingId, _orderId);
}
/**
* @notice Returns an order from listing
*/
function getOrder(uint256 listingId, uint256 orderId)
public
view
returns (Order memory)
{
return listings[listingId].orders[orderId];
}
function _escrowExpired(uint256 _created, uint256 _escrowTime)
internal
view
returns (bool)
{
uint256 elapsed = block.number.sub(_escrowTime);
return (elapsed > _created);
}
}