@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,366 +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
- import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
12
-
13
- import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
14
- import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
15
- import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
16
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
17
-
18
- import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
19
- import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
20
- import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
21
- import {DefifaDelegation} from "../../src/structs/DefifaDelegation.sol";
22
- import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
23
- import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
24
- import {DefifaTierCashOutWeight} from "../../src/structs/DefifaTierCashOutWeight.sol";
25
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
26
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
27
- import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
28
- import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
29
- import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
30
- import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
31
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
32
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
33
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
34
- import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
35
-
36
- /// @notice Verifies the fix for H-2: Pending reserve NFTs are now included in the cash-out weight
37
- /// denominator. Before the fix, paid holders could cash out before reserves were minted and extract
38
- /// more than their fair share.
39
- ///
40
- /// With BWA + HHI-adjusted quorum, a single-tier winner-take-all scorecard gives the sole beneficiary
41
- /// 0 attestation power (BWA multiplier = 1 - 1 = 0). To allow ratification, we add 3 disinterested
42
- /// tiers (weight = 0) whose attestors provide governance power to meet the adjusted quorum.
43
- contract FixPendingReserveDilutionTest is JBTest, TestBaseWorkflow {
44
- using JBRulesetMetadataResolver for JBRuleset;
45
-
46
- address _protocolFeeProjectTokenAccount;
47
- address _defifaProjectTokenAccount;
48
- uint256 _protocolFeeProjectId;
49
- uint256 _defifaProjectId;
50
- uint256 _gameId = 3;
51
-
52
- DefifaDeployer deployer;
53
- DefifaHook hook;
54
- DefifaGovernor governor;
55
-
56
- address projectOwner = address(bytes20(keccak256("projectOwner")));
57
- address reserveBeneficiary = address(bytes20(keccak256("reserveBeneficiary")));
58
- address player = address(bytes20(keccak256("player")));
59
- address disinterested1 = address(bytes20(keccak256("disinterested1")));
60
- address disinterested2 = address(bytes20(keccak256("disinterested2")));
61
- address disinterested3 = address(bytes20(keccak256("disinterested3")));
62
-
63
- uint256 _pid;
64
- DefifaHook _nft;
65
- DefifaGovernor _gov;
66
-
67
- function setUp() public virtual override {
68
- super.setUp();
69
-
70
- JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
71
- _tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
72
- JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
73
- tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
74
- JBRulesetConfig[] memory rc = new JBRulesetConfig[](1);
75
- rc[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
- _protocolFeeProjectId = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
107
- vm.prank(projectOwner);
108
- _protocolFeeProjectTokenAccount =
109
- address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
110
- _defifaProjectId = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
111
- vm.prank(projectOwner);
112
- _defifaProjectTokenAccount =
113
- address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
114
-
115
- hook =
116
- new DefifaHook(jbDirectory(), IERC20(_defifaProjectTokenAccount), IERC20(_protocolFeeProjectTokenAccount));
117
- governor = new DefifaGovernor(jbController(), address(this));
118
- deployer = new DefifaDeployer(
119
- address(hook),
120
- new DefifaTokenUriResolver(ITypeface(address(0))),
121
- governor,
122
- jbController(),
123
- new JBAddressRegistry(),
124
- _protocolFeeProjectId,
125
- _defifaProjectId
126
- );
127
- hook.transferOwnership(address(deployer));
128
- governor.transferOwnership(address(deployer));
129
- }
130
-
131
- /// @notice With the fix, a paid holder's cash-out share is diluted by pending reserves.
132
- /// The paid holder should NOT be able to reclaim the full post-fee surplus when pending
133
- /// reserves exist -- the reserve holder's share must be protected.
134
- function test_paidHolderCashOutDilutedByPendingReserves() external {
135
- (_pid, _nft, _gov) = _launch(_launchData());
136
-
137
- // Mint phase: player mints 1 NFT into tier 1, disinterested users mint tiers 2-4.
138
- vm.warp(block.timestamp + 1 days + 1);
139
- _mint(player, 1, 1 ether);
140
- _delegateSelf(player, 1);
141
- vm.warp(block.timestamp + 1);
142
- _mint(disinterested1, 2, 1 ether);
143
- _delegateSelf(disinterested1, 2);
144
- vm.warp(block.timestamp + 1);
145
- _mint(disinterested2, 3, 1 ether);
146
- _delegateSelf(disinterested2, 3);
147
- vm.warp(block.timestamp + 1);
148
- _mint(disinterested3, 4, 1 ether);
149
- _delegateSelf(disinterested3, 4);
150
-
151
- // Verify there is a pending reserve for tier 1.
152
- assertEq(_nft.store().numberOfPendingReservesFor(address(_nft), 1), 1, "one reserve should be pending");
153
-
154
- // Advance to scoring phase.
155
- vm.warp(block.timestamp + 2 days + 1);
156
-
157
- // Submit scorecard giving all weight to tier 1; tiers 2-4 get 0 (disinterested attestors).
158
- DefifaTierCashOutWeight[] memory sc = new DefifaTierCashOutWeight[](4);
159
- sc[0] = DefifaTierCashOutWeight({id: 1, cashOutWeight: _nft.TOTAL_CASHOUT_WEIGHT()});
160
- sc[1] = DefifaTierCashOutWeight({id: 2, cashOutWeight: 0});
161
- sc[2] = DefifaTierCashOutWeight({id: 3, cashOutWeight: 0});
162
- sc[3] = DefifaTierCashOutWeight({id: 4, cashOutWeight: 0});
163
- uint256 proposalId = _gov.submitScorecardFor(_gameId, sc);
164
-
165
- // Disinterested users attest (they have full BWA power since their tiers get 0 weight).
166
- // The player (tier 1, 100% weight) has 0 BWA power and cannot meaningfully attest.
167
- vm.prank(disinterested1);
168
- _gov.attestToScorecardFrom(_gameId, proposalId);
169
- vm.prank(disinterested2);
170
- _gov.attestToScorecardFrom(_gameId, proposalId);
171
- vm.prank(disinterested3);
172
- _gov.attestToScorecardFrom(_gameId, proposalId);
173
-
174
- vm.warp(block.timestamp + _gov.attestationGracePeriodOf(_gameId) + 1);
175
- _gov.ratifyScorecardFrom(_gameId, sc);
176
-
177
- // The player should only reclaim HALF of their tier's share of the post-fee surplus
178
- // (1 of 2 tokens in tier 1, since the pending reserve counts in the denominator).
179
- // Note: total pot includes 4 ETH from all minters but tier 1 gets 100% of weight.
180
- // Fees are taken from the terminal surplus. Post-fee surplus is available for cash-out.
181
- uint256 postFeeSurplus = 4 ether - (4 ether / 20) - (4 ether / 40);
182
- // Tier 1 gets 100% weight. Player holds 1 of 2 units (1 paid + 1 pending reserve).
183
- uint256 expectedPlayerReclaim = postFeeSurplus / 2;
184
-
185
- uint256 beforePlayerBalance = player.balance;
186
- _cashOut(player, 1, 1);
187
- uint256 playerReclaim = player.balance - beforePlayerBalance;
188
-
189
- // The player should receive approximately half the surplus, not the full amount.
190
- assertApproxEqAbs(
191
- playerReclaim,
192
- expectedPlayerReclaim,
193
- 1, // 1 wei tolerance for rounding
194
- "paid holder should only reclaim half due to pending reserve dilution"
195
- );
196
-
197
- // Specifically, the player should NOT get the full pot.
198
- assertLt(playerReclaim, postFeeSurplus, "paid holder should NOT reclaim full surplus with pending reserves");
199
- }
200
-
201
- /// @notice After reserves are minted, the reserve holder should be able to cash out their share.
202
- function test_reserveHolderCanCashOutAfterMinting() external {
203
- (_pid, _nft, _gov) = _launch(_launchData());
204
-
205
- // Mint phase: player mints 1 NFT into tier 1, disinterested users mint tiers 2-4.
206
- vm.warp(block.timestamp + 1 days + 1);
207
- _mint(player, 1, 1 ether);
208
- _delegateSelf(player, 1);
209
- vm.warp(block.timestamp + 1);
210
- _mint(disinterested1, 2, 1 ether);
211
- _delegateSelf(disinterested1, 2);
212
- vm.warp(block.timestamp + 1);
213
- _mint(disinterested2, 3, 1 ether);
214
- _delegateSelf(disinterested2, 3);
215
- vm.warp(block.timestamp + 1);
216
- _mint(disinterested3, 4, 1 ether);
217
- _delegateSelf(disinterested3, 4);
218
-
219
- // Advance to scoring phase.
220
- vm.warp(block.timestamp + 2 days + 1);
221
-
222
- // Submit scorecard: tier 1 gets all weight; tiers 2-4 are disinterested.
223
- DefifaTierCashOutWeight[] memory sc = new DefifaTierCashOutWeight[](4);
224
- sc[0] = DefifaTierCashOutWeight({id: 1, cashOutWeight: _nft.TOTAL_CASHOUT_WEIGHT()});
225
- sc[1] = DefifaTierCashOutWeight({id: 2, cashOutWeight: 0});
226
- sc[2] = DefifaTierCashOutWeight({id: 3, cashOutWeight: 0});
227
- sc[3] = DefifaTierCashOutWeight({id: 4, cashOutWeight: 0});
228
- uint256 proposalId = _gov.submitScorecardFor(_gameId, sc);
229
-
230
- // Disinterested users attest (full BWA power since 0 weight tiers).
231
- vm.prank(disinterested1);
232
- _gov.attestToScorecardFrom(_gameId, proposalId);
233
- vm.prank(disinterested2);
234
- _gov.attestToScorecardFrom(_gameId, proposalId);
235
- vm.prank(disinterested3);
236
- _gov.attestToScorecardFrom(_gameId, proposalId);
237
-
238
- vm.warp(block.timestamp + _gov.attestationGracePeriodOf(_gameId) + 1);
239
- _gov.ratifyScorecardFrom(_gameId, sc);
240
-
241
- // Mint the reserve NFTs for tier 1.
242
- JB721TiersMintReservesConfig[] memory reserveConfigs = new JB721TiersMintReservesConfig[](1);
243
- reserveConfigs[0] = JB721TiersMintReservesConfig({tierId: 1, count: 1});
244
- _nft.mintReservesFor(reserveConfigs);
245
- assertEq(_nft.balanceOf(reserveBeneficiary), 1, "reserve NFT should be minted");
246
-
247
- // Now player cashes out.
248
- uint256 beforePlayerBalance = player.balance;
249
- _cashOut(player, 1, 1);
250
- uint256 playerReclaim = player.balance - beforePlayerBalance;
251
-
252
- // Reserve holder cashes out (token ID for tier 1, token number 2).
253
- uint256 beforeReserveBalance = reserveBeneficiary.balance;
254
- _cashOut(reserveBeneficiary, 1, 2);
255
- uint256 reserveReclaim = reserveBeneficiary.balance - beforeReserveBalance;
256
-
257
- // Both should get approximately equal shares.
258
- assertApproxEqAbs(
259
- playerReclaim,
260
- reserveReclaim,
261
- 1, // 1 wei tolerance
262
- "paid and reserve holders should get equal shares"
263
- );
264
- }
265
-
266
- // ---- helpers ----
267
-
268
- function _launchData() internal returns (DefifaLaunchProjectData memory) {
269
- DefifaTierParams[] memory tp = new DefifaTierParams[](4);
270
- // Tier 1: has reserves (the tier under test)
271
- tp[0] = DefifaTierParams({
272
- reservedRate: 1, // 1 reserve per mint
273
- reservedTokenBeneficiary: reserveBeneficiary,
274
- encodedIPFSUri: bytes32(0),
275
- shouldUseReservedTokenBeneficiaryAsDefault: false,
276
- name: "TEAM"
277
- });
278
- // Tiers 2-4: disinterested attestors (no reserves, standard rate)
279
- for (uint256 i = 1; i < 4; i++) {
280
- tp[i] = DefifaTierParams({
281
- reservedRate: 1001,
282
- reservedTokenBeneficiary: address(0),
283
- encodedIPFSUri: bytes32(0),
284
- shouldUseReservedTokenBeneficiaryAsDefault: false,
285
- name: "TEAM"
286
- });
287
- }
288
-
289
- return DefifaLaunchProjectData({
290
- name: "DEFIFA",
291
- projectUri: "",
292
- contractUri: "",
293
- baseUri: "",
294
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
295
- mintPeriodDuration: 1 days,
296
- start: uint48(block.timestamp + 3 days),
297
- refundPeriodDuration: 1 days,
298
- store: new JB721TiersHookStore(),
299
- splits: new JBSplit[](0),
300
- attestationStartTime: 0,
301
- attestationGracePeriod: 100_381,
302
- defaultAttestationDelegate: address(0),
303
- tierPrice: 1 ether,
304
- tiers: tp,
305
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
306
- terminal: jbMultiTerminal(),
307
- minParticipation: 0,
308
- scorecardTimeout: 0,
309
- timelockDuration: 0
310
- });
311
- }
312
-
313
- function _launch(DefifaLaunchProjectData memory d) internal returns (uint256 p, DefifaHook n, DefifaGovernor g) {
314
- g = governor;
315
- p = deployer.launchGameWith(d);
316
- JBRuleset memory fc = jbRulesets().currentOf(p);
317
- if (fc.dataHook() == address(0)) (fc,) = jbRulesets().latestQueuedOf(p);
318
- n = DefifaHook(fc.dataHook());
319
- }
320
-
321
- function _mint(address user, uint256 tid, uint256 amt) internal {
322
- vm.deal(user, amt);
323
- uint16[] memory m = new uint16[](1);
324
- // forge-lint: disable-next-line(unsafe-typecast)
325
- m[0] = uint16(tid);
326
- bytes[] memory data = new bytes[](1);
327
- data[0] = abi.encode(user, m);
328
- bytes4[] memory ids = new bytes4[](1);
329
- ids[0] = metadataHelper().getId("pay", address(hook));
330
- bytes memory metadata = metadataHelper().createMetadata(ids, data);
331
- vm.prank(user);
332
- jbMultiTerminal().pay{value: amt}(_pid, JBConstants.NATIVE_TOKEN, amt, user, 0, "", metadata);
333
- }
334
-
335
- function _delegateSelf(address user, uint256 tid) internal {
336
- DefifaDelegation[] memory dd = new DefifaDelegation[](1);
337
- dd[0] = DefifaDelegation({delegatee: user, tierId: tid});
338
- vm.prank(user);
339
- _nft.setTierDelegatesTo(dd);
340
- }
341
-
342
- function _cashOut(address user, uint256 tid, uint256 tnum) internal {
343
- bytes memory meta = _cashOutMeta(tid, tnum);
344
- vm.prank(user);
345
- jbMultiTerminal()
346
- .cashOutTokensOf({
347
- holder: user,
348
- projectId: _pid,
349
- cashOutCount: 0,
350
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
351
- minTokensReclaimed: 0,
352
- beneficiary: payable(user),
353
- metadata: meta
354
- });
355
- }
356
-
357
- function _cashOutMeta(uint256 tid, uint256 tnum) internal view returns (bytes memory) {
358
- uint256[] memory cid = new uint256[](1);
359
- cid[0] = (tid * 1_000_000_000) + tnum;
360
- bytes[] memory data = new bytes[](1);
361
- data[0] = abi.encode(cid);
362
- bytes4[] memory ids = new bytes4[](1);
363
- ids[0] = metadataHelper().getId("cashOut", address(hook));
364
- return metadataHelper().createMetadata(ids, data);
365
- }
366
- }
@@ -1,184 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
5
- import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
6
- import {DefifaHook} from "../../src/DefifaHook.sol";
7
- import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
8
- import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
9
- import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
10
-
11
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
12
- import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
13
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
14
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
15
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
16
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
17
- import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
18
- import {JBRulesetConfig, JBTerminalConfig} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
19
- import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
20
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
21
- import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
22
- import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
23
- import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
24
- import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
25
-
26
- import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
27
- import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
28
-
29
- /// @notice Tests for H-5 audit fix: tier cap of 128 enforced in launchGameWith().
30
- contract H5TierCapValidationTest is JBTest, TestBaseWorkflow {
31
- DefifaDeployer internal deployer;
32
- DefifaGovernor internal governor;
33
- DefifaHook internal hookCodeOrigin;
34
-
35
- function setUp() public virtual override {
36
- super.setUp();
37
-
38
- JBAccountingContext[] memory tokens = new JBAccountingContext[](1);
39
- tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
40
-
41
- JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
42
- terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokens});
43
-
44
- JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
45
- rulesetConfigs[0] = JBRulesetConfig({
46
- mustStartAtOrAfter: 0,
47
- duration: 10 days,
48
- weight: 1e18,
49
- weightCutPercent: 0,
50
- approvalHook: IJBRulesetApprovalHook(address(0)),
51
- metadata: JBRulesetMetadata({
52
- reservedPercent: 0,
53
- cashOutTaxRate: 0,
54
- baseCurrency: JBCurrencyIds.ETH,
55
- pausePay: false,
56
- pauseCreditTransfers: false,
57
- allowOwnerMinting: false,
58
- allowSetCustomToken: false,
59
- allowTerminalMigration: false,
60
- allowSetTerminals: false,
61
- allowSetController: false,
62
- allowAddAccountingContext: false,
63
- allowAddPriceFeed: false,
64
- ownerMustSendPayouts: false,
65
- holdFees: false,
66
- useTotalSurplusForCashOuts: false,
67
- useDataHookForPay: true,
68
- useDataHookForCashOut: true,
69
- dataHook: address(0),
70
- metadata: 0
71
- }),
72
- splitGroups: new JBSplitGroup[](0),
73
- fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
74
- });
75
-
76
- address projectOwner = address(bytes20(keccak256("projectOwner")));
77
- uint256 protocolFeeProjectId =
78
- jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
79
- vm.prank(projectOwner);
80
- address nanaToken =
81
- address(jbController().deployERC20For(protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
82
-
83
- uint256 defifaProjectId = jbController().launchProjectFor(projectOwner, "", rulesetConfigs, terminalConfigs, "");
84
- vm.prank(projectOwner);
85
- address defifaToken = address(jbController().deployERC20For(defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
86
-
87
- hookCodeOrigin = new DefifaHook(jbDirectory(), IERC20(defifaToken), IERC20(nanaToken));
88
- governor = new DefifaGovernor(jbController(), address(this));
89
- deployer = new DefifaDeployer(
90
- address(hookCodeOrigin),
91
- new DefifaTokenUriResolver(ITypeface(address(0))),
92
- governor,
93
- jbController(),
94
- new JBAddressRegistry(),
95
- defifaProjectId,
96
- protocolFeeProjectId
97
- );
98
-
99
- hookCodeOrigin.transferOwnership(address(deployer));
100
- governor.transferOwnership(address(deployer));
101
- }
102
-
103
- /// @notice Launching with exactly 128 tiers should succeed (boundary).
104
- function test_launch128TiersSucceeds() external {
105
- DefifaLaunchProjectData memory data = _launchData(128);
106
- uint256 gameId = deployer.launchGameWith(data);
107
- assertGt(gameId, 0, "game should be created with 128 tiers");
108
- }
109
-
110
- /// @notice Launching with 129 tiers must revert with DefifaDeployer_InvalidGameConfiguration.
111
- function test_launch129TiersReverts() external {
112
- DefifaLaunchProjectData memory data = _launchData(129);
113
- vm.expectRevert(DefifaDeployer.DefifaDeployer_InvalidGameConfiguration.selector);
114
- deployer.launchGameWith(data);
115
- }
116
-
117
- /// @notice Launching with 1 tier should succeed (minimum valid).
118
- function test_launch1TierSucceeds() external {
119
- DefifaLaunchProjectData memory data = _launchData(1);
120
- uint256 gameId = deployer.launchGameWith(data);
121
- assertGt(gameId, 0, "game should be created with 1 tier");
122
- }
123
-
124
- /// @notice Launching with 0 tiers does not revert at the tier cap check (no lower-bound validation exists).
125
- /// @dev This documents current behavior: the deployer only enforces the upper cap of 128.
126
- function test_launch0TiersDoesNotRevertAtTierCap() external {
127
- DefifaLaunchProjectData memory data = _launchData(0);
128
- uint256 gameId = deployer.launchGameWith(data);
129
- assertGt(gameId, 0, "0-tier game created (no lower-bound check)");
130
- }
131
-
132
- /// @notice Fuzz: any tier count above 128 reverts, any from 1-128 succeeds.
133
- function test_fuzz_tierCapBoundary(uint256 tierCount) external {
134
- tierCount = bound(tierCount, 1, 256);
135
- DefifaLaunchProjectData memory data = _launchData(tierCount);
136
-
137
- if (tierCount > 128) {
138
- vm.expectRevert(DefifaDeployer.DefifaDeployer_InvalidGameConfiguration.selector);
139
- deployer.launchGameWith(data);
140
- } else {
141
- uint256 gameId = deployer.launchGameWith(data);
142
- assertGt(gameId, 0, "game should be created within tier cap");
143
- }
144
- }
145
-
146
- // ─── Helpers
147
- // ─────────────────────────────────────────────────────────────────
148
-
149
- function _launchData(uint256 tierCount) internal returns (DefifaLaunchProjectData memory) {
150
- DefifaTierParams[] memory tiers = new DefifaTierParams[](tierCount);
151
- for (uint256 i; i < tierCount; i++) {
152
- tiers[i] = DefifaTierParams({
153
- name: "TEAM",
154
- reservedRate: 0,
155
- reservedTokenBeneficiary: address(0),
156
- encodedIPFSUri: bytes32(0),
157
- shouldUseReservedTokenBeneficiaryAsDefault: false
158
- });
159
- }
160
-
161
- return DefifaLaunchProjectData({
162
- name: "DEFIFA",
163
- projectUri: "",
164
- contractUri: "",
165
- baseUri: "",
166
- tiers: tiers,
167
- tierPrice: 1 ether,
168
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
169
- mintPeriodDuration: 1 days,
170
- refundPeriodDuration: 1 days,
171
- start: uint48(block.timestamp + 3 days),
172
- splits: new JBSplit[](0),
173
- attestationStartTime: 0,
174
- attestationGracePeriod: 100_381,
175
- defaultAttestationDelegate: address(0),
176
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
177
- terminal: jbMultiTerminal(),
178
- store: new JB721TiersHookStore(),
179
- minParticipation: 0,
180
- scorecardTimeout: 7 days,
181
- timelockDuration: 0
182
- });
183
- }
184
- }