@ballkidz/defifa 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/AUDIT_INSTRUCTIONS.md +6 -2
  2. package/README.md +11 -2
  3. package/RISKS.md +3 -1
  4. package/STYLE_GUIDE.md +14 -11
  5. package/package.json +31 -14
  6. package/script/Deploy.s.sol +4 -1
  7. package/src/DefifaDeployer.sol +79 -47
  8. package/src/DefifaGovernor.sol +57 -12
  9. package/src/DefifaHook.sol +83 -26
  10. package/src/DefifaProjectOwner.sol +4 -3
  11. package/src/DefifaTokenUriResolver.sol +113 -20
  12. package/src/enums/DefifaGamePhase.sol +6 -0
  13. package/src/enums/DefifaScorecardState.sol +4 -0
  14. package/src/interfaces/IDefifaDeployer.sol +5 -0
  15. package/src/interfaces/IDefifaGamePhaseReporter.sol +4 -0
  16. package/src/interfaces/IDefifaGamePotReporter.sol +10 -0
  17. package/src/interfaces/IDefifaGovernor.sol +4 -0
  18. package/src/interfaces/IDefifaHook.sol +5 -0
  19. package/src/interfaces/IDefifaTokenUriResolver.sol +3 -0
  20. package/src/libraries/DefifaFontImporter.sol +1 -1
  21. package/src/libraries/DefifaHookLib.sol +9 -10
  22. package/src/structs/DefifaAttestations.sol +3 -2
  23. package/src/structs/DefifaDelegation.sol +1 -0
  24. package/src/structs/DefifaLaunchProjectData.sol +2 -3
  25. package/src/structs/DefifaOpsData.sol +1 -0
  26. package/src/structs/DefifaScorecard.sol +2 -0
  27. package/src/structs/DefifaTierCashOutWeight.sol +3 -1
  28. package/src/structs/DefifaTierParams.sol +1 -0
  29. package/CRYPTO_ECON.pdf +0 -0
  30. package/CRYPTO_ECON.tex +0 -997
  31. package/foundry.lock +0 -17
  32. package/references/operations.md +0 -32
  33. package/references/runtime.md +0 -43
  34. package/slither-ci.config.json +0 -10
  35. package/sphinx.lock +0 -521
  36. package/test/BWAFunctionComparison.t.sol +0 -1320
  37. package/test/DefifaAdversarialQuorum.t.sol +0 -617
  38. package/test/DefifaAuditLowGuards.t.sol +0 -308
  39. package/test/DefifaFeeAccounting.t.sol +0 -581
  40. package/test/DefifaGovernanceHardening.t.sol +0 -1315
  41. package/test/DefifaGovernor.t.sol +0 -1378
  42. package/test/DefifaHookRegressions.t.sol +0 -415
  43. package/test/DefifaMintCostInvariant.t.sol +0 -319
  44. package/test/DefifaNoContest.t.sol +0 -941
  45. package/test/DefifaSecurity.t.sol +0 -741
  46. package/test/DefifaUSDC.t.sol +0 -480
  47. package/test/Fork.t.sol +0 -2388
  48. package/test/TestAuditGaps.sol +0 -984
  49. package/test/TestQALastMile.t.sol +0 -514
  50. package/test/audit/AttestationDoubleCount.t.sol +0 -218
  51. package/test/audit/CodexNemesisCurrencyMismatchBypass.t.sol +0 -112
  52. package/test/audit/CodexNemesisNoContestReserveDrain.t.sol +0 -238
  53. package/test/audit/CodexNemesisOneTierZeroTimeoutLockVerified.t.sol +0 -218
  54. package/test/audit/CodexNemesisSingleTierTimeoutLock.t.sol +0 -237
  55. package/test/audit/CodexRegistryMismatch.t.sol +0 -191
  56. package/test/audit/CodexTierCapMismatch.t.sol +0 -171
  57. package/test/audit/CurrencyMismatchFix.t.sol +0 -265
  58. package/test/audit/FixPendingReserveDilution.t.sol +0 -366
  59. package/test/audit/H5TierCapValidation.t.sol +0 -184
  60. package/test/audit/PendingReserveDilution.t.sol +0 -298
  61. package/test/audit/PendingReserveQuorumGrief.t.sol +0 -355
  62. package/test/audit/PendingReserveSnapshotBypass.t.sol +0 -319
  63. package/test/regression/AttestationDelegateBeneficiary.t.sol +0 -271
  64. package/test/regression/FulfillmentBlocksRatification.t.sol +0 -279
  65. package/test/regression/GracePeriodBypass.t.sol +0 -302
@@ -1,1378 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
5
-
6
- import {DefifaGovernor} from "../src/DefifaGovernor.sol";
7
- import {DefifaDeployer} from "../src/DefifaDeployer.sol";
8
- import {DefifaHook} from "../src/DefifaHook.sol";
9
- import {DefifaTokenUriResolver} from "../src/DefifaTokenUriResolver.sol";
10
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
11
-
12
- import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
13
- import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
14
- import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
15
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
16
-
17
- import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
18
- import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
19
- import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
20
- import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
21
- import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
22
- import {DefifaDelegation} from "../src/structs/DefifaDelegation.sol";
23
- import {DefifaLaunchProjectData} from "../src/structs/DefifaLaunchProjectData.sol";
24
- import {DefifaTierParams} from "../src/structs/DefifaTierParams.sol";
25
- import {DefifaTierCashOutWeight} from "../src/structs/DefifaTierCashOutWeight.sol";
26
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
27
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
28
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
29
- import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
30
- import {JBMultiTerminal} from "@bananapus/core-v6/src/JBMultiTerminal.sol";
31
- import {JBRulesetConfig, JBTerminalConfig} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
32
- import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
33
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
34
- import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
35
- import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
36
- import {JBTerminalStore} from "@bananapus/core-v6/src/JBTerminalStore.sol";
37
-
38
- /// @dev Helper to read block.timestamp via an external call, bypassing the via-ir optimizer's timestamp caching.
39
- contract TimestampReader {
40
- function timestamp() external view returns (uint256) {
41
- return block.timestamp;
42
- }
43
- }
44
-
45
- contract DefifaGovernorTest is JBTest, TestBaseWorkflow {
46
- using JBRulesetMetadataResolver for JBRuleset;
47
-
48
- TimestampReader private _tsReader = new TimestampReader();
49
-
50
- address _protocolFeeProjectTokenAccount;
51
- address _defifaProjectTokenAccount;
52
-
53
- uint256 _protocolFeeProjectId;
54
- uint256 _defifaProjectId;
55
- address _owner = 0x1000000000000000000000000000000000000000;
56
- uint256 _gameId = 3;
57
-
58
- DefifaDeployer deployer;
59
- DefifaHook hook;
60
- DefifaGovernor governor;
61
-
62
- address projectOwner = address(bytes20(keccak256("projectOwner")));
63
-
64
- function setUp() public virtual override {
65
- super.setUp();
66
-
67
- // Terminal configurations.
68
- JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
69
- _tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
70
-
71
- JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
72
- terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
73
-
74
- JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
75
- rulesetConfigs[0] = JBRulesetConfig({
76
- mustStartAtOrAfter: 0,
77
- duration: 10 days,
78
- weight: 1e18,
79
- weightCutPercent: 0,
80
- approvalHook: IJBRulesetApprovalHook(address(0)),
81
- metadata: JBRulesetMetadata({
82
- reservedPercent: 0,
83
- cashOutTaxRate: 0,
84
- baseCurrency: JBCurrencyIds.ETH,
85
- pausePay: false,
86
- pauseCreditTransfers: false,
87
- allowOwnerMinting: false,
88
- allowSetCustomToken: false,
89
- allowTerminalMigration: false,
90
- allowSetTerminals: false,
91
- allowSetController: false,
92
- allowAddAccountingContext: false,
93
- allowAddPriceFeed: false,
94
- ownerMustSendPayouts: false,
95
- holdFees: false,
96
- useTotalSurplusForCashOuts: false,
97
- useDataHookForPay: true,
98
- useDataHookForCashOut: true,
99
- dataHook: address(0),
100
- metadata: 0
101
- }),
102
- splitGroups: new JBSplitGroup[](0),
103
- fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
104
- });
105
-
106
- // Launch the NANA fee project.
107
- _protocolFeeProjectId =
108
- jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
109
- vm.prank(projectOwner);
110
- _protocolFeeProjectTokenAccount =
111
- address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
112
-
113
- // Launch the Defifa fee project.
114
- _defifaProjectId =
115
- jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
116
- vm.prank(projectOwner);
117
- _defifaProjectTokenAccount =
118
- address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
119
-
120
- hook = new DefifaHook(
121
- jbDirectory(), IERC20(address(_defifaProjectTokenAccount)), IERC20(_protocolFeeProjectTokenAccount)
122
- );
123
- governor = new DefifaGovernor(jbController(), address(this));
124
- JBAddressRegistry _registry = new JBAddressRegistry();
125
- DefifaTokenUriResolver _tokenUriResolver = new DefifaTokenUriResolver(ITypeface(address(0)));
126
- deployer = new DefifaDeployer(
127
- address(hook),
128
- _tokenUriResolver,
129
- governor,
130
- jbController(),
131
- _registry,
132
- _defifaProjectId,
133
- _protocolFeeProjectId
134
- );
135
-
136
- // Transfer ownership of the hook to the deployer.
137
- hook.transferOwnership(address(deployer));
138
- // Transfer ownership of the governor to the deployer.
139
- governor.transferOwnership(address(deployer));
140
- }
141
-
142
- function testReceiveVotingPower(uint8 nTiers, uint8 tier) public {
143
- vm.assume(nTiers < 100);
144
- vm.assume(nTiers >= tier);
145
- vm.assume(tier != 0);
146
- address _user = address(bytes20(keccak256("user")));
147
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
148
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
149
-
150
- // Phase 1: Mint
151
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
152
- //deployer.queueNextPhaseOf(_projectId);
153
- // User should have no voting power (yet)
154
- assertEq(_governor.getAttestationWeight(_gameId, _user, uint48(block.timestamp)), 0);
155
- // fund user
156
- vm.deal(_user, 1 ether);
157
- // Build metadata to buy specific NFT
158
- uint16[] memory rawMetadata = new uint16[](1);
159
- vm.assume(tier != 0);
160
- rawMetadata[0] = uint16(tier); // reward tier
161
-
162
- // Pay to the project and mint an NFT
163
- vm.prank(_user);
164
- jbMultiTerminal().pay{value: 1 ether}(
165
- _projectId,
166
- JBConstants.NATIVE_TOKEN,
167
- 1 ether,
168
- _user,
169
- 0,
170
- "",
171
- _buildPayMetadata(abi.encode(_user, rawMetadata))
172
- );
173
-
174
- // The user should now have a balance
175
- assertEq(_nft.balanceOf(_user), 1);
176
-
177
- // Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
178
- vm.warp(block.timestamp + 1);
179
-
180
- assertEq(_nft.store().tierOf(address(_nft), tier, false).votingUnits, 1 ether);
181
- assertEq(
182
- _governor.MAX_ATTESTATION_POWER_TIER(),
183
- _governor.getAttestationWeight(_gameId, _user, uint48(block.timestamp))
184
- );
185
- }
186
-
187
- // cashOuts can happen after mint phase
188
- // function testRefund_fails_afterMintPhase() external {
189
- // uint8 nTiers = 10;
190
- // address[] memory _users = new address[](nTiers);
191
- // DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
192
- // (uint256 _projectId, , ) = createDefifaProject(defifaData);
193
- // // Phase 1: Mint
194
- // vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
195
- // //deployer.queueNextPhaseOf(_projectId);
196
- // for (uint256 i = 0; i < nTiers; i++) {
197
- // // Generate a new address for each tier
198
- // _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i)))));
199
- // // fund user
200
- // vm.deal(_users[i], 1 ether);
201
- // // Build metadata to buy specific NFT
202
- // uint16[] memory rawMetadata = new uint16[](1);
203
- // rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
204
- // bytes memory metadata = abi.encode(
205
- // bytes32(0),
206
- // bytes32(0),
207
- // type(IDefifaHook).interfaceId,
208
- // _users[i],
209
- // rawMetadata
210
- // );
211
- // // Pay to the project and mint an NFT
212
- // vm.prank(_users[i]);
213
- // jbMultiTerminal().pay{value: 1 ether}(
214
- // _projectId,
215
- // 1 ether,
216
- // address(0),
217
- // _users[i],
218
- // 0,
219
- // true,
220
- // '',
221
- // metadata
222
- // );
223
- // }
224
- // // Phase 2: Redeem
225
- // vm.warp(block.timestamp + defifaData.mintPeriodDuration);
226
- // //deployer.queueNextPhaseOf(_projectId);
227
- // // Phase 3: Start
228
- // vm.warp(defifaData.start + 1);
229
- // //deployer.queueNextPhaseOf(_projectId);
230
- // // Make sure this is actually Phase 3
231
- // assertEq(jbRulesets().currentOf(_projectId).number, 3);
232
- // for (uint256 i = 0; i < _users.length; i++) {
233
- // address _user = _users[i];
234
- // // Craft the metadata: redeem the tokenId
235
- // bytes memory cashOutMetadata;
236
- // {
237
- // uint256[] memory cashOutId = new uint256[](1);
238
- // cashOutId[0] = _generateTokenId(i + 1, 1);
239
- // cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutId);
240
- // }
241
- // vm.expectRevert(abi.encodeWithSignature('FUNDING_CYCLE_REDEEM_PAUSED()'));
242
- // vm.prank(_user);
243
- // JBMultiTerminal(address(jbMultiTerminal())).redeemTokensOf({
244
- // _holder: _user,
245
- // _projectId: _projectId,
246
- // _tokenCount: 0,
247
- // _token: address(0),
248
- // _minReturnedTokens: 0,
249
- // _beneficiary: payable(_user),
250
- // _memo: 'Refund plz',
251
- // _metadata: cashOutMetadata
252
- // });
253
- // }
254
- // // // Phase 4: End
255
- // // vm.warp(deployer.endOf(_projectId));
256
- // // Forward the amount of blocks needed to reach the end (and round up)
257
- // // vm.roll(deployer.endOf(_projectId) - block.timestamp / 12 + 1);
258
- // vm.warp(block.timestamp + 1 weeks);
259
- // assertEq(jbRulesets().currentOf(_projectId).number, 4);
260
- // for (uint256 i = 0; i < _users.length; i++) {
261
- // address _user = _users[i];
262
- // // Craft the metadata: redeem the tokenId
263
- // bytes memory cashOutMetadata;
264
- // {
265
- // uint256[] memory cashOutId = new uint256[](1);
266
- // cashOutId[0] = _generateTokenId(i + 1, 1);
267
- // cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutId);
268
- // }
269
- // // Here the refunds are not allowed but cashOuts are,
270
- // // so it should instead revert with an error showing that there is no cashOut set for our tier
271
- // vm.expectRevert(abi.encodeWithSignature('NOTHING_TO_CLAIM()'));
272
- // vm.prank(_user);
273
- // JBMultiTerminal(address(jbMultiTerminal())).redeemTokensOf({
274
- // _holder: _user,
275
- // _projectId: _projectId,
276
- // _tokenCount: 0,
277
- // _token: address(0),
278
- // _minReturnedTokens: 0,
279
- // _beneficiary: payable(_user),
280
- // _memo: 'Refund plz',
281
- // _metadata: cashOutMetadata
282
- // });
283
- // }
284
- // }
285
-
286
- function testMint_fails_afterMintPhase() external {
287
- uint8 nTiers = 10;
288
- address[] memory _users = new address[](nTiers);
289
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
290
- (uint256 _projectId,,) = createDefifaProject(defifaData);
291
- // Phase 1: minting
292
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
293
- //deployer.queueNextPhaseOf(_projectId);
294
- // Phase 2: Redeem
295
- vm.warp(block.timestamp + defifaData.mintPeriodDuration);
296
- //deployer.queueNextPhaseOf(_projectId);
297
- // Make sure this is actually Phase 2
298
- assertEq(jbRulesets().currentOf(_projectId).cycleNumber, 2);
299
-
300
- for (uint256 i = 0; i < nTiers; i++) {
301
- // Generate a new address for each tier
302
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
303
- // fund user
304
- vm.deal(_users[i], 1 ether);
305
- // Build metadata to buy specific NFT
306
- uint16[] memory rawMetadata = new uint16[](1);
307
- // forge-lint: disable-next-line(unsafe-typecast)
308
- rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
309
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
310
- // Pay to the project and mint an NFT
311
- vm.expectRevert(JBTerminalStore.JBTerminalStore_RulesetPaymentPaused.selector);
312
- vm.prank(_users[i]);
313
- jbMultiTerminal().pay{value: 1 ether}(
314
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
315
- );
316
- }
317
- for (uint256 i = 0; i < nTiers; i++) {
318
- // Generate a new address for each tier
319
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
320
- // fund user
321
- vm.deal(_users[i], 1 ether);
322
- // Build metadata to buy specific NFT
323
- uint16[] memory rawMetadata = new uint16[](1);
324
- // forge-lint: disable-next-line(unsafe-typecast)
325
- rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
326
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
327
- // Pay to the project and mint an NFT
328
- vm.expectRevert(JBTerminalStore.JBTerminalStore_RulesetPaymentPaused.selector);
329
- vm.prank(_users[i]);
330
- jbMultiTerminal().pay{value: 1 ether}(
331
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
332
- );
333
- }
334
- }
335
-
336
- // Transfers are no longer disabled
337
- // function testTransfer_fails_afterTradeDeadline() external {
338
- // uint8 nTiers = 10;
339
- // address[] memory _users = new address[](nTiers);
340
- // DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData();
341
- // (uint256 _projectId, DefifaHook _nft, ) = createDefifaProject(
342
- // uint256(nTiers),
343
- // getBasicDefifaLaunchData()
344
- // );
345
- // // Phase 1: minting
346
- // vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
347
- // for (uint256 i = 0; i < nTiers; i++) {
348
- // // Generate a new address for each tier
349
- // _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i)))));
350
- // // fund user
351
- // vm.deal(_users[i], 1 ether);
352
- // // Build metadata to buy specific NFT
353
- // uint16[] memory rawMetadata = new uint16[](1);
354
- // rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
355
- // bytes memory metadata = abi.encode(
356
- // bytes32(0),
357
- // bytes32(0),
358
- // type(IDefifaHook).interfaceId,
359
- // false,
360
- // false,
361
- // false,
362
- // rawMetadata
363
- // );
364
- // // Pay to the project and mint an NFT
365
- // vm.prank(_users[i]);
366
- // jbMultiTerminal().pay{value: 1 ether}(
367
- // _projectId,
368
- // 1 ether,
369
- // address(0),
370
- // _users[i],
371
- // 0,
372
- // true,
373
- // '',
374
- // metadata
375
- // );
376
- // }
377
- // // Phase 2: Redeem
378
- // vm.warp(block.timestamp + defifaData.mintPeriodDuration);
379
- // //deployer.queueNextPhaseOf(_projectId);
380
- // // Make sure this is actually Phase 2
381
- // assertEq(jbRulesets().currentOf(_projectId).number, 2);
382
- // // Phase 3: Start
383
- // vm.warp(defifaData.start + 1);
384
- // //deployer.queueNextPhaseOf(_projectId);
385
- // // Make sure this is actually Phase 3
386
- // assertEq(jbRulesets().currentOf(_projectId).number, 3);
387
- // uint256 _tokenIdToTransfer = _generateTokenId(1, 1);
388
- // vm.prank(_users[0]);
389
- // // trasnfers not possible in phase 3
390
- // vm.expectRevert(abi.encodeWithSignature('TRANSFERS_PAUSED()'));
391
- // _nft.transferFrom(_users[0], _users[1], _tokenIdToTransfer);
392
- // }
393
- function testSetCashOutRates_fails_unmetQuorum() external {
394
- uint8 nTiers = 10;
395
- address[] memory _users = new address[](nTiers);
396
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
397
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
398
- // Phase 1: minting
399
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
400
- //deployer.queueNextPhaseOf(_projectId);
401
- for (uint256 i = 0; i < nTiers; i++) {
402
- // Generate a new address for each tier
403
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
404
- // fund user
405
- vm.deal(_users[i], 1 ether);
406
- // Build metadata to buy specific NFT
407
- uint16[] memory rawMetadata = new uint16[](1);
408
- // forge-lint: disable-next-line(unsafe-typecast)
409
- rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
410
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
411
- // Pay to the project and mint an NFT
412
- vm.prank(_users[i]);
413
- jbMultiTerminal().pay{value: 1 ether}(
414
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
415
- );
416
- // Set the delegate as the user themselves
417
- DefifaDelegation[] memory tiered721SetDelegatesData = new DefifaDelegation[](1);
418
- tiered721SetDelegatesData[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
419
- vm.prank(_users[i]);
420
- _nft.setTierDelegatesTo(tiered721SetDelegatesData);
421
- // Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
422
- vm.warp(_tsReader.timestamp() + 1);
423
- assertEq(
424
- _governor.MAX_ATTESTATION_POWER_TIER(),
425
- // forge-lint: disable-next-line(unsafe-typecast)
426
- _governor.getAttestationWeight(_gameId, _users[i], uint48(_tsReader.timestamp()))
427
- );
428
- }
429
- // Warp to scoring phase (past start time)
430
- vm.warp(defifaData.start + 1);
431
- // Generate the scorecards
432
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
433
- // We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
434
- for (uint256 i = 0; i < scorecards.length; i++) {
435
- scorecards[i].id = i + 1;
436
- scorecards[i].cashOutWeight = i % 2 == 0 ? 1e18 / (scorecards.length / 2) : 0;
437
- }
438
- // Forward time so proposals can be created
439
- uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
440
- // Forward time so voting becomes active
441
- vm.warp(_tsReader.timestamp() + _governor.attestationStartTimeOf(_gameId) + 1);
442
- // We have only 40% vote on the proposal, making it still be below quorum.
443
- for (uint256 i = 0; i < _users.length * 4 / 10; i++) {
444
- vm.prank(_users[i]);
445
- _governor.attestToScorecardFrom(_gameId, _proposalId);
446
- }
447
- // Forward the amount of blocks needed to reach the end (and round up)
448
- vm.warp(_tsReader.timestamp() + _governor.attestationGracePeriodOf(_gameId) + 1);
449
- // Execute the proposal
450
- vm.expectRevert(DefifaGovernor.DefifaGovernor_NotAllowed.selector);
451
- _governor.ratifyScorecardFrom(_gameId, scorecards);
452
- }
453
-
454
- function testSetCashOutRatesAndRedeem_multipleTiers(uint8 nTiers, uint8[] calldata distribution) public {
455
- nTiers = uint8(bound(uint256(nTiers), 11, 99));
456
- vm.assume(distribution.length > 0 && distribution.length < nTiers);
457
-
458
- uint256 _sumDistribution;
459
- for (uint256 i = 0; i < distribution.length; i++) {
460
- _sumDistribution += distribution[i];
461
- }
462
- vm.assume(_sumDistribution > 0);
463
- address[] memory _users = new address[](nTiers);
464
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
465
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
466
-
467
- // Phase 1: minting
468
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
469
- for (uint256 i = 0; i < nTiers; i++) {
470
- // Generate a new address for each tier
471
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
472
- // fund user
473
- vm.deal(_users[i], 1 ether);
474
-
475
- // Build metadata to buy specific NFT
476
- bytes memory metadata;
477
- {
478
- uint16[] memory rawMetadata = new uint16[](1);
479
- // forge-lint: disable-next-line(unsafe-typecast)
480
- rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
481
- metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
482
- }
483
-
484
- // Pay to the project and mint an NFT
485
- vm.prank(_users[i]);
486
- jbMultiTerminal().pay{value: 1 ether}(
487
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
488
- );
489
- // Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
490
- vm.warp(block.timestamp + 1);
491
- assertEq(
492
- _governor.MAX_ATTESTATION_POWER_TIER(),
493
- // forge-lint: disable-next-line(unsafe-typecast)
494
- _governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
495
- );
496
- // Have a user mint and refund the tier
497
- mintAndRefund(_nft, _projectId, i + 1);
498
- }
499
- // Have a user mint and refund the tier
500
- mintAndRefund(_nft, _projectId, 1);
501
-
502
- // Warp to scoring phase (past start time)
503
- vm.warp(defifaData.start + 1);
504
- // Generate the scorecards — must sum to exactly TOTAL_CASHOUT_WEIGHT
505
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
506
- uint256 assignedCashOutWeight;
507
- // We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
508
- for (uint256 i = 0; i < scorecards.length; i++) {
509
- scorecards[i].id = i + 1;
510
- if (distribution.length <= i) continue;
511
- scorecards[i].cashOutWeight = (uint256(distribution[i]) * _nft.TOTAL_CASHOUT_WEIGHT()) / _sumDistribution;
512
- assignedCashOutWeight += scorecards[i].cashOutWeight;
513
- }
514
- // Absorb rounding remainder into first tier with weight
515
- if (assignedCashOutWeight < _nft.TOTAL_CASHOUT_WEIGHT()) {
516
- uint256 remainder = _nft.TOTAL_CASHOUT_WEIGHT() - assignedCashOutWeight;
517
- for (uint256 i = 0; i < scorecards.length; i++) {
518
- if (scorecards[i].cashOutWeight > 0) {
519
- scorecards[i].cashOutWeight += remainder;
520
- break;
521
- }
522
- }
523
- }
524
- // Forward time so proposals can be created
525
- uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
526
- // Forward time so voting becomes active
527
- vm.warp(block.timestamp + _governor.attestationStartTimeOf(_gameId) + 1);
528
- // No voting delay after the initial voting delay has passed in
529
- //assertEq(_governor.attestationStartTimeOf(_gameId), 0);
530
- // All the users vote
531
- // 0 = Against
532
- // 1 = For
533
- // 2 = Abstain
534
- // BWA may reduce beneficiaries' power to zero; skip those gracefully.
535
- for (uint256 i = 0; i < _users.length; i++) {
536
- vm.prank(_users[i]);
537
- try _governor.attestToScorecardFrom(_gameId, _proposalId) {} catch {}
538
- }
539
- // each block is of 12 secs
540
- vm.warp(block.timestamp + _governor.attestationGracePeriodOf(_gameId));
541
-
542
- _governor.ratifyScorecardFrom(_gameId, scorecards);
543
- // Move forward 1 block to start the new ruleset.
544
- vm.roll(block.number + 1);
545
-
546
- _verifyCashOutsAndRedeem(
547
- _projectId, _nft, scorecards, _users, _sumDistribution, distribution, assignedCashOutWeight
548
- );
549
- }
550
-
551
- function _verifyCashOutsAndRedeem(
552
- uint256 _projectId,
553
- DefifaHook _nft,
554
- DefifaTierCashOutWeight[] memory scorecards,
555
- address[] memory _users,
556
- uint256 _sumDistribution,
557
- uint8[] calldata distribution,
558
- uint256 assignedCashOutWeight
559
- )
560
- internal
561
- {
562
- uint256 _pot = jbMultiTerminal().currentSurplusOf(_projectId, new address[](0), 18, JBCurrencyIds.ETH);
563
- // Assert that the deployer did *NOT* receive any fee tokens.
564
- assertEq(IERC20(_protocolFeeProjectTokenAccount).balanceOf(address(deployer)), 0);
565
- assertEq(IERC20(_defifaProjectTokenAccount).balanceOf(address(deployer)), 0);
566
-
567
- // Verify that the cashOutWeights actually changed
568
- for (uint256 i = 0; i < scorecards.length; i++) {
569
- _verifySingleCashOut(_projectId, _nft, scorecards[i], _users[i], _pot, _sumDistribution, distribution, i);
570
- }
571
- // All NFTs should have been redeemed, only some dust should be left
572
- uint256 remainingSurplus =
573
- jbMultiTerminal().currentSurplusOf(_projectId, new address[](0), 18, JBCurrencyIds.ETH);
574
- uint256 _expected = _pot * (_nft.TOTAL_CASHOUT_WEIGHT() - assignedCashOutWeight) / _nft.TOTAL_CASHOUT_WEIGHT();
575
- assertApproxEqAbs(remainingSurplus, _expected, 10 ** 14);
576
-
577
- // There should be no fee tokens left in the hook.
578
- assertEq(IERC20(_protocolFeeProjectTokenAccount).balanceOf(address(_nft)), 0);
579
- assertEq(IERC20(_defifaProjectTokenAccount).balanceOf(address(_nft)), 0);
580
- }
581
-
582
- function _verifySingleCashOut(
583
- uint256 _projectId,
584
- DefifaHook _nft,
585
- DefifaTierCashOutWeight memory scorecard,
586
- address _user,
587
- uint256 _pot,
588
- uint256 _sumDistribution,
589
- uint8[] calldata distribution,
590
- uint256 i
591
- )
592
- internal
593
- {
594
- assertEq(_nft.tierCashOutWeights()[i], scorecard.cashOutWeight);
595
-
596
- bytes memory cashOutMetadata;
597
- uint256 _receiveDefifa;
598
- uint256 _receiveNana;
599
- {
600
- uint256[] memory cashOutId = new uint256[](1);
601
- cashOutId[0] = _generateTokenId(i + 1, 1);
602
- cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutId));
603
- (_receiveDefifa, _receiveNana) = _nft.tokensClaimableFor(cashOutId);
604
- }
605
- uint256 _nanaBalance = IERC20(_protocolFeeProjectTokenAccount).balanceOf(_user);
606
- uint256 _defifaBalance = IERC20(_defifaProjectTokenAccount).balanceOf(_user);
607
-
608
- vm.prank(_user);
609
- JBMultiTerminal(address(jbMultiTerminal()))
610
- .cashOutTokensOf({
611
- holder: _user,
612
- projectId: _projectId,
613
- cashOutCount: 0,
614
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
615
- minTokensReclaimed: 0,
616
- beneficiary: payable(_user),
617
- metadata: cashOutMetadata
618
- });
619
-
620
- assertEq(IERC20(_protocolFeeProjectTokenAccount).balanceOf(_user), _nanaBalance + _receiveNana);
621
- assertEq(IERC20(_defifaProjectTokenAccount).balanceOf(_user), _defifaBalance + _receiveDefifa);
622
-
623
- if (scorecard.cashOutWeight == 0) return;
624
-
625
- uint256 _expectedTierCashOut = _pot;
626
- if (distribution.length > i) {
627
- _expectedTierCashOut = (_expectedTierCashOut * distribution[i]) / _sumDistribution;
628
- }
629
- assertApproxEqRel(_expectedTierCashOut, _user.balance, 0.001 ether);
630
- }
631
-
632
- function testVotingPowerDecreasesAfterRefund() public {
633
- uint256 nOfOtherTiers = 31;
634
- // forge-lint: disable-next-line(unsafe-typecast)
635
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(uint8(nOfOtherTiers + 1));
636
- (uint256 _projectId, DefifaHook _hook, DefifaGovernor _governor) = createDefifaProject(defifaData);
637
-
638
- // Phase 1: minting
639
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
640
- //deployer.queueNextPhaseOf(_projectId);
641
-
642
- JB721Tier memory _tier = _hook.store().tierOf(address(_hook), 1, false);
643
- uint256 _cost = _tier.price;
644
-
645
- address _refundUser = address(bytes20(keccak256("refund_user")));
646
- // The user should have no balance
647
- assertEq(_hook.balanceOf(_refundUser), 0);
648
- // Build metadata to buy specific NFT
649
- uint16[] memory rawMetadata = new uint16[](1);
650
- rawMetadata[0] = uint16(1); // reward tier, 1 indexed
651
- bytes memory metadata = _buildPayMetadata(abi.encode(_refundUser, rawMetadata));
652
- // Pay to the project and mint an NFT
653
- vm.deal(_refundUser, _cost);
654
-
655
- vm.prank(_refundUser);
656
- jbMultiTerminal().pay{value: _cost}(_projectId, JBConstants.NATIVE_TOKEN, _cost, _refundUser, 0, "", metadata);
657
-
658
- vm.warp(block.timestamp + 1);
659
-
660
- assertEq(
661
- _governor.MAX_ATTESTATION_POWER_TIER(),
662
- _governor.getAttestationWeight(_gameId, _refundUser, uint48(block.timestamp))
663
- );
664
-
665
- // User should no longer have any funds
666
- assertEq(_refundUser.balance, 0);
667
- // The user should have have a token
668
- assertEq(_hook.balanceOf(_refundUser), 1);
669
-
670
- uint256 _numberBurned = _hook.store().numberOfBurnedFor(address(_hook), 1);
671
- // Craft the metadata: redeem the tokenId
672
- bytes memory cashOutMetadata;
673
- {
674
- uint256[] memory cashOutId = new uint256[](1);
675
- cashOutId[0] = _generateTokenId(1, _tier.initialSupply - _tier.remainingSupply + 1 + _numberBurned);
676
- cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutId));
677
- }
678
-
679
- vm.prank(_refundUser);
680
- JBMultiTerminal(address(jbMultiTerminal()))
681
- .cashOutTokensOf({
682
- holder: _refundUser,
683
- projectId: _projectId,
684
- cashOutCount: 0,
685
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
686
- minTokensReclaimed: 0,
687
- beneficiary: payable(_refundUser),
688
- metadata: cashOutMetadata
689
- });
690
- vm.warp(block.timestamp + 1);
691
-
692
- assertEq(_refundUser.balance, _cost);
693
- assertEq(_hook.balanceOf(_refundUser), 0);
694
-
695
- assertEq(0, _governor.getAttestationWeight(_gameId, _refundUser, uint48(block.timestamp)));
696
- }
697
-
698
- function testRevertsIfDelegationisDoneAfterMintPhase(
699
- uint8 nUsersWithWinningTier,
700
- uint8 winningTierExtraWeight,
701
- uint8 baseCashOutWeight
702
- )
703
- public
704
- {
705
- uint256 nOfOtherTiers = 31;
706
- vm.assume(nUsersWithWinningTier > 1 && nUsersWithWinningTier < 100);
707
- uint256 totalWeight = baseCashOutWeight * (nOfOtherTiers + 1) + winningTierExtraWeight;
708
- vm.assume(totalWeight > 1);
709
-
710
- address[] memory _users = new address[](nOfOtherTiers + nUsersWithWinningTier);
711
- // forge-lint: disable-next-line(unsafe-typecast)
712
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(uint8(nOfOtherTiers + 1));
713
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
714
- // Phase 1: minting
715
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
716
- //deployer.queueNextPhaseOf(_projectId);
717
-
718
- for (uint256 i = 0; i < nOfOtherTiers + nUsersWithWinningTier; i++) {
719
- // Generate a new address for each tier
720
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
721
- // fund user
722
- vm.deal(_users[i], 1 ether);
723
- if (i < nOfOtherTiers) {
724
- // Build metadata to buy specific NFT
725
- uint16[] memory rawMetadata = new uint16[](1);
726
- // forge-lint: disable-next-line(unsafe-typecast)
727
- rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
728
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
729
- // Pay to the project and mint an NFT
730
- vm.prank(_users[i]);
731
- jbMultiTerminal().pay{value: 1 ether}(
732
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
733
- );
734
- // Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
735
- vm.warp(block.timestamp + 1);
736
- assertEq(
737
- _governor.MAX_ATTESTATION_POWER_TIER(),
738
- // forge-lint: disable-next-line(unsafe-typecast)
739
- _governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
740
- );
741
- } else {
742
- // Build metadata to buy specific NFT
743
- uint16[] memory rawMetadata = new uint16[](1);
744
- // forge-lint: disable-next-line(unsafe-typecast)
745
- rawMetadata[0] = uint16(nOfOtherTiers + 1); // reward tier, 1 indexed
746
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
747
- // Pay to the project and mint an NFT
748
- vm.prank(_users[i]);
749
- jbMultiTerminal().pay{value: 1 ether}(
750
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
751
- );
752
- // Forward 1 block, user should have a part of the voting power of their tier
753
- vm.warp(block.timestamp + 1);
754
- assertEq(
755
- _governor.MAX_ATTESTATION_POWER_TIER() / (i - nOfOtherTiers + 1),
756
- // forge-lint: disable-next-line(unsafe-typecast)
757
- _governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
758
- );
759
- }
760
- }
761
- // Phase 2: Redeem
762
- vm.warp(block.timestamp + defifaData.mintPeriodDuration);
763
- //deployer.queueNextPhaseOf(_projectId);
764
-
765
- vm.prank(_users[0]);
766
- vm.expectRevert(abi.encodeWithSignature("DefifaHook_DelegateChangesUnavailableInThisPhase()"));
767
- _nft.setTierDelegateTo(_users[1], 1);
768
- }
769
-
770
- function testSetCashOutRatesAndRedeem_singleTier(
771
- uint8 nUsersWithWinningTier,
772
- uint8 winningTierExtraWeight,
773
- uint8 baseCashOutWeight
774
- )
775
- public
776
- {
777
- uint256 nOfOtherTiers = 31;
778
- vm.assume(nUsersWithWinningTier > 1 && nUsersWithWinningTier < 100);
779
- uint256 totalWeight = baseCashOutWeight * (nOfOtherTiers + 1) + winningTierExtraWeight;
780
- vm.assume(totalWeight > 1);
781
-
782
- address[] memory _users = new address[](nOfOtherTiers + nUsersWithWinningTier);
783
- // forge-lint: disable-next-line(unsafe-typecast)
784
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(uint8(nOfOtherTiers + 1));
785
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
786
- // Phase 1: minting
787
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
788
- //deployer.queueNextPhaseOf(_projectId);
789
-
790
- for (uint256 i = 0; i < nOfOtherTiers + nUsersWithWinningTier; i++) {
791
- // Generate a new address for each tier
792
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
793
- // fund user
794
- vm.deal(_users[i], 1 ether);
795
- if (i < nOfOtherTiers) {
796
- // Build metadata to buy specific NFT
797
- uint16[] memory rawMetadata = new uint16[](1);
798
- // forge-lint: disable-next-line(unsafe-typecast)
799
- rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
800
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
801
- // Pay to the project and mint an NFT
802
- vm.prank(_users[i]);
803
- jbMultiTerminal().pay{value: 1 ether}(
804
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
805
- );
806
- // Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
807
- vm.warp(block.timestamp + 1);
808
- assertEq(
809
- _governor.MAX_ATTESTATION_POWER_TIER(),
810
- // forge-lint: disable-next-line(unsafe-typecast)
811
- _governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
812
- );
813
- } else {
814
- // Build metadata to buy specific NFT
815
- uint16[] memory rawMetadata = new uint16[](1);
816
- // forge-lint: disable-next-line(unsafe-typecast)
817
- rawMetadata[0] = uint16(nOfOtherTiers + 1); // reward tier, 1 indexed
818
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
819
- // Pay to the project and mint an NFT
820
- vm.prank(_users[i]);
821
- jbMultiTerminal().pay{value: 1 ether}(
822
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
823
- );
824
- // Forward 1 block, user should have a part of the voting power of their tier
825
- vm.warp(block.timestamp + 1);
826
- assertEq(
827
- _governor.MAX_ATTESTATION_POWER_TIER() / (i - nOfOtherTiers + 1),
828
- // forge-lint: disable-next-line(unsafe-typecast)
829
- _governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
830
- );
831
- }
832
- }
833
- // Have a user mint and refund the tier
834
- mintAndRefund(_nft, _projectId, 1);
835
- // Warp to scoring phase (past start time)
836
- vm.warp(defifaData.start + 1);
837
- // Generate the scorecards
838
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nOfOtherTiers + 1);
839
-
840
- uint256 totalCashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT();
841
-
842
- // We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
843
- uint256 assignedCashOutWeight;
844
- for (uint256 i = 0; i < scorecards.length; i++) {
845
- scorecards[i].id = i + 1;
846
- if (baseCashOutWeight != 0) {
847
- scorecards[i].cashOutWeight = (totalCashOutWeight * uint256(baseCashOutWeight)) / totalWeight;
848
- }
849
- if (i == nOfOtherTiers && winningTierExtraWeight != 0) {
850
- scorecards[i].cashOutWeight += (totalCashOutWeight * uint256(winningTierExtraWeight)) / totalWeight;
851
- }
852
- assignedCashOutWeight += scorecards[i].cashOutWeight;
853
- }
854
- // Absorb rounding remainder into first tier with weight
855
- if (assignedCashOutWeight < totalCashOutWeight) {
856
- uint256 remainder = totalCashOutWeight - assignedCashOutWeight;
857
- for (uint256 i = 0; i < scorecards.length; i++) {
858
- if (scorecards[i].cashOutWeight > 0) {
859
- scorecards[i].cashOutWeight += remainder;
860
- break;
861
- }
862
- }
863
- }
864
- {
865
- // Forward time so proposals can be created
866
- uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
867
- // Forward time so voting becomes active
868
- vm.warp(block.timestamp + _governor.attestationStartTimeOf(_gameId) + 1);
869
- // No voting delay after the initial voting delay has passed in
870
- // assertEq(_governor.attestationStartTimeOf(_gameId), 0);
871
- // All the users vote
872
- // 0 = Against
873
- // 1 = For
874
- // 2 = Abstain
875
- // BWA may reduce beneficiaries' power to zero; skip those gracefully.
876
- for (uint256 i = 0; i < _users.length; i++) {
877
- vm.prank(_users[i]);
878
- try _governor.attestToScorecardFrom(_gameId, _proposalId) {} catch {}
879
- }
880
- }
881
-
882
- // Forward the amount of blocks needed to reach the end (and round up)
883
- vm.warp(block.timestamp + _governor.attestationGracePeriodOf(_gameId));
884
-
885
- _governor.ratifyScorecardFrom(_gameId, scorecards);
886
- vm.warp(block.timestamp + 1);
887
-
888
- uint256 _pot = jbMultiTerminal().currentSurplusOf(_projectId, new address[](0), 18, JBCurrencyIds.ETH);
889
-
890
- // Verify that the cashOutWeights actually changed
891
- for (uint256 i = 0; i < _users.length; i++) {
892
- address _user = _users[i];
893
- uint256 _tier = i <= nOfOtherTiers ? i + 1 : nOfOtherTiers + 1;
894
- // Craft the metadata: redeem the tokenId
895
- bytes memory cashOutMetadata;
896
- {
897
- uint256[] memory cashOutId = new uint256[](1);
898
- cashOutId[0] = _generateTokenId(_tier, _tier == nOfOtherTiers + 1 ? i - nOfOtherTiers + 1 : 1);
899
- cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutId));
900
- }
901
- uint256 _expectedTierCashOut;
902
- {
903
- // Calculate how much weight his tier has
904
- uint256 _tierWeight = _tier == nOfOtherTiers + 1
905
- ? uint256(baseCashOutWeight) + uint256(winningTierExtraWeight)
906
- : baseCashOutWeight;
907
-
908
- // If the cashOut is 0 this will revert
909
- vm.prank(_user);
910
- JBMultiTerminal(address(jbMultiTerminal()))
911
- .cashOutTokensOf({
912
- holder: _user,
913
- projectId: _projectId,
914
- cashOutCount: 0,
915
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
916
- minTokensReclaimed: 0,
917
- beneficiary: payable(_user),
918
- metadata: cashOutMetadata
919
- });
920
- // We calculate the expected output based on the given distribution and how much is in the pot
921
- _expectedTierCashOut = (_pot * _tierWeight) / totalWeight;
922
- }
923
- {
924
- // If this is the winning tier then the amount is divided among the nUsersWithWinningTier
925
- if (_tier == nOfOtherTiers + 1) {
926
- _expectedTierCashOut = _expectedTierCashOut / nUsersWithWinningTier;
927
- }
928
- }
929
- // Assert that our expected tier cashOut is ~equal to the actual amount
930
- // Allowing for some rounding errors, max allowed error is 0.000001 ether
931
- assertApproxEqRel(_expectedTierCashOut, _user.balance, 0.0001 ether);
932
- }
933
- // All NFTs should have been redeemed, only some dust should be left
934
- // Max allowed dust is 0.0001
935
- uint256 remainingSurplus =
936
- jbMultiTerminal().currentSurplusOf(_projectId, new address[](0), 18, JBCurrencyIds.ETH);
937
- assertApproxEqAbs(
938
- remainingSurplus, _pot * (totalCashOutWeight - assignedCashOutWeight) / totalCashOutWeight, 10 ** 14
939
- );
940
- }
941
-
942
- function testPhaseTimes(
943
- uint16 _durationUntilProjectLaunch,
944
- uint16 _mintPeriodDuration,
945
- uint16 _inBetweenMintAndFifa,
946
- uint16 _fifaDuration
947
- )
948
- public
949
- {
950
- vm.assume(
951
- _durationUntilProjectLaunch > 2 && _mintPeriodDuration > 1 && _inBetweenMintAndFifa > 1 && _fifaDuration > 1
952
- );
953
- uint48 _launchProjectAt = uint48(block.timestamp) + _durationUntilProjectLaunch;
954
- DefifaTierParams[] memory tierParams = new DefifaTierParams[](1);
955
- tierParams[0] = DefifaTierParams({
956
- reservedRate: 1001,
957
- reservedTokenBeneficiary: address(0),
958
- encodedIPFSUri: bytes32(0), // this way we dont need more tokenUris
959
- shouldUseReservedTokenBeneficiaryAsDefault: false,
960
- name: "DEFIFA"
961
- });
962
-
963
- DefifaLaunchProjectData memory _launchData = DefifaLaunchProjectData({
964
- name: "DEFIFA",
965
- projectUri: "",
966
- contractUri: "",
967
- baseUri: "",
968
- tierPrice: 1 ether,
969
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
970
- mintPeriodDuration: _mintPeriodDuration,
971
- start: _launchProjectAt + uint48(_mintPeriodDuration) + _inBetweenMintAndFifa,
972
- refundPeriodDuration: _inBetweenMintAndFifa,
973
- store: new JB721TiersHookStore(),
974
- splits: new JBSplit[](0),
975
- attestationStartTime: 0,
976
- attestationGracePeriod: 100_381,
977
- defaultAttestationDelegate: address(0),
978
- tiers: tierParams,
979
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
980
- terminal: jbMultiTerminal(),
981
- minParticipation: 0,
982
- scorecardTimeout: 0,
983
- timelockDuration: 0
984
- });
985
- (uint256 _projectId, DefifaHook _nft,) = createDefifaProject(_launchData);
986
- // Wait until the phase 1 start
987
- vm.warp(_launchProjectAt);
988
- // Get the hook
989
- _nft = DefifaHook(jbRulesets().currentOf(_projectId).dataHook());
990
- // We should be in the minting phase now
991
- assertEq(jbRulesets().currentOf(_projectId).cycleNumber, 1);
992
- // Queue Phase 2
993
- //deployer.queueNextPhaseOf(_projectId);
994
- // Go to the end of the minting phase and check if we are still in the minting phase
995
- vm.warp(_launchProjectAt + _mintPeriodDuration - 1);
996
- assertEq(jbRulesets().currentOf(_projectId).cycleNumber, 1);
997
- // We should now be in phase 2, minting is paused and the treasury is frozen
998
- vm.warp(_launchProjectAt + _mintPeriodDuration);
999
- assertEq(jbRulesets().currentOf(_projectId).cycleNumber, 2);
1000
- // Queue Phase 3
1001
-
1002
- //deployer.queueNextPhaseOf(_projectId);
1003
- // We should now be in phase 4, game has ended
1004
- vm.warp(_launchProjectAt + _mintPeriodDuration + _inBetweenMintAndFifa + _fifaDuration);
1005
- assertEq(jbRulesets().currentOf(_projectId).cycleNumber, 3);
1006
- }
1007
-
1008
- function testWhenScorecardIsSubmittedWithUnmintedTier() public {
1009
- uint8 nTiers = 10;
1010
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
1011
- (,, DefifaGovernor _governor) = createDefifaProject(defifaData);
1012
- // Phase 1: Mint
1013
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
1014
-
1015
- // Warp to scoring phase (past start time)
1016
- vm.warp(defifaData.start + 1);
1017
- // Generate the scorecards
1018
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
1019
- // We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
1020
- for (uint256 i = 0; i < scorecards.length; i++) {
1021
- scorecards[i].id = i + 1;
1022
- scorecards[i].cashOutWeight = i % 2 == 0 ? 1e18 / (scorecards.length / 2) : 0;
1023
- }
1024
-
1025
- vm.expectRevert(abi.encodeWithSignature("DefifaGovernor_UnownedProposedCashoutValue()"));
1026
- // Forward time so proposals can be created
1027
- _governor.submitScorecardFor(_gameId, scorecards);
1028
- }
1029
-
1030
- // function testWhenPhaseIsAlreadyQueued() public {
1031
- // uint8 nTiers = 10;
1032
- // address[] memory _users = new address[](nTiers);
1033
- // DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
1034
- // (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
1035
- // // Phase 1: Mint
1036
- // vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
1037
- // //deployer.queueNextPhaseOf(_projectId);
1038
- // for (uint256 i = 0; i < nTiers; i++) {
1039
- // // Generate a new address for each tier
1040
- // _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
1041
- // // fund user
1042
- // vm.deal(_users[i], 1 ether);
1043
- // // Build metadata to buy specific NFT
1044
- // uint16[] memory rawMetadata = new uint16[](1);
1045
- // rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
1046
- // bytes memory metadata =
1047
- // _buildPayMetadata(abi.encode(_users[i], rawMetadata);
1048
- // // Pay to the project and mint an NFT
1049
- // vm.prank(_users[i]);
1050
- // jbMultiTerminal().pay{value: 1 ether}(_projectId, JBConstants.NATIVE_TOKEN, 1 ether,_users[i], 0, "",
1051
- // metadata); // Set the delegate as the user themselves
1052
- // DefifaDelegation[] memory tiered721SetDelegatesData =
1053
- // new DefifaDelegation[](1);
1054
- // tiered721SetDelegatesData[0] =
1055
- // DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
1056
- // vm.prank(_users[i]);
1057
- // _nft.setTierDelegatesTo(tiered721SetDelegatesData);
1058
- // // Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
1059
- // vm.roll(block.number + 1);
1060
- // assertEq(_governor.MAX_ATTESTATION_POWER_TIER(), _governor.getAttestationWeight(_gameId, _users[i],
1061
- // block.number - 1)); }
1062
- // // Phase 2: Redeem
1063
- // vm.warp(block.timestamp + defifaData.mintPeriodDuration);
1064
- // //deployer.queueNextPhaseOf(_projectId);
1065
- // // Right at the end of Phase 2
1066
- // vm.warp(defifaData.start - 1);
1067
- // vm.expectRevert(abi.encodeWithSignature("PHASE_ALREADY_QUEUED()"));
1068
- // //deployer.queueNextPhaseOf(_projectId);
1069
- // }
1070
-
1071
- function testSettingTierCashOutWeightBeforeEndPhase() public {
1072
- uint8 nTiers = 10;
1073
- address[] memory _users = new address[](nTiers);
1074
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
1075
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
1076
- // Phase 1: Mint
1077
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
1078
- //deployer.queueNextPhaseOf(_projectId);
1079
- for (uint256 i = 0; i < nTiers; i++) {
1080
- // Generate a new address for each tier
1081
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
1082
- // fund user
1083
- vm.deal(_users[i], 1 ether);
1084
- // Build metadata to buy specific NFT
1085
- uint16[] memory rawMetadata = new uint16[](1);
1086
- // forge-lint: disable-next-line(unsafe-typecast)
1087
- rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed
1088
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
1089
- // Pay to the project and mint an NFT
1090
- vm.prank(_users[i]);
1091
- jbMultiTerminal().pay{value: 1 ether}(
1092
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
1093
- );
1094
- // Set the delegate as the user themselves
1095
- DefifaDelegation[] memory tiered721SetDelegatesData = new DefifaDelegation[](1);
1096
- tiered721SetDelegatesData[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
1097
- vm.prank(_users[i]);
1098
- _nft.setTierDelegatesTo(tiered721SetDelegatesData);
1099
- // Forward 1 block, user should receive all the voting power of the tier, as its the only NFT
1100
- vm.warp(_tsReader.timestamp() + 1);
1101
- assertEq(
1102
- _governor.MAX_ATTESTATION_POWER_TIER(),
1103
- // forge-lint: disable-next-line(unsafe-typecast)
1104
- _governor.getAttestationWeight(_gameId, _users[i], uint48(_tsReader.timestamp()))
1105
- );
1106
- }
1107
- // Warp to scoring phase (past start time)
1108
- vm.warp(defifaData.start + 1);
1109
- // Generate the scorecards
1110
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
1111
- // We can't have a neutral outcome, so we only give shares to tiers that are an even number (in our array)
1112
- for (uint256 i = 0; i < scorecards.length; i++) {
1113
- scorecards[i].id = i + 1;
1114
- scorecards[i].cashOutWeight = i % 2 == 0 ? 1e18 / (scorecards.length / 2) : 0;
1115
- }
1116
- // Forward time so proposals can be created
1117
- uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
1118
- // Forward time so voting becomes active
1119
- vm.warp(_tsReader.timestamp() + _governor.attestationStartTimeOf(_gameId));
1120
- // All the users vote
1121
- for (uint256 i = 0; i < _users.length; i++) {
1122
- vm.prank(_users[i]);
1123
- _governor.attestToScorecardFrom(_gameId, _proposalId);
1124
- }
1125
- // Execute the proposal — should fail because grace period hasn't ended
1126
- vm.expectRevert(DefifaGovernor.DefifaGovernor_NotAllowed.selector);
1127
- _governor.ratifyScorecardFrom(_gameId, scorecards);
1128
- }
1129
-
1130
- function testWhenCashOutWeightisMoreThanMaxCashOutWeight(uint8 nTiers) public {
1131
- // Anything above 10 should cause the error we are looking for.
1132
- // As a sanity check we let it also run for less than 10 to see if it does not error in that case.
1133
- nTiers = uint8(bound(nTiers, 2, 20));
1134
-
1135
- // With exact-weight validation, only nTiers == 10 produces weights that sum to TOTAL_CASHOUT_WEIGHT.
1136
- // Delegate to separate helpers to avoid stack-too-deep.
1137
- if (nTiers == 10) {
1138
- _testCashOutWeightExact(nTiers);
1139
- } else {
1140
- _testCashOutWeightInvalid(nTiers);
1141
- }
1142
- }
1143
-
1144
- /// @dev nTiers == 10: all weights valid, full flow (submit → vote → ratify).
1145
- function _testCashOutWeightExact(uint8 nTiers) internal {
1146
- address[] memory _users = new address[](nTiers);
1147
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
1148
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
1149
-
1150
- uint256 cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT() / 10;
1151
-
1152
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
1153
- for (uint256 i = 0; i < nTiers; i++) {
1154
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
1155
- vm.deal(_users[i], 1 ether);
1156
- uint16[] memory rawMetadata = new uint16[](1);
1157
- // forge-lint: disable-next-line(unsafe-typecast)
1158
- rawMetadata[0] = uint16(i + 1);
1159
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
1160
- vm.prank(_users[i]);
1161
- jbMultiTerminal().pay{value: 1 ether}(
1162
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
1163
- );
1164
- DefifaDelegation[] memory tiered721SetDelegatesData = new DefifaDelegation[](1);
1165
- tiered721SetDelegatesData[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
1166
- vm.prank(_users[i]);
1167
- _nft.setTierDelegatesTo(tiered721SetDelegatesData);
1168
- assertEq(
1169
- _governor.MAX_ATTESTATION_POWER_TIER(),
1170
- // forge-lint: disable-next-line(unsafe-typecast)
1171
- _governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
1172
- );
1173
- }
1174
- vm.warp(defifaData.start + 1);
1175
-
1176
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
1177
- for (uint256 i = 0; i < scorecards.length; i++) {
1178
- scorecards[i].id = i + 1;
1179
- scorecards[i].cashOutWeight = cashOutWeight;
1180
- }
1181
-
1182
- uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
1183
- vm.warp(block.timestamp + _governor.attestationStartTimeOf(_gameId));
1184
- for (uint256 i = 0; i < _users.length; i++) {
1185
- vm.prank(_users[i]);
1186
- _governor.attestToScorecardFrom(_gameId, _proposalId);
1187
- }
1188
- vm.warp(block.timestamp + _governor.attestationGracePeriodOf(_gameId) + 1);
1189
- _governor.ratifyScorecardFrom(_gameId, scorecards);
1190
- }
1191
-
1192
- /// @dev nTiers != 10: weights don't sum to TOTAL_CASHOUT_WEIGHT, submitScorecardFor reverts.
1193
- function _testCashOutWeightInvalid(uint8 nTiers) internal {
1194
- address[] memory _users = new address[](nTiers);
1195
- DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(nTiers);
1196
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = createDefifaProject(defifaData);
1197
-
1198
- uint256 cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT() / 10;
1199
-
1200
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
1201
- for (uint256 i = 0; i < nTiers; i++) {
1202
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
1203
- vm.deal(_users[i], 1 ether);
1204
- uint16[] memory rawMetadata = new uint16[](1);
1205
- // forge-lint: disable-next-line(unsafe-typecast)
1206
- rawMetadata[0] = uint16(i + 1);
1207
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
1208
- vm.prank(_users[i]);
1209
- jbMultiTerminal().pay{value: 1 ether}(
1210
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
1211
- );
1212
- DefifaDelegation[] memory tiered721SetDelegatesData = new DefifaDelegation[](1);
1213
- tiered721SetDelegatesData[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
1214
- vm.prank(_users[i]);
1215
- _nft.setTierDelegatesTo(tiered721SetDelegatesData);
1216
- assertEq(
1217
- _governor.MAX_ATTESTATION_POWER_TIER(),
1218
- // forge-lint: disable-next-line(unsafe-typecast)
1219
- _governor.getAttestationWeight(_gameId, _users[i], uint48(block.timestamp))
1220
- );
1221
- }
1222
- vm.warp(defifaData.start + 1);
1223
-
1224
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
1225
- for (uint256 i = 0; i < scorecards.length; i++) {
1226
- scorecards[i].id = i + 1;
1227
- scorecards[i].cashOutWeight = cashOutWeight;
1228
- }
1229
-
1230
- vm.expectRevert(DefifaHook.DefifaHook_InvalidCashoutWeights.selector);
1231
- _governor.submitScorecardFor(_gameId, scorecards);
1232
- }
1233
-
1234
- function getBasicDefifaLaunchData(uint8 nTiers) internal returns (DefifaLaunchProjectData memory) {
1235
- DefifaTierParams[] memory tierParams = new DefifaTierParams[](nTiers);
1236
- for (uint256 i = 0; i < nTiers; i++) {
1237
- tierParams[i] = DefifaTierParams({
1238
- reservedRate: 1001,
1239
- reservedTokenBeneficiary: address(0),
1240
- encodedIPFSUri: bytes32(0), // this way we dont need more tokenUris
1241
- shouldUseReservedTokenBeneficiaryAsDefault: false,
1242
- name: "DEFIFA"
1243
- });
1244
- }
1245
-
1246
- return DefifaLaunchProjectData({
1247
- name: "DEFIFA",
1248
- projectUri: "",
1249
- contractUri: "",
1250
- baseUri: "",
1251
- tierPrice: 1 ether,
1252
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
1253
- mintPeriodDuration: 1 days,
1254
- start: uint48(block.timestamp + 3 days),
1255
- refundPeriodDuration: 1 days,
1256
- store: new JB721TiersHookStore(),
1257
- splits: new JBSplit[](0),
1258
- attestationStartTime: 0,
1259
- attestationGracePeriod: 100_381,
1260
- defaultAttestationDelegate: address(0),
1261
- tiers: tierParams,
1262
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
1263
- terminal: jbMultiTerminal(),
1264
- minParticipation: 0,
1265
- scorecardTimeout: 0,
1266
- timelockDuration: 0
1267
- });
1268
- }
1269
-
1270
- // ----- internal helpers ------
1271
- function createDefifaProject(DefifaLaunchProjectData memory defifaLaunchData)
1272
- internal
1273
- returns (uint256 projectId, DefifaHook nft, DefifaGovernor _governor)
1274
- {
1275
- _governor = governor;
1276
- (projectId) = deployer.launchGameWith(defifaLaunchData);
1277
- // Get a reference to the latest configured funding cycle's data hook, which should be the hook that was
1278
- // deployed and attached to the project.
1279
- JBRuleset memory _fc = jbRulesets().currentOf(projectId);
1280
- if (_fc.dataHook() == address(0)) {
1281
- (_fc,) = jbRulesets().latestQueuedOf(projectId);
1282
- }
1283
- nft = DefifaHook(_fc.dataHook());
1284
- }
1285
-
1286
- function mintAndRefund(DefifaHook _hook, uint256 _projectId, uint256 _tierId) internal {
1287
- JB721Tier memory _tier = _hook.store().tierOf(address(_hook), _tierId, false);
1288
- uint256 _cost = _tier.price;
1289
- address _refundUser = address(bytes20(keccak256("refund_user")));
1290
- // The user should have no balance
1291
- assertEq(_hook.balanceOf(_refundUser), 0);
1292
- // Build metadata to buy specific NFT
1293
- uint16[] memory rawMetadata = new uint16[](1);
1294
- // forge-lint: disable-next-line(unsafe-typecast)
1295
- rawMetadata[0] = uint16(_tierId); // reward tier, 1 indexed
1296
- bytes memory metadata = _buildPayMetadata(abi.encode(_refundUser, rawMetadata));
1297
- // Pay to the project and mint an NFT
1298
- vm.deal(_refundUser, _cost);
1299
- vm.prank(_refundUser);
1300
- jbMultiTerminal().pay{value: _cost}(_projectId, JBConstants.NATIVE_TOKEN, _cost, _refundUser, 0, "", metadata);
1301
- // User should no longer have any funds
1302
- assertEq(_refundUser.balance, 0);
1303
- // The user should have have a token
1304
- assertEq(_hook.balanceOf(_refundUser), 1);
1305
- // Craft the metadata: redeem the tokenId
1306
- bytes memory cashOutMetadata;
1307
- {
1308
- uint256[] memory cashOutId = new uint256[](1);
1309
- cashOutId[0] = _generateTokenId(_tierId, _tier.initialSupply - --_tier.remainingSupply);
1310
- cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutId));
1311
- }
1312
- vm.prank(_refundUser);
1313
- JBMultiTerminal(address(jbMultiTerminal()))
1314
- .cashOutTokensOf({
1315
- holder: _refundUser,
1316
- projectId: _projectId,
1317
- cashOutCount: 0,
1318
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
1319
- minTokensReclaimed: 0,
1320
- beneficiary: payable(_refundUser),
1321
- metadata: cashOutMetadata
1322
- });
1323
- // User should have their original funds again
1324
- assertEq(_refundUser.balance, _cost);
1325
- // User should no longer have the NFT
1326
- assertEq(_hook.balanceOf(_refundUser), 0);
1327
- }
1328
-
1329
- // Create launchProjectFor(..) payload
1330
- string name = "NAME";
1331
- string symbol = "SYM";
1332
- string baseUri = "http://www.null.com/";
1333
- string contractUri = "ipfs://null";
1334
- address reserveBeneficiary = address(bytes20(keccak256("reserveBeneficiary")));
1335
- //QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz
1336
- bytes32[] tokenUris = [
1337
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1338
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1339
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1340
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1341
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1342
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1343
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1344
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1345
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
1346
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89)
1347
- ];
1348
-
1349
- function _generateTokenId(uint256 _tierId, uint256 _tokenNumber) internal pure returns (uint256) {
1350
- return (_tierId * 1_000_000_000) + _tokenNumber;
1351
- }
1352
-
1353
- function _buildPayMetadata(bytes memory metadata) internal view returns (bytes memory) {
1354
- // Build the metadata using the tiers to mint and the overspending flag.
1355
- bytes[] memory data = new bytes[](1);
1356
- data[0] = metadata;
1357
-
1358
- // Pass the hook ID.
1359
- bytes4[] memory ids = new bytes4[](1);
1360
- ids[0] = metadataHelper().getId("pay", address(hook));
1361
-
1362
- // Generate the metadata.
1363
- return metadataHelper().createMetadata(ids, data);
1364
- }
1365
-
1366
- function _buildCashOutMetadata(bytes memory metadata) internal view returns (bytes memory) {
1367
- // Build the metadata using the tiers to mint and the overspending flag.
1368
- bytes[] memory data = new bytes[](1);
1369
- data[0] = metadata;
1370
-
1371
- // Pass the hook ID.
1372
- bytes4[] memory ids = new bytes4[](1);
1373
- ids[0] = metadataHelper().getId("cashOut", address(hook));
1374
-
1375
- // Generate the metadata.
1376
- return metadataHelper().createMetadata(ids, data);
1377
- }
1378
- }