-
Notifications
You must be signed in to change notification settings - Fork 7
/
PCOLicenseClaimerFacet.sol
451 lines (396 loc) · 15 KB
/
PCOLicenseClaimerFacet.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
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import "../libraries/LibPCOLicenseClaimer.sol";
import "../libraries/LibPCOLicenseParams.sol";
import "../../pco-license/interfaces/ICFABasePCO.sol";
import "../interfaces/IPCOLicenseParamsStore.sol";
import "../interfaces/IPCOLicenseClaimer.sol";
import {IERC721} from "@solidstate/contracts/interfaces/IERC721.sol";
import {BeaconDiamond} from "../../beacon-diamond/BeaconDiamond.sol";
import {IConstantFlowAgreementV1} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../beneficiary/interfaces/ICFABeneficiary.sol";
import {ERC721BaseInternal} from "@solidstate/contracts/token/ERC721/base/ERC721Base.sol";
import {IDiamondReadable} from "@solidstate/contracts/proxy/diamond/readable/IDiamondReadable.sol";
import {OwnableStorage} from "@solidstate/contracts/access/ownable/OwnableStorage.sol";
contract PCOLicenseClaimerFacetV1 is IPCOLicenseClaimerV1, ERC721BaseInternal {
using SafeERC20 for ISuperToken;
using OwnableStorage for OwnableStorage.Layout;
modifier onlyOwner() {
require(
msg.sender == OwnableStorage.layout().owner,
"Ownable: sender must be owner"
);
_;
}
/**
* @notice Initialize.
* - Must be the contract owner
* @param auctionStart start time of the genesis land parcel auction.
* @param auctionEnd when the required bid amount reaches its minimum value.
* @param startingBid start price of the genesis land auction. Decreases to endingBid between auctionStart and auctionEnd.
* @param endingBid the final/minimum required bid reached and maintained at the end of the auction.
* @param beacon The beacon contract for PCO licenses
*/
function initializeClaimer(
uint256 auctionStart,
uint256 auctionEnd,
uint256 startingBid,
uint256 endingBid,
address beacon
) external onlyOwner {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
ds.auctionStart = auctionStart;
ds.auctionEnd = auctionEnd;
ds.startingBid = startingBid;
ds.endingBid = endingBid;
ds.beacon = beacon;
}
/**
* @notice Admin can update the starting bid.
* @param startingBid The new starting bid
*/
function setStartingBid(uint256 startingBid) external onlyOwner {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
ds.startingBid = startingBid;
}
/// @notice Starting bid
function getStartingBid() external view returns (uint256) {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
return ds.startingBid;
}
/**
* @notice Admin can update the ending bid.
* @param endingBid The new ending bid
*/
function setEndingBid(uint256 endingBid) external onlyOwner {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
ds.endingBid = endingBid;
}
/// @notice Ending bid
function getEndingBid() external view returns (uint256) {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
return ds.endingBid;
}
/**
* @notice Admin can update the start time of the initial Dutch auction.
* @param auctionStart The new start time of the initial Dutch auction
*/
function setAuctionStart(uint256 auctionStart) external onlyOwner {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
ds.auctionStart = auctionStart;
}
/// @notice Auction start
function getAuctionStart() external view returns (uint256) {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
return ds.auctionStart;
}
/**
* @notice Admin can update the end time of the initial Dutch auction.
* @param auctionEnd The new end time of the initial Dutch auction
*/
function setAuctionEnd(uint256 auctionEnd) external onlyOwner {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
ds.auctionEnd = auctionEnd;
}
/// @notice Auction end
function getAuctionEnd() external view returns (uint256) {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
return ds.auctionEnd;
}
/**
* @notice Admin can update the beacon contract
* @param beacon The new beacon contract
*/
function setBeacon(address beacon) external onlyOwner {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
ds.beacon = beacon;
}
/// @notice Get Beacon
function getBeacon() external view returns (address) {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
return ds.beacon;
}
/**
* @notice The current dutch auction price of a parcel.
*/
function requiredBid() external view returns (uint256) {
return LibPCOLicenseClaimer._requiredBid();
}
/**
* @notice Get beacon proxy for license
* @param licenseId License ID
*/
function getBeaconProxy(uint256 licenseId) external view returns (address) {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
return ds.beaconProxies[licenseId];
}
/**
* @notice Get the next proxy address for user. To be used to grant permissions before calling claim
* @param user User address
*/
function getNextProxyAddress(address user) public view returns (address) {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
return
address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
keccak256(
abi.encodePacked(user, ds.userSalts[user])
),
keccak256(
abi.encodePacked(
type(BeaconDiamond).creationCode,
abi.encode(
address(this),
IDiamondReadable(ds.beacon)
)
)
)
)
)
)
)
);
}
/**
* @notice Claim a new parcel and license
* - Must have ERC-20 approval of payment token
* - To-be-created contract must have create flow permissions for bidder. See getNextProxyAddress
* @param initialContributionRate Initial contribution rate of parcel
* @param initialForSalePrice Initial for sale price of parcel
* @param baseCoordinate Base coordinate of new parcel
* @param path Path of new parcel
*/
function claim(
int96 initialContributionRate,
uint256 initialForSalePrice,
uint64 baseCoordinate,
uint256[] memory path
) external {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
LibPCOLicenseParams.DiamondStorage storage ls = LibPCOLicenseParams
.diamondStorage();
uint256 _requiredBid = LibPCOLicenseClaimer._requiredBid();
require(
initialForSalePrice >= _requiredBid,
"PCOLicenseClaimerFacet: Initial for sale price does not meet requirement"
);
uint256 licenseId = LibGeoWebParcel.nextId();
BeaconDiamond proxy = new BeaconDiamond{
salt: keccak256(
abi.encodePacked(msg.sender, ds.userSalts[msg.sender])
)
}(address(this), IDiamondReadable(ds.beacon));
// Increment user salt
ds.userSalts[msg.sender] += 1;
// Store beacon proxy
ds.beaconProxies[licenseId] = address(proxy);
emit ParcelClaimed(licenseId, msg.sender);
{
// Transfer required buffer
IConstantFlowAgreementV1 cfa = IConstantFlowAgreementV1(
address(
ls.host.getAgreementClass(
keccak256(
"org.superfluid-finance.agreements.ConstantFlowAgreement.v1"
)
)
)
);
uint256 requiredBuffer = cfa.getDepositRequiredForFlowRate(
ls.paymentToken,
initialContributionRate
);
ls.paymentToken.safeTransferFrom(
msg.sender,
address(proxy),
requiredBuffer
);
}
// Initialize beacon
ICFABasePCO(address(proxy)).initializeBid(
ls.beneficiary,
IPCOLicenseParamsStore(address(this)),
IERC721(address(this)),
licenseId,
msg.sender,
initialContributionRate,
initialForSalePrice
);
// Transfer initial payment
if (_requiredBid > 0) {
ls.paymentToken.safeTransferFrom(
msg.sender,
address(ls.beneficiary),
_requiredBid
);
}
// Build and mint (reentrancy on ERC721 transfer)
_buildAndMint(msg.sender, baseCoordinate, path);
}
/**
* @notice Build a parcel and mint a license
* @param user Address of license owner to be
* @param baseCoordinate Base coordinate of parcel to claim
* @param path Path of parcel to claim
*/
function _buildAndMint(
address user,
uint64 baseCoordinate,
uint256[] memory path
) internal {
uint256 licenseId = LibGeoWebParcel.nextId();
LibGeoWebParcel.build(baseCoordinate, path);
_safeMint(user, licenseId);
}
}
contract PCOLicenseClaimerFacetV2 is
PCOLicenseClaimerFacetV1,
IPCOLicenseClaimerV2
{
using SafeERC20 for ISuperToken;
/**
* @notice Claim a new parcel and license
* - Must have ERC-20 approval of payment token
* - To-be-created contract must have create flow permissions for bidder. See getNextProxyAddress
* @param initialContributionRate Initial contribution rate of parcel
* @param initialForSalePrice Initial for sale price of parcel
* @param parcel New parcel
*/
function claim(
int96 initialContributionRate,
uint256 initialForSalePrice,
LibGeoWebParcelV2.LandParcel memory parcel
) external {
_claim(
initialContributionRate,
initialForSalePrice,
parcel,
new bytes(0)
);
}
/**
* @notice Claim a new parcel and license with content hash
* - Must have ERC-20 approval of payment token
* - To-be-created contract must have create flow permissions for bidder. See getNextProxyAddress
* @param initialContributionRate Initial contribution rate of parcel
* @param initialForSalePrice Initial for sale price of parcel
* @param parcel New parcel
* @param contentHash Content hash for parcel content
*/
function claim(
int96 initialContributionRate,
uint256 initialForSalePrice,
LibGeoWebParcelV2.LandParcel memory parcel,
bytes calldata contentHash
) external {
_claim(
initialContributionRate,
initialForSalePrice,
parcel,
contentHash
);
}
function _claim(
int96 initialContributionRate,
uint256 initialForSalePrice,
LibGeoWebParcelV2.LandParcel memory parcel,
bytes memory contentHash
) internal {
LibPCOLicenseClaimer.DiamondStorage storage ds = LibPCOLicenseClaimer
.diamondStorage();
LibPCOLicenseParams.DiamondStorage storage ls = LibPCOLicenseParams
.diamondStorage();
{
require(
initialForSalePrice >= LibPCOLicenseClaimer._requiredBid(),
"PCOLicenseClaimerFacetV2: Initial for sale price does not meet requirement"
);
}
uint256 licenseId = LibGeoWebParcel.nextId();
BeaconDiamond proxy = new BeaconDiamond{
salt: keccak256(
abi.encodePacked(msg.sender, ds.userSalts[msg.sender])
)
}(address(this), IDiamondReadable(ds.beacon));
// Increment user salt
ds.userSalts[msg.sender] += 1;
// Store beacon proxy
ds.beaconProxies[licenseId] = address(proxy);
emit ParcelClaimedV2(licenseId, msg.sender);
{
// Transfer required buffer
IConstantFlowAgreementV1 cfa = IConstantFlowAgreementV1(
address(
ls.host.getAgreementClass(
keccak256(
"org.superfluid-finance.agreements.ConstantFlowAgreement.v1"
)
)
)
);
uint256 requiredBuffer = cfa.getDepositRequiredForFlowRate(
ls.paymentToken,
initialContributionRate
);
ls.paymentToken.safeTransferFrom(
msg.sender,
address(proxy),
requiredBuffer
);
}
// Initialize beacon
ICFABasePCO(address(proxy)).initializeBid(
ls.beneficiary,
IPCOLicenseParamsStore(address(this)),
IERC721(address(this)),
licenseId,
msg.sender,
initialContributionRate,
initialForSalePrice,
contentHash
);
// Transfer initial payment
if (LibPCOLicenseClaimer._requiredBid() > 0) {
ls.paymentToken.safeTransferFrom(
msg.sender,
address(ls.beneficiary),
LibPCOLicenseClaimer._requiredBid()
);
}
// Build and mint (reentrancy on ERC721 transfer)
_buildAndMint(msg.sender, parcel);
}
/**
* @notice Build a parcel and mint a license
* @param user Address of license owner to be
* @param parcel New parcel
*/
function _buildAndMint(
address user,
LibGeoWebParcelV2.LandParcel memory parcel
) internal {
uint256 licenseId = LibGeoWebParcel.nextId();
LibGeoWebParcelV2.build(parcel);
_safeMint(user, licenseId);
}
}