@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,279 +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 {DefifaScorecardState} from "../../src/enums/DefifaScorecardState.sol";
11
- import {IDefifaDeployer} from "../../src/interfaces/IDefifaDeployer.sol";
12
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
13
-
14
- import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.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 {Strings} from "@openzeppelin/contracts/utils/Strings.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 {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
33
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
34
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
35
- import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
36
- import {JBMultiTerminal} from "@bananapus/core-v6/src/JBMultiTerminal.sol";
37
-
38
- /// @dev Helper to read block.timestamp via an external call, bypassing the via-ir optimizer's timestamp caching.
39
- contract TimestampReader3 {
40
- function timestamp() external view returns (uint256) {
41
- return block.timestamp;
42
- }
43
- }
44
-
45
- /// @title FulfillmentBlocksRatification
46
- /// @notice Regression test: ratification should succeed even when sendPayoutsOf reverts inside fulfillCommitmentsOf.
47
- /// @dev Tests the internal try-catch wrapper around sendPayoutsOf in fulfillCommitmentsOf.
48
- /// The test verifies that the CommitmentPayoutFailed event is emitted and ratification completes.
49
- contract FulfillmentBlocksRatification is JBTest, TestBaseWorkflow {
50
- using JBRulesetMetadataResolver for JBRuleset;
51
-
52
- TimestampReader3 private _tsReader = new TimestampReader3();
53
-
54
- address _protocolFeeProjectTokenAccount;
55
- address _defifaProjectTokenAccount;
56
- uint256 _protocolFeeProjectId;
57
- uint256 _defifaProjectId;
58
- uint256 _gameId = 3;
59
-
60
- DefifaDeployer deployer;
61
- DefifaHook hook;
62
- DefifaGovernor governor;
63
-
64
- address projectOwner = address(bytes20(keccak256("projectOwner")));
65
-
66
- function setUp() public virtual override {
67
- super.setUp();
68
-
69
- JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
70
- _tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
71
-
72
- JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
73
- terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
74
-
75
- JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
76
- rulesetConfigs[0] = JBRulesetConfig({
77
- mustStartAtOrAfter: 0,
78
- duration: 10 days,
79
- weight: 1e18,
80
- weightCutPercent: 0,
81
- approvalHook: IJBRulesetApprovalHook(address(0)),
82
- metadata: JBRulesetMetadata({
83
- reservedPercent: 0,
84
- cashOutTaxRate: 0,
85
- baseCurrency: JBCurrencyIds.ETH,
86
- pausePay: false,
87
- pauseCreditTransfers: false,
88
- allowOwnerMinting: false,
89
- allowSetCustomToken: false,
90
- allowTerminalMigration: false,
91
- allowSetTerminals: false,
92
- allowSetController: false,
93
- allowAddAccountingContext: false,
94
- allowAddPriceFeed: false,
95
- ownerMustSendPayouts: false,
96
- holdFees: false,
97
- useTotalSurplusForCashOuts: false,
98
- useDataHookForPay: true,
99
- useDataHookForCashOut: true,
100
- dataHook: address(0),
101
- metadata: 0
102
- }),
103
- splitGroups: new JBSplitGroup[](0),
104
- fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
105
- });
106
-
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
- _defifaProjectId =
114
- jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
115
- vm.prank(projectOwner);
116
- _defifaProjectTokenAccount =
117
- address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
118
-
119
- hook = new DefifaHook(
120
- jbDirectory(), IERC20(address(_defifaProjectTokenAccount)), IERC20(_protocolFeeProjectTokenAccount)
121
- );
122
- governor = new DefifaGovernor(jbController(), address(this));
123
- JBAddressRegistry _registry = new JBAddressRegistry();
124
- DefifaTokenUriResolver _tokenUriResolver = new DefifaTokenUriResolver(ITypeface(address(0)));
125
- deployer = new DefifaDeployer(
126
- address(hook),
127
- _tokenUriResolver,
128
- governor,
129
- jbController(),
130
- _registry,
131
- _defifaProjectId,
132
- _protocolFeeProjectId
133
- );
134
-
135
- hook.transferOwnership(address(deployer));
136
- governor.transferOwnership(address(deployer));
137
- }
138
-
139
- /// @notice Test that ratification emits CommitmentPayoutFailed when sendPayoutsOf reverts,
140
- /// but the scorecard is still ratified and the final ruleset is queued.
141
- /// @dev We mock sendPayoutsOf to revert, then verify ratification still succeeds.
142
- function test_ratificationSucceedsWhenPayoutReverts() public {
143
- uint8 nTiers = 4;
144
- address[] memory _users = new address[](nTiers);
145
- DefifaLaunchProjectData memory defifaData = _getBasicLaunchData(nTiers);
146
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = _createProject(defifaData);
147
-
148
- // Phase 1: Mint
149
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
150
- for (uint256 i = 0; i < nTiers; i++) {
151
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
152
- vm.deal(_users[i], 1 ether);
153
- uint16[] memory rawMetadata = new uint16[](1);
154
- // forge-lint: disable-next-line(unsafe-typecast)
155
- rawMetadata[0] = uint16(i + 1);
156
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
157
- vm.prank(_users[i]);
158
- jbMultiTerminal().pay{value: 1 ether}(
159
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
160
- );
161
- vm.warp(_tsReader.timestamp() + 1);
162
- }
163
-
164
- // Warp to scoring phase
165
- vm.warp(defifaData.start + 1);
166
-
167
- // Build scorecards
168
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
169
- uint256 weightPerTier = _nft.TOTAL_CASHOUT_WEIGHT() / nTiers;
170
- uint256 assigned;
171
- for (uint256 i = 0; i < nTiers; i++) {
172
- scorecards[i].id = i + 1;
173
- scorecards[i].cashOutWeight = weightPerTier;
174
- assigned += weightPerTier;
175
- }
176
- if (assigned < _nft.TOTAL_CASHOUT_WEIGHT()) {
177
- scorecards[0].cashOutWeight += _nft.TOTAL_CASHOUT_WEIGHT() - assigned;
178
- }
179
-
180
- // Submit and attest
181
- uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
182
- vm.warp(_tsReader.timestamp() + _governor.attestationStartTimeOf(_gameId) + 1);
183
- for (uint256 i = 0; i < _users.length; i++) {
184
- vm.prank(_users[i]);
185
- _governor.attestToScorecardFrom(_gameId, _proposalId);
186
- }
187
- vm.warp(_tsReader.timestamp() + _governor.attestationGracePeriodOf(_gameId) + 1);
188
-
189
- // Mock sendPayoutsOf on the terminal to revert
190
- vm.mockCallRevert(
191
- address(jbMultiTerminal()),
192
- abi.encodeWithSelector(JBMultiTerminal.sendPayoutsOf.selector),
193
- abi.encodeWithSignature("Error(string)", "simulated payout failure")
194
- );
195
-
196
- // Ratification should succeed. CommitmentPayoutFailed is emitted from the deployer.
197
- vm.expectEmit(true, false, false, false);
198
- emit IDefifaDeployer.CommitmentPayoutFailed(_gameId, 0, "");
199
-
200
- _governor.ratifyScorecardFrom(_gameId, scorecards);
201
-
202
- // Clear mock
203
- vm.clearMockedCalls();
204
-
205
- // Verify the scorecard was ratified
206
- assertEq(
207
- _governor.ratifiedScorecardIdOf(_gameId), _proposalId, "Scorecard should be ratified despite payout failure"
208
- );
209
-
210
- // Verify the state is RATIFIED
211
- assertEq(
212
- uint256(_governor.stateOf(_gameId, _proposalId)),
213
- uint256(DefifaScorecardState.RATIFIED),
214
- "Scorecard state should be RATIFIED"
215
- );
216
-
217
- // Verify fulfilledCommitmentsOf is sentinel (1)
218
- assertEq(deployer.fulfilledCommitmentsOf(_projectId), 1, "should be sentinel value 1");
219
- }
220
-
221
- // ----- Internal helpers ------
222
-
223
- function _getBasicLaunchData(uint8 nTiers) internal returns (DefifaLaunchProjectData memory) {
224
- DefifaTierParams[] memory tierParams = new DefifaTierParams[](nTiers);
225
- for (uint256 i = 0; i < nTiers; i++) {
226
- tierParams[i] = DefifaTierParams({
227
- reservedRate: 1001,
228
- reservedTokenBeneficiary: address(0),
229
- encodedIPFSUri: bytes32(0),
230
- shouldUseReservedTokenBeneficiaryAsDefault: false,
231
- name: "DEFIFA"
232
- });
233
- }
234
-
235
- return DefifaLaunchProjectData({
236
- name: "DEFIFA",
237
- projectUri: "",
238
- contractUri: "",
239
- baseUri: "",
240
- tierPrice: 1 ether,
241
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
242
- mintPeriodDuration: 1 days,
243
- start: uint48(block.timestamp + 3 days),
244
- refundPeriodDuration: 1 days,
245
- store: new JB721TiersHookStore(),
246
- splits: new JBSplit[](0),
247
- attestationStartTime: 0,
248
- attestationGracePeriod: 100_381,
249
- defaultAttestationDelegate: address(0),
250
- tiers: tierParams,
251
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
252
- terminal: jbMultiTerminal(),
253
- minParticipation: 0,
254
- scorecardTimeout: 0,
255
- timelockDuration: 0
256
- });
257
- }
258
-
259
- function _createProject(DefifaLaunchProjectData memory defifaLaunchData)
260
- internal
261
- returns (uint256 projectId, DefifaHook nft, DefifaGovernor _governor)
262
- {
263
- _governor = governor;
264
- (projectId) = deployer.launchGameWith(defifaLaunchData);
265
- JBRuleset memory _fc = jbRulesets().currentOf(projectId);
266
- if (_fc.dataHook() == address(0)) {
267
- (_fc,) = jbRulesets().latestQueuedOf(projectId);
268
- }
269
- nft = DefifaHook(_fc.dataHook());
270
- }
271
-
272
- function _buildPayMetadata(bytes memory metadata) internal view returns (bytes memory) {
273
- bytes[] memory data = new bytes[](1);
274
- data[0] = metadata;
275
- bytes4[] memory ids = new bytes4[](1);
276
- ids[0] = metadataHelper().getId("pay", address(hook));
277
- return metadataHelper().createMetadata(ids, data);
278
- }
279
- }
@@ -1,302 +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 {DefifaScorecardState} from "../../src/enums/DefifaScorecardState.sol";
11
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
12
-
13
- import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
14
- import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
15
- import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.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 {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 {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
28
- import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
29
- import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
30
- import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
31
- import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
32
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
33
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
34
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
35
- import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
36
-
37
- /// @dev Helper to read block.timestamp via an external call, bypassing the via-ir optimizer's timestamp caching.
38
- contract TimestampReader2 {
39
- function timestamp() external view returns (uint256) {
40
- return block.timestamp;
41
- }
42
- }
43
-
44
- /// @title GracePeriodBypass
45
- /// @notice Regression test: grace period should extend from attestation start, not submission time.
46
- /// When a scorecard is submitted early (before attestationStartTime), the grace period
47
- /// must not expire before attestations begin.
48
- contract GracePeriodBypass is JBTest, TestBaseWorkflow {
49
- using JBRulesetMetadataResolver for JBRuleset;
50
-
51
- TimestampReader2 private _tsReader = new TimestampReader2();
52
-
53
- address _protocolFeeProjectTokenAccount;
54
- address _defifaProjectTokenAccount;
55
- uint256 _protocolFeeProjectId;
56
- uint256 _defifaProjectId;
57
- uint256 _gameId = 3;
58
-
59
- DefifaDeployer deployer;
60
- DefifaHook hook;
61
- DefifaGovernor governor;
62
-
63
- address projectOwner = address(bytes20(keccak256("projectOwner")));
64
-
65
- function setUp() public virtual override {
66
- super.setUp();
67
-
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
- _protocolFeeProjectId =
107
- jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
108
- vm.prank(projectOwner);
109
- _protocolFeeProjectTokenAccount =
110
- address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
111
-
112
- _defifaProjectId =
113
- jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
114
- vm.prank(projectOwner);
115
- _defifaProjectTokenAccount =
116
- address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
117
-
118
- hook = new DefifaHook(
119
- jbDirectory(), IERC20(address(_defifaProjectTokenAccount)), IERC20(_protocolFeeProjectTokenAccount)
120
- );
121
- governor = new DefifaGovernor(jbController(), address(this));
122
- JBAddressRegistry _registry = new JBAddressRegistry();
123
- DefifaTokenUriResolver _tokenUriResolver = new DefifaTokenUriResolver(ITypeface(address(0)));
124
- deployer = new DefifaDeployer(
125
- address(hook),
126
- _tokenUriResolver,
127
- governor,
128
- jbController(),
129
- _registry,
130
- _defifaProjectId,
131
- _protocolFeeProjectId
132
- );
133
-
134
- hook.transferOwnership(address(deployer));
135
- governor.transferOwnership(address(deployer));
136
- }
137
-
138
- /// @notice Test that grace period extends from attestation start, not submission time.
139
- /// @dev With the fix, a scorecard submitted early should have its grace period start after
140
- /// attestationsBegin, ensuring the grace period doesn't expire before attestations start.
141
- function test_gracePeriodExtendsFromAttestationStart() public {
142
- uint8 nTiers = 4;
143
- address[] memory _users = new address[](nTiers);
144
-
145
- // Set attestation start time far in the future (e.g. block.timestamp + 10 days)
146
- // Grace period of 1 day
147
- uint256 futureAttestationStart = block.timestamp + 10 days;
148
- uint256 gracePeriod = 1 days;
149
-
150
- DefifaLaunchProjectData memory defifaData =
151
- _getBasicLaunchDataWithAttestationTiming(nTiers, futureAttestationStart, gracePeriod);
152
- (uint256 _projectId, DefifaHook _nft, DefifaGovernor _governor) = _createProject(defifaData);
153
-
154
- // Phase 1: Mint
155
- vm.warp(defifaData.start - defifaData.mintPeriodDuration - defifaData.refundPeriodDuration);
156
- for (uint256 i = 0; i < nTiers; i++) {
157
- _users[i] = address(bytes20(keccak256(abi.encode("user", Strings.toString(i)))));
158
- vm.deal(_users[i], 1 ether);
159
-
160
- uint16[] memory rawMetadata = new uint16[](1);
161
- // forge-lint: disable-next-line(unsafe-typecast)
162
- rawMetadata[0] = uint16(i + 1);
163
- bytes memory metadata = _buildPayMetadata(abi.encode(_users[i], rawMetadata));
164
-
165
- vm.prank(_users[i]);
166
- jbMultiTerminal().pay{value: 1 ether}(
167
- _projectId, JBConstants.NATIVE_TOKEN, 1 ether, _users[i], 0, "", metadata
168
- );
169
-
170
- DefifaDelegation[] memory delegations = new DefifaDelegation[](1);
171
- delegations[0] = DefifaDelegation({delegatee: _users[i], tierId: uint256(i + 1)});
172
- vm.prank(_users[i]);
173
- _nft.setTierDelegatesTo(delegations);
174
-
175
- vm.warp(_tsReader.timestamp() + 1);
176
- }
177
-
178
- // Warp to scoring phase
179
- vm.warp(defifaData.start + 1);
180
-
181
- // Submit scorecard early (attestation start time is still in the future)
182
- DefifaTierCashOutWeight[] memory scorecards = new DefifaTierCashOutWeight[](nTiers);
183
- uint256 weightPerTier = _nft.TOTAL_CASHOUT_WEIGHT() / nTiers;
184
- uint256 assigned;
185
- for (uint256 i = 0; i < nTiers; i++) {
186
- scorecards[i].id = i + 1;
187
- scorecards[i].cashOutWeight = weightPerTier;
188
- assigned += weightPerTier;
189
- }
190
- if (assigned < _nft.TOTAL_CASHOUT_WEIGHT()) {
191
- scorecards[0].cashOutWeight += _nft.TOTAL_CASHOUT_WEIGHT() - assigned;
192
- }
193
-
194
- uint256 submissionTime = _tsReader.timestamp();
195
- uint256 _proposalId = _governor.submitScorecardFor(_gameId, scorecards);
196
-
197
- // The scorecard should be PENDING (attestations haven't started yet)
198
- assertEq(
199
- uint256(_governor.stateOf(_gameId, _proposalId)),
200
- uint256(DefifaScorecardState.PENDING),
201
- "Scorecard should be PENDING before attestation start"
202
- );
203
-
204
- // Key assertion: warp past the old grace period end (submissionTime + gracePeriod)
205
- // but BEFORE attestations begin. The scorecard should still be PENDING, NOT in a post-grace state.
206
- vm.warp(submissionTime + gracePeriod + 1);
207
-
208
- // With the fix, the scorecard should still be PENDING because attestationsBegin hasn't arrived yet.
209
- assertEq(
210
- uint256(_governor.stateOf(_gameId, _proposalId)),
211
- uint256(DefifaScorecardState.PENDING),
212
- "Scorecard should still be PENDING even after old grace period would have ended"
213
- );
214
-
215
- // Now warp to after attestation start (attestation begin + 1)
216
- vm.warp(futureAttestationStart + 1);
217
-
218
- // Now the scorecard should be ACTIVE (attestations are open and grace period hasn't ended yet)
219
- assertEq(
220
- uint256(_governor.stateOf(_gameId, _proposalId)),
221
- uint256(DefifaScorecardState.ACTIVE),
222
- "Scorecard should be ACTIVE after attestation start but before grace period ends"
223
- );
224
-
225
- // Warp to after attestation start + grace period
226
- vm.warp(futureAttestationStart + gracePeriod + 1);
227
-
228
- // Now grace period has truly ended, so the state should be ACTIVE (quorum not met)
229
- // The key here is that it transitioned properly - grace period ran from attestation start
230
- assertEq(
231
- uint256(_governor.stateOf(_gameId, _proposalId)),
232
- uint256(DefifaScorecardState.ACTIVE),
233
- "Scorecard should be ACTIVE (no quorum) after grace period truly ends"
234
- );
235
- }
236
-
237
- // ----- Internal helpers ------
238
-
239
- function _getBasicLaunchDataWithAttestationTiming(
240
- uint8 nTiers,
241
- uint256 attestationStartTime,
242
- uint256 attestationGracePeriod
243
- )
244
- internal
245
- returns (DefifaLaunchProjectData memory)
246
- {
247
- DefifaTierParams[] memory tierParams = new DefifaTierParams[](nTiers);
248
- for (uint256 i = 0; i < nTiers; i++) {
249
- tierParams[i] = DefifaTierParams({
250
- reservedRate: 1001,
251
- reservedTokenBeneficiary: address(0),
252
- encodedIPFSUri: bytes32(0),
253
- shouldUseReservedTokenBeneficiaryAsDefault: false,
254
- name: "DEFIFA"
255
- });
256
- }
257
-
258
- return DefifaLaunchProjectData({
259
- name: "DEFIFA",
260
- projectUri: "",
261
- contractUri: "",
262
- baseUri: "",
263
- tierPrice: 1 ether,
264
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
265
- mintPeriodDuration: 1 days,
266
- start: uint48(block.timestamp + 3 days),
267
- refundPeriodDuration: 1 days,
268
- store: new JB721TiersHookStore(),
269
- splits: new JBSplit[](0),
270
- attestationStartTime: attestationStartTime,
271
- attestationGracePeriod: attestationGracePeriod,
272
- defaultAttestationDelegate: address(0),
273
- tiers: tierParams,
274
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
275
- terminal: jbMultiTerminal(),
276
- minParticipation: 0,
277
- scorecardTimeout: 0,
278
- timelockDuration: 0
279
- });
280
- }
281
-
282
- function _createProject(DefifaLaunchProjectData memory defifaLaunchData)
283
- internal
284
- returns (uint256 projectId, DefifaHook nft, DefifaGovernor _governor)
285
- {
286
- _governor = governor;
287
- (projectId) = deployer.launchGameWith(defifaLaunchData);
288
- JBRuleset memory _fc = jbRulesets().currentOf(projectId);
289
- if (_fc.dataHook() == address(0)) {
290
- (_fc,) = jbRulesets().latestQueuedOf(projectId);
291
- }
292
- nft = DefifaHook(_fc.dataHook());
293
- }
294
-
295
- function _buildPayMetadata(bytes memory metadata) internal view returns (bytes memory) {
296
- bytes[] memory data = new bytes[](1);
297
- data[0] = metadata;
298
- bytes4[] memory ids = new bytes4[](1);
299
- ids[0] = metadataHelper().getId("pay", address(hook));
300
- return metadataHelper().createMetadata(ids, data);
301
- }
302
- }