@ballkidz/defifa 0.0.24 → 0.0.26

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 (50) 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 +74 -46
  8. package/src/DefifaGovernor.sol +53 -11
  9. package/src/DefifaHook.sol +79 -25
  10. package/src/DefifaTokenUriResolver.sol +111 -19
  11. package/src/interfaces/IDefifaDeployer.sol +5 -0
  12. package/src/interfaces/IDefifaGovernor.sol +4 -0
  13. package/src/interfaces/IDefifaHook.sol +5 -0
  14. package/src/libraries/DefifaHookLib.sol +9 -10
  15. package/src/structs/DefifaLaunchProjectData.sol +0 -3
  16. package/CRYPTO_ECON.pdf +0 -0
  17. package/CRYPTO_ECON.tex +0 -997
  18. package/foundry.lock +0 -17
  19. package/references/operations.md +0 -32
  20. package/references/runtime.md +0 -43
  21. package/slither-ci.config.json +0 -10
  22. package/sphinx.lock +0 -521
  23. package/test/BWAFunctionComparison.t.sol +0 -1320
  24. package/test/DefifaAdversarialQuorum.t.sol +0 -617
  25. package/test/DefifaAuditLowGuards.t.sol +0 -308
  26. package/test/DefifaFeeAccounting.t.sol +0 -581
  27. package/test/DefifaGovernanceHardening.t.sol +0 -1315
  28. package/test/DefifaGovernor.t.sol +0 -1378
  29. package/test/DefifaHookRegressions.t.sol +0 -415
  30. package/test/DefifaMintCostInvariant.t.sol +0 -319
  31. package/test/DefifaNoContest.t.sol +0 -941
  32. package/test/DefifaSecurity.t.sol +0 -741
  33. package/test/DefifaUSDC.t.sol +0 -480
  34. package/test/Fork.t.sol +0 -2388
  35. package/test/TestAuditGaps.sol +0 -984
  36. package/test/TestQALastMile.t.sol +0 -514
  37. package/test/audit/AttestationDoubleCount.t.sol +0 -218
  38. package/test/audit/CodexNemesisCurrencyMismatchBypass.t.sol +0 -112
  39. package/test/audit/CodexNemesisNoContestReserveDrain.t.sol +0 -238
  40. package/test/audit/CodexRegistryMismatch.t.sol +0 -191
  41. package/test/audit/CodexTierCapMismatch.t.sol +0 -171
  42. package/test/audit/CurrencyMismatchFix.t.sol +0 -265
  43. package/test/audit/FixPendingReserveDilution.t.sol +0 -366
  44. package/test/audit/H5TierCapValidation.t.sol +0 -184
  45. package/test/audit/PendingReserveDilution.t.sol +0 -298
  46. package/test/audit/PendingReserveQuorumGrief.t.sol +0 -355
  47. package/test/audit/PendingReserveSnapshotBypass.t.sol +0 -319
  48. package/test/regression/AttestationDelegateBeneficiary.t.sol +0 -271
  49. package/test/regression/FulfillmentBlocksRatification.t.sol +0 -279
  50. package/test/regression/GracePeriodBypass.t.sol +0 -302
@@ -1,218 +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 {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
21
- import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
22
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
23
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
24
- import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
25
- import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
26
- import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
27
- import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
28
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
29
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
30
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
31
- import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
32
-
33
- contract AttestationDoubleCountTest is JBTest, TestBaseWorkflow {
34
- using JBRulesetMetadataResolver for JBRuleset;
35
-
36
- uint256 internal _protocolFeeProjectId;
37
- uint256 internal _defifaProjectId;
38
-
39
- DefifaDeployer internal deployer;
40
- DefifaHook internal hook;
41
- DefifaGovernor internal governor;
42
-
43
- uint256 internal _mintPhaseStart;
44
-
45
- function setUp() public virtual override {
46
- super.setUp();
47
-
48
- JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
49
- _tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
50
-
51
- JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
52
- terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
53
-
54
- JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
55
- rulesetConfigs[0] = JBRulesetConfig({
56
- mustStartAtOrAfter: 0,
57
- duration: 10 days,
58
- weight: 1e18,
59
- weightCutPercent: 0,
60
- approvalHook: IJBRulesetApprovalHook(address(0)),
61
- metadata: JBRulesetMetadata({
62
- reservedPercent: 0,
63
- cashOutTaxRate: 0,
64
- baseCurrency: JBCurrencyIds.ETH,
65
- pausePay: false,
66
- pauseCreditTransfers: false,
67
- allowOwnerMinting: false,
68
- allowSetCustomToken: false,
69
- allowTerminalMigration: false,
70
- allowSetTerminals: false,
71
- allowSetController: false,
72
- allowAddAccountingContext: false,
73
- allowAddPriceFeed: false,
74
- ownerMustSendPayouts: false,
75
- holdFees: false,
76
- useTotalSurplusForCashOuts: false,
77
- useDataHookForPay: true,
78
- useDataHookForCashOut: true,
79
- dataHook: address(0),
80
- metadata: 0
81
- }),
82
- splitGroups: new JBSplitGroup[](0),
83
- fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
84
- });
85
-
86
- address projectOwner = address(bytes20(keccak256("projectOwner")));
87
-
88
- _protocolFeeProjectId =
89
- jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
90
- vm.prank(projectOwner);
91
- address _nanaToken =
92
- address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
93
-
94
- _defifaProjectId =
95
- jbController().launchProjectFor(address(projectOwner), "", rulesetConfigs, terminalConfigs, "");
96
- vm.prank(projectOwner);
97
- address _defifaToken = address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
98
-
99
- hook = new DefifaHook(jbDirectory(), IERC20(_defifaToken), IERC20(_nanaToken));
100
- governor = new DefifaGovernor(jbController(), address(this));
101
- deployer = new DefifaDeployer(
102
- address(hook),
103
- new DefifaTokenUriResolver(ITypeface(address(0))),
104
- governor,
105
- jbController(),
106
- new JBAddressRegistry(),
107
- _defifaProjectId,
108
- _protocolFeeProjectId
109
- );
110
-
111
- hook.transferOwnership(address(deployer));
112
- governor.transferOwnership(address(deployer));
113
- }
114
-
115
- function test_attestationUnitsDuplicateAfterBeneficiaryTransfer() external {
116
- address payer = address(bytes20(keccak256("payer")));
117
- address beneficiary = address(bytes20(keccak256("beneficiary")));
118
- address recipient = address(bytes20(keccak256("recipient")));
119
-
120
- (uint256 projectId, DefifaHook nft) = _launchGame();
121
- vm.warp(_mintPhaseStart);
122
-
123
- vm.deal(payer, 1 ether);
124
- uint16[] memory tierIds = new uint16[](1);
125
- tierIds[0] = 1;
126
- bytes memory metadata = _buildPayMetadata(abi.encode(address(0), tierIds));
127
-
128
- vm.prank(payer);
129
- jbMultiTerminal().pay{value: 1 ether}({
130
- projectId: projectId,
131
- token: JBConstants.NATIVE_TOKEN,
132
- amount: 1 ether,
133
- beneficiary: beneficiary,
134
- minReturnedTokens: 0,
135
- memo: "",
136
- metadata: metadata
137
- });
138
-
139
- uint256 totalUnitsBefore = nft.getTierTotalAttestationUnitsOf(1);
140
- uint256 beneficiaryUnitsBefore = nft.getTierAttestationUnitsOf(beneficiary, 1);
141
- assertGt(totalUnitsBefore, 0, "tier should have nonzero attestation units");
142
- assertEq(beneficiaryUnitsBefore, totalUnitsBefore, "beneficiary receives delegated units after mint");
143
-
144
- uint256 tokenId = 1_000_000_001;
145
- vm.prank(beneficiary);
146
- nft.transferFrom(beneficiary, recipient, tokenId);
147
-
148
- uint256 beneficiaryUnitsAfter = nft.getTierAttestationUnitsOf(beneficiary, 1);
149
- uint256 recipientUnitsAfter = nft.getTierAttestationUnitsOf(recipient, 1);
150
- uint256 totalUnitsAfter = nft.getTierTotalAttestationUnitsOf(1);
151
-
152
- // After the fix: attestation units go to beneficiary on mint, then move to recipient on transfer.
153
- // Total units stay constant, beneficiary loses units, recipient gains them.
154
- assertEq(totalUnitsAfter, totalUnitsBefore, "total tier units stay constant");
155
- assertEq(beneficiaryUnitsAfter, 0, "beneficiary loses attestation units after transferring NFT");
156
- assertEq(recipientUnitsAfter, totalUnitsAfter, "recipient receives full attestation units from transfer");
157
- // No double-counting: sum of individual units equals total.
158
- assertEq(
159
- beneficiaryUnitsAfter + recipientUnitsAfter,
160
- totalUnitsAfter,
161
- "no double-counting: sum of individual units equals total"
162
- );
163
- }
164
-
165
- function _launchGame() internal returns (uint256 projectId, DefifaHook nft) {
166
- DefifaTierParams[] memory tierParams = new DefifaTierParams[](2);
167
- for (uint256 i = 0; i < 2; i++) {
168
- tierParams[i] = DefifaTierParams({
169
- reservedRate: 1001,
170
- reservedTokenBeneficiary: address(0),
171
- encodedIPFSUri: bytes32(0),
172
- shouldUseReservedTokenBeneficiaryAsDefault: false,
173
- name: "DEFIFA"
174
- });
175
- }
176
-
177
- DefifaLaunchProjectData memory d = DefifaLaunchProjectData({
178
- name: "DEFIFA",
179
- projectUri: "",
180
- contractUri: "",
181
- baseUri: "",
182
- tierPrice: 1 ether,
183
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
184
- mintPeriodDuration: 1 days,
185
- start: uint48(block.timestamp + 3 days),
186
- refundPeriodDuration: 1 days,
187
- store: new JB721TiersHookStore(),
188
- splits: new JBSplit[](0),
189
- attestationStartTime: 0,
190
- attestationGracePeriod: 100_381,
191
- defaultAttestationDelegate: address(0),
192
- tiers: tierParams,
193
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
194
- terminal: jbMultiTerminal(),
195
- minParticipation: 0,
196
- scorecardTimeout: 0,
197
- timelockDuration: 0
198
- });
199
-
200
- _mintPhaseStart = d.start - d.mintPeriodDuration - d.refundPeriodDuration;
201
-
202
- projectId = deployer.launchGameWith(d);
203
-
204
- JBRuleset memory fc = jbRulesets().currentOf(projectId);
205
- if (fc.dataHook() == address(0)) {
206
- (fc,) = jbRulesets().latestQueuedOf(projectId);
207
- }
208
- nft = DefifaHook(fc.dataHook());
209
- }
210
-
211
- function _buildPayMetadata(bytes memory metadata) internal view returns (bytes memory) {
212
- bytes[] memory data = new bytes[](1);
213
- data[0] = metadata;
214
- bytes4[] memory ids = new bytes4[](1);
215
- ids[0] = metadataHelper().getId("pay", address(hook));
216
- return metadataHelper().createMetadata(ids, data);
217
- }
218
- }
@@ -1,112 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {DefifaUSDCTest} from "../DefifaUSDC.t.sol";
5
- import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
6
- import {DefifaTierCashOutWeight} from "../../src/structs/DefifaTierCashOutWeight.sol";
7
- import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
8
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
9
- import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
10
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
11
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
12
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
13
-
14
- /// @notice Regression test for the currency mismatch fix: ERC-20 games now correctly resolve payout limits via
15
- /// baseCurrency. Before the fix, using a non-canonical currency (e.g. currency=1 for USDC) caused sendPayoutsOf to use
16
- /// uint32(uint160(token)) which didn't match the stored payout limit currency, silently skipping payouts.
17
- /// After the fix, fulfillCommitmentsOf uses metadata.baseCurrency which always matches the stored limit.
18
- contract CodexNemesisCurrencyMismatchBypassTest is DefifaUSDCTest {
19
- /// @notice Verify that an ERC-20 game with non-canonical currency (1) correctly pays out commitment fees.
20
- function test_nonCanonicalCurrencyPayoutsNowSucceed() external {
21
- uint104 tierPrice = 100e6;
22
-
23
- (_pid, _nft, _gov) = _launch(_launchDataUsdcNonCanonical(tierPrice));
24
- _users = new address[](2);
25
- _users[0] = _addr(0);
26
- _users[1] = _addr(1);
27
-
28
- vm.warp(block.timestamp + 1 days + 1);
29
-
30
- _mintUsdc(_users[0], 1, tierPrice);
31
- _mintUsdc(_users[1], 2, tierPrice);
32
- _delegateSelf(_users[0], 1);
33
- _delegateSelf(_users[1], 2);
34
-
35
- vm.warp(block.timestamp + 2 days);
36
-
37
- DefifaTierCashOutWeight[] memory scorecard = new DefifaTierCashOutWeight[](2);
38
- scorecard[0] = DefifaTierCashOutWeight({id: 1, cashOutWeight: _nft.TOTAL_CASHOUT_WEIGHT()});
39
- scorecard[1] = DefifaTierCashOutWeight({id: 2, cashOutWeight: 0});
40
-
41
- vm.prank(_users[1]);
42
- uint256 scorecardId = _gov.submitScorecardFor(_pid, scorecard);
43
-
44
- vm.prank(_users[1]);
45
- _gov.attestToScorecardFrom(_pid, scorecardId);
46
-
47
- vm.warp(block.timestamp + _gov.attestationGracePeriodOf(_pid) + 1);
48
-
49
- uint256 preRatificationBalance = _balance();
50
- uint256 expectedFee = (preRatificationBalance * 75_000_000) / JBConstants.SPLITS_TOTAL_PERCENT;
51
-
52
- vm.prank(_users[1]);
53
- _gov.ratifyScorecardFrom(_pid, scorecard);
54
-
55
- // After the fix: payout succeeds, fulfilledCommitmentsOf stores the actual fee (not the sentinel).
56
- assertEq(
57
- deployer.fulfilledCommitmentsOf(_pid), expectedFee, "fulfilled commitments equals the expected fee amount"
58
- );
59
-
60
- // The fee has been paid out, reducing the game pot.
61
- assertEq(_balance(), preRatificationBalance - expectedFee, "balance decreased by the fee amount");
62
-
63
- // Winner cashes out and receives only the post-fee surplus, not the full pot.
64
- uint256 winnerBalBefore = usdc.balanceOf(_users[0]);
65
- _cashOutUsdc(_users[0], 1, 1);
66
- uint256 winnerReceived = usdc.balanceOf(_users[0]) - winnerBalBefore;
67
-
68
- assertEq(
69
- winnerReceived,
70
- preRatificationBalance - expectedFee,
71
- "winner receives the post-fee surplus, not the full pot"
72
- );
73
- }
74
-
75
- function _launchDataUsdcNonCanonical(uint104 tierPrice) internal returns (DefifaLaunchProjectData memory) {
76
- DefifaTierParams[] memory tp = new DefifaTierParams[](2);
77
- for (uint256 i; i < 2; i++) {
78
- tp[i] = DefifaTierParams({
79
- reservedRate: 1001,
80
- reservedTokenBeneficiary: address(0),
81
- encodedIPFSUri: bytes32(0),
82
- shouldUseReservedTokenBeneficiaryAsDefault: false,
83
- name: "DEFIFA"
84
- });
85
- }
86
-
87
- // Non-canonical currency (1 = ETH currency ID) for a USDC token.
88
- // Before the fix, this caused fulfillCommitmentsOf to silently skip payouts.
89
- return DefifaLaunchProjectData({
90
- name: "DEFIFA_USDC_NONCANONICAL",
91
- projectUri: "",
92
- contractUri: "",
93
- baseUri: "",
94
- token: JBAccountingContext({token: address(usdc), decimals: 6, currency: 1}),
95
- mintPeriodDuration: 1 days,
96
- start: uint48(block.timestamp + 3 days),
97
- refundPeriodDuration: 1 days,
98
- store: new JB721TiersHookStore(),
99
- splits: new JBSplit[](0),
100
- attestationStartTime: 0,
101
- attestationGracePeriod: 1 days,
102
- defaultAttestationDelegate: address(0),
103
- tierPrice: tierPrice,
104
- tiers: tp,
105
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
106
- terminal: jbMultiTerminal(),
107
- minParticipation: 0,
108
- scorecardTimeout: 0,
109
- timelockDuration: 0
110
- });
111
- }
112
- }
@@ -1,238 +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
- import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
6
-
7
- import {DefifaDeployer} from "../../src/DefifaDeployer.sol";
8
- import {DefifaGovernor} from "../../src/DefifaGovernor.sol";
9
- import {DefifaHook} from "../../src/DefifaHook.sol";
10
- import {DefifaTokenUriResolver} from "../../src/DefifaTokenUriResolver.sol";
11
- import {DefifaGamePhase} from "../../src/enums/DefifaGamePhase.sol";
12
- import {DefifaLaunchProjectData} from "../../src/structs/DefifaLaunchProjectData.sol";
13
- import {DefifaTierParams} from "../../src/structs/DefifaTierParams.sol";
14
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
15
- import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
16
- import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
17
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
18
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
19
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
20
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
21
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
22
- import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
23
- import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
24
- import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
25
- import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
26
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
27
- import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
28
- import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
29
- import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
30
-
31
- contract CodexNemesisNoContestReserveDrainTest is JBTest, TestBaseWorkflow {
32
- uint256 internal _protocolFeeProjectId;
33
- uint256 internal _defifaProjectId;
34
-
35
- DefifaDeployer internal _deployer;
36
- DefifaHook internal _hookImpl;
37
- DefifaGovernor internal _governorImpl;
38
-
39
- address internal _projectOwner = address(bytes20(keccak256("projectOwner")));
40
- address internal _player = address(bytes20(keccak256("player")));
41
- address internal _reserveBeneficiary = address(bytes20(keccak256("reserveBeneficiary")));
42
-
43
- function setUp() public virtual override {
44
- super.setUp();
45
-
46
- JBAccountingContext[] memory tokens = new JBAccountingContext[](1);
47
- tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
48
-
49
- JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
50
- terminalConfigs[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokens});
51
-
52
- JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
53
- rulesetConfigs[0] = JBRulesetConfig({
54
- mustStartAtOrAfter: 0,
55
- duration: 10 days,
56
- weight: 1e18,
57
- weightCutPercent: 0,
58
- approvalHook: IJBRulesetApprovalHook(address(0)),
59
- metadata: JBRulesetMetadata({
60
- reservedPercent: 0,
61
- cashOutTaxRate: 0,
62
- baseCurrency: JBCurrencyIds.ETH,
63
- pausePay: false,
64
- pauseCreditTransfers: false,
65
- allowOwnerMinting: false,
66
- allowSetCustomToken: false,
67
- allowTerminalMigration: false,
68
- allowSetTerminals: false,
69
- allowSetController: false,
70
- allowAddAccountingContext: false,
71
- allowAddPriceFeed: false,
72
- ownerMustSendPayouts: false,
73
- holdFees: false,
74
- useTotalSurplusForCashOuts: false,
75
- useDataHookForPay: true,
76
- useDataHookForCashOut: true,
77
- dataHook: address(0),
78
- metadata: 0
79
- }),
80
- splitGroups: new JBSplitGroup[](0),
81
- fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
82
- });
83
-
84
- _protocolFeeProjectId =
85
- jbController().launchProjectFor(address(_projectOwner), "", rulesetConfigs, terminalConfigs, "");
86
- vm.prank(_projectOwner);
87
- address nanaToken =
88
- address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
89
-
90
- _defifaProjectId =
91
- jbController().launchProjectFor(address(_projectOwner), "", rulesetConfigs, terminalConfigs, "");
92
- vm.prank(_projectOwner);
93
- address defifaToken = address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
94
-
95
- _hookImpl = new DefifaHook(jbDirectory(), IERC20(defifaToken), IERC20(nanaToken));
96
- _governorImpl = new DefifaGovernor(jbController(), address(this));
97
- _deployer = new DefifaDeployer(
98
- address(_hookImpl),
99
- new DefifaTokenUriResolver(ITypeface(address(0))),
100
- _governorImpl,
101
- jbController(),
102
- new JBAddressRegistry(),
103
- _defifaProjectId,
104
- _protocolFeeProjectId
105
- );
106
-
107
- _hookImpl.transferOwnership(address(_deployer));
108
- _governorImpl.transferOwnership(address(_deployer));
109
- }
110
-
111
- function test_noContestReserveMintExcludedFromRefund() external {
112
- DefifaLaunchProjectData memory data = _launchData();
113
- uint256 projectId = _deployer.launchGameWith(data);
114
-
115
- vm.warp(data.start - data.mintPeriodDuration - data.refundPeriodDuration);
116
- vm.deal(_player, 1 ether);
117
-
118
- uint16[] memory tierIds = new uint16[](1);
119
- tierIds[0] = 1;
120
- vm.prank(_player);
121
- jbMultiTerminal().pay{value: 1 ether}(
122
- projectId,
123
- JBConstants.NATIVE_TOKEN,
124
- 1 ether,
125
- _player,
126
- 0,
127
- "",
128
- _buildPayMetadata(abi.encode(_player, tierIds))
129
- );
130
-
131
- vm.warp(data.start + 1);
132
- (, JBRulesetMetadata memory metadata) = jbController().currentRulesetOf(projectId);
133
- DefifaHook hook = DefifaHook(metadata.dataHook);
134
- assertEq(uint256(_deployer.currentGamePhaseOf(projectId)), uint256(DefifaGamePhase.NO_CONTEST));
135
-
136
- JB721TiersMintReservesConfig[] memory reserveConfigs = new JB721TiersMintReservesConfig[](1);
137
- reserveConfigs[0] = JB721TiersMintReservesConfig({tierId: 1, count: 1});
138
- hook.mintReservesFor(reserveConfigs);
139
-
140
- assertEq(hook.balanceOf(_reserveBeneficiary), 1, "reserve beneficiary received a free NFT");
141
- assertTrue(hook.isReserveMint(_generateTokenId(1, 2)), "token flagged as reserve mint");
142
-
143
- _deployer.triggerNoContestFor(projectId);
144
-
145
- // Build metadata for the reserve token cashout before calling expectRevert.
146
- uint256 reserveTokenId = _generateTokenId(1, 2);
147
- uint256[] memory reserveTokenIds = new uint256[](1);
148
- reserveTokenIds[0] = reserveTokenId;
149
- bytes memory reserveCashOutMetadata = _buildCashOutMetadata(reserveTokenIds);
150
-
151
- // The reserve beneficiary's cashout reverts — reserve-minted tokens are excluded from refund calculations.
152
- vm.prank(_reserveBeneficiary);
153
- vm.expectRevert();
154
- jbMultiTerminal()
155
- .cashOutTokensOf(
156
- _reserveBeneficiary,
157
- projectId,
158
- 0,
159
- JBConstants.NATIVE_TOKEN,
160
- 0,
161
- payable(_reserveBeneficiary),
162
- reserveCashOutMetadata
163
- );
164
-
165
- // The paid player can still get their full refund.
166
- uint256 playerTokenId = _generateTokenId(1, 1);
167
- uint256 balanceBefore = _player.balance;
168
- _cashOut(projectId, _player, playerTokenId);
169
-
170
- // Player gets full refund (1 ether minus fee).
171
- assertTrue(_player.balance > balanceBefore, "player received refund");
172
- assertEq(hook.balanceOf(_player), 0, "player NFT burned");
173
- }
174
-
175
- function _cashOut(uint256 projectId, address holder, uint256 tokenId) internal {
176
- uint256[] memory tokenIds = new uint256[](1);
177
- tokenIds[0] = tokenId;
178
- bytes memory cashOutMetadata = _buildCashOutMetadata(tokenIds);
179
-
180
- vm.prank(holder);
181
- jbMultiTerminal()
182
- .cashOutTokensOf(holder, projectId, 0, JBConstants.NATIVE_TOKEN, 0, payable(holder), cashOutMetadata);
183
- }
184
-
185
- function _launchData() internal returns (DefifaLaunchProjectData memory) {
186
- DefifaTierParams[] memory tierParams = new DefifaTierParams[](1);
187
- tierParams[0] = DefifaTierParams({
188
- reservedRate: 1,
189
- reservedTokenBeneficiary: _reserveBeneficiary,
190
- encodedIPFSUri: bytes32(0),
191
- shouldUseReservedTokenBeneficiaryAsDefault: false,
192
- name: "SOLE"
193
- });
194
-
195
- return DefifaLaunchProjectData({
196
- name: "DEFIFA",
197
- projectUri: "",
198
- contractUri: "",
199
- baseUri: "",
200
- tiers: tierParams,
201
- tierPrice: 1 ether,
202
- token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
203
- mintPeriodDuration: 1 days,
204
- refundPeriodDuration: 1 days,
205
- start: uint48(block.timestamp + 3 days),
206
- splits: new JBSplit[](0),
207
- attestationStartTime: 0,
208
- attestationGracePeriod: 1 days,
209
- defaultAttestationDelegate: address(0),
210
- defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
211
- terminal: jbMultiTerminal(),
212
- store: new JB721TiersHookStore(),
213
- minParticipation: 2 ether,
214
- scorecardTimeout: 0,
215
- timelockDuration: 0
216
- });
217
- }
218
-
219
- function _buildPayMetadata(bytes memory decodedData) internal view returns (bytes memory) {
220
- bytes4[] memory ids = new bytes4[](1);
221
- ids[0] = metadataHelper().getId("pay", address(_hookImpl));
222
- bytes[] memory datas = new bytes[](1);
223
- datas[0] = decodedData;
224
- return metadataHelper().createMetadata(ids, datas);
225
- }
226
-
227
- function _buildCashOutMetadata(uint256[] memory tokenIds) internal view returns (bytes memory) {
228
- bytes4[] memory ids = new bytes4[](1);
229
- ids[0] = metadataHelper().getId("cashOut", address(_hookImpl));
230
- bytes[] memory datas = new bytes[](1);
231
- datas[0] = abi.encode(tokenIds);
232
- return metadataHelper().createMetadata(ids, datas);
233
- }
234
-
235
- function _generateTokenId(uint256 tierId, uint256 tokenNumber) internal pure returns (uint256) {
236
- return (tierId * 1_000_000_000) + tokenNumber;
237
- }
238
- }