@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.
- package/AUDIT_INSTRUCTIONS.md +6 -2
- package/README.md +11 -2
- package/RISKS.md +3 -1
- package/STYLE_GUIDE.md +14 -11
- package/package.json +31 -14
- package/script/Deploy.s.sol +4 -1
- package/src/DefifaDeployer.sol +74 -46
- package/src/DefifaGovernor.sol +53 -11
- package/src/DefifaHook.sol +79 -25
- package/src/DefifaTokenUriResolver.sol +111 -19
- package/src/interfaces/IDefifaDeployer.sol +5 -0
- package/src/interfaces/IDefifaGovernor.sol +4 -0
- package/src/interfaces/IDefifaHook.sol +5 -0
- package/src/libraries/DefifaHookLib.sol +9 -10
- package/src/structs/DefifaLaunchProjectData.sol +0 -3
- package/CRYPTO_ECON.pdf +0 -0
- package/CRYPTO_ECON.tex +0 -997
- package/foundry.lock +0 -17
- package/references/operations.md +0 -32
- package/references/runtime.md +0 -43
- package/slither-ci.config.json +0 -10
- package/sphinx.lock +0 -521
- package/test/BWAFunctionComparison.t.sol +0 -1320
- package/test/DefifaAdversarialQuorum.t.sol +0 -617
- package/test/DefifaAuditLowGuards.t.sol +0 -308
- package/test/DefifaFeeAccounting.t.sol +0 -581
- package/test/DefifaGovernanceHardening.t.sol +0 -1315
- package/test/DefifaGovernor.t.sol +0 -1378
- package/test/DefifaHookRegressions.t.sol +0 -415
- package/test/DefifaMintCostInvariant.t.sol +0 -319
- package/test/DefifaNoContest.t.sol +0 -941
- package/test/DefifaSecurity.t.sol +0 -741
- package/test/DefifaUSDC.t.sol +0 -480
- package/test/Fork.t.sol +0 -2388
- package/test/TestAuditGaps.sol +0 -984
- package/test/TestQALastMile.t.sol +0 -514
- package/test/audit/AttestationDoubleCount.t.sol +0 -218
- package/test/audit/CodexNemesisCurrencyMismatchBypass.t.sol +0 -112
- package/test/audit/CodexNemesisNoContestReserveDrain.t.sol +0 -238
- package/test/audit/CodexRegistryMismatch.t.sol +0 -191
- package/test/audit/CodexTierCapMismatch.t.sol +0 -171
- package/test/audit/CurrencyMismatchFix.t.sol +0 -265
- package/test/audit/FixPendingReserveDilution.t.sol +0 -366
- package/test/audit/H5TierCapValidation.t.sol +0 -184
- package/test/audit/PendingReserveDilution.t.sol +0 -298
- package/test/audit/PendingReserveQuorumGrief.t.sol +0 -355
- package/test/audit/PendingReserveSnapshotBypass.t.sol +0 -319
- package/test/regression/AttestationDelegateBeneficiary.t.sol +0 -271
- package/test/regression/FulfillmentBlocksRatification.t.sol +0 -279
- package/test/regression/GracePeriodBypass.t.sol +0 -302
|
@@ -1,741 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import {DefifaGovernor} from "../src/DefifaGovernor.sol";
|
|
5
|
-
import {DefifaDeployer} from "../src/DefifaDeployer.sol";
|
|
6
|
-
import {DefifaHook} from "../src/DefifaHook.sol";
|
|
7
|
-
import {DefifaTokenUriResolver} from "../src/DefifaTokenUriResolver.sol";
|
|
8
|
-
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
9
|
-
|
|
10
|
-
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
11
|
-
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
12
|
-
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
13
|
-
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
14
|
-
|
|
15
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
16
|
-
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
17
|
-
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
18
|
-
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
19
|
-
import {JB721TiersMintReservesConfig} from "@bananapus/721-hook-v6/src/structs/JB721TiersMintReservesConfig.sol";
|
|
20
|
-
import {DefifaDelegation} from "../src/structs/DefifaDelegation.sol";
|
|
21
|
-
import {DefifaLaunchProjectData} from "../src/structs/DefifaLaunchProjectData.sol";
|
|
22
|
-
import {DefifaTierParams} from "../src/structs/DefifaTierParams.sol";
|
|
23
|
-
import {DefifaTierCashOutWeight} from "../src/structs/DefifaTierCashOutWeight.sol";
|
|
24
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
25
|
-
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
26
|
-
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
27
|
-
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
28
|
-
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
29
|
-
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
30
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
31
|
-
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.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
|
-
import {JBMultiTerminal} from "@bananapus/core-v6/src/JBMultiTerminal.sol";
|
|
36
|
-
|
|
37
|
-
/// @dev Helper to read block.timestamp via an external call, bypassing the via-ir optimizer's timestamp caching.
|
|
38
|
-
contract TimestampReader {
|
|
39
|
-
function timestamp() external view returns (uint256) {
|
|
40
|
-
return block.timestamp;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/// @title DefifaSecurityTest
|
|
45
|
-
/// @notice High-volume game integrity, fund conservation, scorecard validation, and governance tests.
|
|
46
|
-
contract DefifaSecurityTest is JBTest, TestBaseWorkflow {
|
|
47
|
-
using JBRulesetMetadataResolver for JBRuleset;
|
|
48
|
-
|
|
49
|
-
TimestampReader private _tsReader = new TimestampReader();
|
|
50
|
-
|
|
51
|
-
address _protocolFeeProjectTokenAccount;
|
|
52
|
-
address _defifaProjectTokenAccount;
|
|
53
|
-
uint256 _protocolFeeProjectId;
|
|
54
|
-
uint256 _defifaProjectId;
|
|
55
|
-
uint256 _gameId = 3;
|
|
56
|
-
|
|
57
|
-
DefifaDeployer deployer;
|
|
58
|
-
DefifaHook hook;
|
|
59
|
-
DefifaGovernor governor;
|
|
60
|
-
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
61
|
-
|
|
62
|
-
// Shared test state (set by _setupGame helpers)
|
|
63
|
-
uint256 _pid;
|
|
64
|
-
DefifaHook _nft;
|
|
65
|
-
DefifaGovernor _gov;
|
|
66
|
-
address[] _users;
|
|
67
|
-
|
|
68
|
-
function setUp() public virtual override {
|
|
69
|
-
super.setUp();
|
|
70
|
-
|
|
71
|
-
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
72
|
-
_tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
73
|
-
JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
|
|
74
|
-
tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
|
|
75
|
-
JBRulesetConfig[] memory rc = new JBRulesetConfig[](1);
|
|
76
|
-
rc[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 = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
|
|
108
|
-
vm.prank(projectOwner);
|
|
109
|
-
_protocolFeeProjectTokenAccount =
|
|
110
|
-
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
111
|
-
_defifaProjectId = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
|
|
112
|
-
vm.prank(projectOwner);
|
|
113
|
-
_defifaProjectTokenAccount =
|
|
114
|
-
address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
115
|
-
|
|
116
|
-
hook =
|
|
117
|
-
new DefifaHook(jbDirectory(), IERC20(_defifaProjectTokenAccount), IERC20(_protocolFeeProjectTokenAccount));
|
|
118
|
-
governor = new DefifaGovernor(jbController(), address(this));
|
|
119
|
-
deployer = new DefifaDeployer(
|
|
120
|
-
address(hook),
|
|
121
|
-
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
122
|
-
governor,
|
|
123
|
-
jbController(),
|
|
124
|
-
new JBAddressRegistry(),
|
|
125
|
-
_protocolFeeProjectId,
|
|
126
|
-
_defifaProjectId
|
|
127
|
-
);
|
|
128
|
-
hook.transferOwnership(address(deployer));
|
|
129
|
-
governor.transferOwnership(address(deployer));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// =========================================================================
|
|
133
|
-
// HIGH-VOLUME: 32 tiers at 100 ETH each = 3,200 ETH pot
|
|
134
|
-
// =========================================================================
|
|
135
|
-
function testHighVolume_32tiers() external {
|
|
136
|
-
_setupGame(32, 100 ether);
|
|
137
|
-
_toScoring();
|
|
138
|
-
|
|
139
|
-
// Tier 1 gets 50%, rest split 50% — must sum to exactly TOTAL_CASHOUT_WEIGHT
|
|
140
|
-
uint256 tw = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
141
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(32);
|
|
142
|
-
uint256 half = tw / 2;
|
|
143
|
-
uint256 perTier = half / 31;
|
|
144
|
-
uint256 assigned;
|
|
145
|
-
for (uint256 i; i < 32; i++) {
|
|
146
|
-
if (i == 0) {
|
|
147
|
-
sc[i].cashOutWeight = half;
|
|
148
|
-
} else if (i == 31) {
|
|
149
|
-
// Last tier absorbs rounding remainder
|
|
150
|
-
sc[i].cashOutWeight = tw - assigned;
|
|
151
|
-
} else {
|
|
152
|
-
sc[i].cashOutWeight = perTier;
|
|
153
|
-
}
|
|
154
|
-
assigned += sc[i].cashOutWeight;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
_attestAndRatify(sc);
|
|
158
|
-
uint256 pot = _surplus();
|
|
159
|
-
uint256 out = _cashOutAllUsers();
|
|
160
|
-
|
|
161
|
-
assertApproxEqAbs(out, pot, 1e15, "cashed out vs pot");
|
|
162
|
-
assertLe(_surplus(), 1e15, "remaining dust");
|
|
163
|
-
// No fee tokens left in hook
|
|
164
|
-
assertEq(IERC20(_protocolFeeProjectTokenAccount).balanceOf(address(_nft)), 0, "no NANA left");
|
|
165
|
-
assertEq(IERC20(_defifaProjectTokenAccount).balanceOf(address(_nft)), 0, "no DEFIFA left");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// =========================================================================
|
|
169
|
-
// MULTI-PLAYER: 5 users in winning tier, 1 each in losing tiers
|
|
170
|
-
// =========================================================================
|
|
171
|
-
function testMultiPlayer_winnerTakesAll() external {
|
|
172
|
-
_setupMultiPlayer();
|
|
173
|
-
_toScoring();
|
|
174
|
-
|
|
175
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(4);
|
|
176
|
-
sc[0].cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
177
|
-
// tiers 2-4 get 0
|
|
178
|
-
|
|
179
|
-
_attestAndRatify(sc);
|
|
180
|
-
|
|
181
|
-
// All winners should get approximately equal shares
|
|
182
|
-
uint256[] memory winnerPayouts = new uint256[](5);
|
|
183
|
-
for (uint256 i; i < 5; i++) {
|
|
184
|
-
uint256 bb = _users[i].balance;
|
|
185
|
-
_cashOut(_users[i], 1, i + 1);
|
|
186
|
-
winnerPayouts[i] = _users[i].balance - bb;
|
|
187
|
-
assertGt(winnerPayouts[i], 0, "winner should receive ETH");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// All winners should get approximately equal amounts (within 0.1%)
|
|
191
|
-
for (uint256 i = 1; i < 5; i++) {
|
|
192
|
-
assertApproxEqRel(winnerPayouts[i], winnerPayouts[0], 0.001 ether, "winner payouts should be equal");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Losers get 0 ETH (but still receive fee tokens, so no NOTHING_TO_CLAIM revert)
|
|
196
|
-
for (uint256 i; i < 3; i++) {
|
|
197
|
-
uint256 bb = _users[5 + i].balance;
|
|
198
|
-
_cashOut(_users[5 + i], i + 2, 1);
|
|
199
|
-
assertEq(_users[5 + i].balance, bb, "loser should receive 0 ETH");
|
|
200
|
-
assertEq(_nft.balanceOf(_users[5 + i]), 0, "loser NFT burned");
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// =========================================================================
|
|
205
|
-
// REFUND: exact price returned during MINT phase
|
|
206
|
-
// =========================================================================
|
|
207
|
-
function testRefundIntegrity() external {
|
|
208
|
-
_setupGame(8, 50 ether);
|
|
209
|
-
|
|
210
|
-
// Refund first 4 during MINT phase
|
|
211
|
-
for (uint256 i; i < 4; i++) {
|
|
212
|
-
uint256 bb = _users[i].balance;
|
|
213
|
-
_refund(_users[i], i + 1);
|
|
214
|
-
assertEq(_users[i].balance - bb, 50 ether, "exact refund");
|
|
215
|
-
assertEq(_nft.balanceOf(_users[i]), 0, "NFT burned");
|
|
216
|
-
}
|
|
217
|
-
assertEq(_surplus(), 50 ether * 4, "pot = remaining mints");
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// =========================================================================
|
|
221
|
-
// ROUNDING: extreme weights at 1000 ETH per tier
|
|
222
|
-
// With BWA + HHI, highly concentrated scorecards on fewer than 4 tiers
|
|
223
|
-
// cannot reach quorum (total BWA = MAX*(n-1) < adjusted quorum for HHI~1).
|
|
224
|
-
// Using 5 tiers ensures total BWA (4*MAX) exceeds the adjusted quorum.
|
|
225
|
-
// =========================================================================
|
|
226
|
-
function testRounding_extremeWeights() external {
|
|
227
|
-
_setupGame(5, 1000 ether);
|
|
228
|
-
_toScoring();
|
|
229
|
-
|
|
230
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(5);
|
|
231
|
-
sc[0].cashOutWeight = 1;
|
|
232
|
-
sc[1].cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT() - 4;
|
|
233
|
-
sc[2].cashOutWeight = 1;
|
|
234
|
-
sc[3].cashOutWeight = 1;
|
|
235
|
-
sc[4].cashOutWeight = 1;
|
|
236
|
-
|
|
237
|
-
_attestAndRatify(sc);
|
|
238
|
-
uint256 pot = _surplus();
|
|
239
|
-
uint256 out = _cashOutAllUsers();
|
|
240
|
-
assertApproxEqAbs(out + _surplus(), pot, 5, "fund conservation");
|
|
241
|
-
assertGt(_users[1].balance, pot * 99 / 100, "tier 2 > 99%");
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// =========================================================================
|
|
245
|
-
// C-D2: overweight scorecard rejected
|
|
246
|
-
// =========================================================================
|
|
247
|
-
function testC_D2_rejectsOverweight() external {
|
|
248
|
-
_setupGame(4, 1 ether);
|
|
249
|
-
_toScoring();
|
|
250
|
-
|
|
251
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(4);
|
|
252
|
-
for (uint256 i; i < 4; i++) {
|
|
253
|
-
sc[i].cashOutWeight = (_nft.TOTAL_CASHOUT_WEIGHT() * 30) / 100; // 120% total
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
vm.expectRevert(DefifaHook.DefifaHook_InvalidCashoutWeights.selector);
|
|
257
|
-
_gov.submitScorecardFor(_gameId, sc);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// =========================================================================
|
|
261
|
-
// M-D6: delegation blocked after MINT phase
|
|
262
|
-
// =========================================================================
|
|
263
|
-
function testM_D6_delegationBlocked() external {
|
|
264
|
-
_setupGame(4, 1 ether);
|
|
265
|
-
|
|
266
|
-
// REFUND phase
|
|
267
|
-
vm.warp(block.timestamp + 1 days);
|
|
268
|
-
vm.prank(_users[0]);
|
|
269
|
-
vm.expectRevert(abi.encodeWithSignature("DefifaHook_DelegateChangesUnavailableInThisPhase()"));
|
|
270
|
-
_nft.setTierDelegateTo(address(1), 1);
|
|
271
|
-
|
|
272
|
-
// SCORING phase
|
|
273
|
-
vm.warp(block.timestamp + 2 days);
|
|
274
|
-
vm.prank(_users[0]);
|
|
275
|
-
vm.expectRevert(abi.encodeWithSignature("DefifaHook_DelegateChangesUnavailableInThisPhase()"));
|
|
276
|
-
_nft.setTierDelegateTo(address(1), 1);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// =========================================================================
|
|
280
|
-
// QUORUM: 50% of minted tiers
|
|
281
|
-
// =========================================================================
|
|
282
|
-
function testQuorum_50pctMintedTiers() external {
|
|
283
|
-
// Launch game with 10 tiers but only mint 6
|
|
284
|
-
_setupPartial(10, 6, 1 ether);
|
|
285
|
-
uint256 expected = (6 * _gov.MAX_ATTESTATION_POWER_TIER()) / 2;
|
|
286
|
-
assertEq(_gov.quorum(_gameId), expected, "quorum = 50% of minted tiers");
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// =========================================================================
|
|
290
|
-
// FUZZ: fund conservation across varying tier/player counts
|
|
291
|
-
// =========================================================================
|
|
292
|
-
function testFuzz_fundConservation(uint8 rawTiers, uint8 rawPlayers) external {
|
|
293
|
-
// Minimum 3 tiers: with BWA + HHI-adjusted quorum, 2-tier games with equal scorecards
|
|
294
|
-
// can never reach quorum (total BWA = MAX*(n-1) < adjusted quorum when n < 3).
|
|
295
|
-
uint8 nTiers = uint8(bound(rawTiers, 3, 12));
|
|
296
|
-
uint8 nPpt = uint8(bound(rawPlayers, 1, 3));
|
|
297
|
-
|
|
298
|
-
_setupMultiN(nTiers, nPpt, 1 ether);
|
|
299
|
-
_toScoring();
|
|
300
|
-
|
|
301
|
-
uint256 tw = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
302
|
-
uint256 wpt = tw / nTiers;
|
|
303
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(nTiers);
|
|
304
|
-
uint256 assigned;
|
|
305
|
-
for (uint256 i; i < nTiers; i++) {
|
|
306
|
-
if (i == nTiers - 1) {
|
|
307
|
-
// Last tier absorbs rounding remainder to satisfy exact weight requirement
|
|
308
|
-
sc[i].cashOutWeight = tw - assigned;
|
|
309
|
-
} else {
|
|
310
|
-
sc[i].cashOutWeight = wpt;
|
|
311
|
-
}
|
|
312
|
-
assigned += sc[i].cashOutWeight;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
_attestAndRatify(sc);
|
|
316
|
-
uint256 pot = _surplus();
|
|
317
|
-
|
|
318
|
-
uint256 total;
|
|
319
|
-
for (uint256 i; i < _users.length; i++) {
|
|
320
|
-
uint256 bb = _users[i].balance;
|
|
321
|
-
uint256 tid = (i / nPpt) + 1;
|
|
322
|
-
uint256 tnum = (i % nPpt) + 1;
|
|
323
|
-
_cashOut(_users[i], tid, tnum);
|
|
324
|
-
total += _users[i].balance - bb;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
assertApproxEqAbs(total + _surplus(), pot, _users.length, "fund conservation");
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// =========================================================================
|
|
331
|
-
// SCORECARD: valid equal-weight scorecard passes
|
|
332
|
-
// =========================================================================
|
|
333
|
-
function testScorecard_equalWeight_passes() external {
|
|
334
|
-
_setupGame(4, 1 ether);
|
|
335
|
-
_toScoring();
|
|
336
|
-
|
|
337
|
-
uint256 tw = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
338
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(4);
|
|
339
|
-
for (uint256 i; i < 4; i++) {
|
|
340
|
-
sc[i].cashOutWeight = tw / 4;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Should succeed without reverting
|
|
344
|
-
_attestAndRatify(sc);
|
|
345
|
-
assertTrue(_nft.cashOutWeightIsSet(), "weights should be set");
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// =========================================================================
|
|
349
|
-
// C-D3: reserved minters get proportional fee tokens ($DEFIFA/$NANA)
|
|
350
|
-
// With BWA + HHI, 2-tier games cannot reach quorum (total BWA power for
|
|
351
|
-
// n tiers = MAX*(n-1) which is always less than HHI-adjusted quorum for n=2).
|
|
352
|
-
// We use 4 tiers with equal weight, all having reserveRate=1. This ensures
|
|
353
|
-
// enough attestation power from all participants to meet the adjusted quorum.
|
|
354
|
-
// =========================================================================
|
|
355
|
-
function testC_D3_reservedMintersGetFeeTokens() external {
|
|
356
|
-
// Setup: 4 tiers, reservedRate=1, reserveBeneficiary = _reserveAddr
|
|
357
|
-
address _reserveAddr = address(bytes20(keccak256("reserveBeneficiary")));
|
|
358
|
-
|
|
359
|
-
DefifaTierParams[] memory tp = new DefifaTierParams[](4);
|
|
360
|
-
for (uint256 i; i < 4; i++) {
|
|
361
|
-
tp[i] = DefifaTierParams({
|
|
362
|
-
reservedRate: 1, // 1 reserve per 1 paid mint
|
|
363
|
-
reservedTokenBeneficiary: _reserveAddr,
|
|
364
|
-
encodedIPFSUri: bytes32(0),
|
|
365
|
-
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
366
|
-
name: "DEFIFA"
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
DefifaLaunchProjectData memory d = DefifaLaunchProjectData({
|
|
370
|
-
name: "DEFIFA",
|
|
371
|
-
projectUri: "",
|
|
372
|
-
contractUri: "",
|
|
373
|
-
baseUri: "",
|
|
374
|
-
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
375
|
-
mintPeriodDuration: 1 days,
|
|
376
|
-
start: uint48(block.timestamp + 3 days),
|
|
377
|
-
refundPeriodDuration: 1 days,
|
|
378
|
-
store: new JB721TiersHookStore(),
|
|
379
|
-
splits: new JBSplit[](0),
|
|
380
|
-
attestationStartTime: 0,
|
|
381
|
-
attestationGracePeriod: 100_381,
|
|
382
|
-
defaultAttestationDelegate: address(0),
|
|
383
|
-
tierPrice: uint104(1 ether),
|
|
384
|
-
tiers: tp,
|
|
385
|
-
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
386
|
-
terminal: jbMultiTerminal(),
|
|
387
|
-
minParticipation: 0,
|
|
388
|
-
scorecardTimeout: 0,
|
|
389
|
-
timelockDuration: 0
|
|
390
|
-
});
|
|
391
|
-
(_pid, _nft, _gov) = _launch(d);
|
|
392
|
-
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
393
|
-
|
|
394
|
-
// Paid mints: 1 user per tier
|
|
395
|
-
_users = new address[](4);
|
|
396
|
-
for (uint256 i; i < 4; i++) {
|
|
397
|
-
_users[i] = _addr(i);
|
|
398
|
-
_mint(_users[i], i + 1, 1 ether);
|
|
399
|
-
_delegateSelf(_users[i], i + 1);
|
|
400
|
-
vm.warp(_tsReader.timestamp() + 1);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Move to scoring phase (reserves can only be minted here)
|
|
404
|
-
_toScoring();
|
|
405
|
-
|
|
406
|
-
// Mint reserved tokens (1 per tier since reserveFrequency=1)
|
|
407
|
-
JB721TiersMintReservesConfig[] memory reserveConfigs = new JB721TiersMintReservesConfig[](4);
|
|
408
|
-
reserveConfigs[0] = JB721TiersMintReservesConfig({tierId: 1, count: 1});
|
|
409
|
-
reserveConfigs[1] = JB721TiersMintReservesConfig({tierId: 2, count: 1});
|
|
410
|
-
reserveConfigs[2] = JB721TiersMintReservesConfig({tierId: 3, count: 1});
|
|
411
|
-
reserveConfigs[3] = JB721TiersMintReservesConfig({tierId: 4, count: 1});
|
|
412
|
-
_nft.mintReservesFor(reserveConfigs);
|
|
413
|
-
|
|
414
|
-
// Reserve beneficiary should hold 4 NFTs (mintReservesFor auto-delegates to self)
|
|
415
|
-
assertEq(_nft.balanceOf(_reserveAddr), 4, "reserve beneficiary holds 4 NFTs");
|
|
416
|
-
|
|
417
|
-
// Seed fee tokens into the hook (simulating protocol fee distribution)
|
|
418
|
-
uint256 defifaAmount = 1000 ether;
|
|
419
|
-
uint256 nanaAmount = 500 ether;
|
|
420
|
-
deal(address(IERC20(_defifaProjectTokenAccount)), address(_nft), defifaAmount);
|
|
421
|
-
deal(address(IERC20(_protocolFeeProjectTokenAccount)), address(_nft), nanaAmount);
|
|
422
|
-
|
|
423
|
-
// Scorecard: equal weight across all 4 tiers
|
|
424
|
-
uint256 tw = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
425
|
-
DefifaTierCashOutWeight[] memory sc = _buildScorecard(4);
|
|
426
|
-
sc[0].cashOutWeight = tw / 4;
|
|
427
|
-
sc[1].cashOutWeight = tw / 4;
|
|
428
|
-
sc[2].cashOutWeight = tw / 4;
|
|
429
|
-
sc[3].cashOutWeight = tw / 4;
|
|
430
|
-
|
|
431
|
-
// Only paid minters attest -- reserve beneficiary has zero attestation weight at the
|
|
432
|
-
// snapshot (attestationsBegin - 1) because they received NFTs via reserve minting after
|
|
433
|
-
// the snapshot timestamp.
|
|
434
|
-
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
435
|
-
vm.warp(_tsReader.timestamp() + _gov.attestationStartTimeOf(_gameId) + 1);
|
|
436
|
-
for (uint256 i; i < _users.length; i++) {
|
|
437
|
-
vm.prank(_users[i]);
|
|
438
|
-
_gov.attestToScorecardFrom(_gameId, pid);
|
|
439
|
-
}
|
|
440
|
-
vm.warp(_tsReader.timestamp() + _gov.attestationGracePeriodOf(_gameId) + 1);
|
|
441
|
-
_gov.ratifyScorecardFrom(_gameId, sc);
|
|
442
|
-
vm.warp(_tsReader.timestamp() + 1);
|
|
443
|
-
|
|
444
|
-
// Cash out paid minters from tiers 1 and 2
|
|
445
|
-
_cashOut(_users[0], 1, 1);
|
|
446
|
-
_cashOut(_users[1], 2, 1);
|
|
447
|
-
|
|
448
|
-
// Check that paid minters got fee tokens
|
|
449
|
-
uint256 user0Defifa = IERC20(_defifaProjectTokenAccount).balanceOf(_users[0]);
|
|
450
|
-
uint256 user0Nana = IERC20(_protocolFeeProjectTokenAccount).balanceOf(_users[0]);
|
|
451
|
-
assertGt(user0Defifa, 0, "paid minter got DEFIFA tokens");
|
|
452
|
-
assertGt(user0Nana, 0, "paid minter got NANA tokens");
|
|
453
|
-
|
|
454
|
-
// Cash out reserved minter's tokens from tiers 1 and 2 (token #2 in each tier)
|
|
455
|
-
bytes memory meta1 = _cashOutMeta(1, 2);
|
|
456
|
-
vm.prank(_reserveAddr);
|
|
457
|
-
JBMultiTerminal(address(jbMultiTerminal()))
|
|
458
|
-
.cashOutTokensOf({
|
|
459
|
-
holder: _reserveAddr,
|
|
460
|
-
projectId: _pid,
|
|
461
|
-
cashOutCount: 0,
|
|
462
|
-
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
463
|
-
minTokensReclaimed: 0,
|
|
464
|
-
beneficiary: payable(_reserveAddr),
|
|
465
|
-
metadata: meta1
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
bytes memory meta2 = _cashOutMeta(2, 2);
|
|
469
|
-
vm.prank(_reserveAddr);
|
|
470
|
-
JBMultiTerminal(address(jbMultiTerminal()))
|
|
471
|
-
.cashOutTokensOf({
|
|
472
|
-
holder: _reserveAddr,
|
|
473
|
-
projectId: _pid,
|
|
474
|
-
cashOutCount: 0,
|
|
475
|
-
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
476
|
-
minTokensReclaimed: 0,
|
|
477
|
-
beneficiary: payable(_reserveAddr),
|
|
478
|
-
metadata: meta2
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
// Reserved minter should have gotten fee tokens from tiers 1+2 cash-outs
|
|
482
|
-
uint256 reserveDefifa = IERC20(_defifaProjectTokenAccount).balanceOf(_reserveAddr);
|
|
483
|
-
uint256 reserveNana = IERC20(_protocolFeeProjectTokenAccount).balanceOf(_reserveAddr);
|
|
484
|
-
assertGt(reserveDefifa, 0, "reserved minter got DEFIFA tokens");
|
|
485
|
-
assertGt(reserveNana, 0, "reserved minter got NANA tokens");
|
|
486
|
-
|
|
487
|
-
// Each tier has 2 tokens (1 paid + 1 reserve), all at 1 ether.
|
|
488
|
-
// Paid minter (1 token in 1 tier) vs reserved minter (2 tokens in 2 tiers).
|
|
489
|
-
// Reserved minter gets 2x fee tokens relative to paid minter.
|
|
490
|
-
assertApproxEqAbs(user0Defifa, reserveDefifa / 2, 1, "reserved gets 2x (2 tokens) vs paid (1 token)");
|
|
491
|
-
assertApproxEqAbs(user0Nana, reserveNana / 2, 1, "NANA distribution matches");
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// =========================================================================
|
|
495
|
-
// GAME LIFECYCLE: cash-out before scorecard gives 0 ETH (weights = 0)
|
|
496
|
-
// =========================================================================
|
|
497
|
-
function testNoCashOut_beforeScorecard() external {
|
|
498
|
-
_setupGame(4, 1 ether);
|
|
499
|
-
_toScoring();
|
|
500
|
-
|
|
501
|
-
// Cash out during scoring before scorecard — weight=0 means NOTHING_TO_CLAIM revert
|
|
502
|
-
bytes memory meta = _cashOutMeta(1, 1);
|
|
503
|
-
vm.expectRevert(DefifaHook.DefifaHook_NothingToClaim.selector);
|
|
504
|
-
vm.prank(_users[0]);
|
|
505
|
-
JBMultiTerminal(address(jbMultiTerminal()))
|
|
506
|
-
.cashOutTokensOf({
|
|
507
|
-
holder: _users[0],
|
|
508
|
-
projectId: _pid,
|
|
509
|
-
cashOutCount: 0,
|
|
510
|
-
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
511
|
-
minTokensReclaimed: 0,
|
|
512
|
-
beneficiary: payable(_users[0]),
|
|
513
|
-
metadata: meta
|
|
514
|
-
});
|
|
515
|
-
// NFT should NOT have been burned (revert rolled it back)
|
|
516
|
-
assertEq(_nft.balanceOf(_users[0]), 1, "NFT not burned on revert");
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// =========================================================================
|
|
520
|
-
// SETUP HELPERS
|
|
521
|
-
// =========================================================================
|
|
522
|
-
|
|
523
|
-
function _setupGame(uint8 nTiers, uint256 tierPrice) internal {
|
|
524
|
-
DefifaLaunchProjectData memory d = _launchData(nTiers, tierPrice);
|
|
525
|
-
(_pid, _nft, _gov) = _launch(d);
|
|
526
|
-
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
527
|
-
_users = new address[](nTiers);
|
|
528
|
-
for (uint256 i; i < nTiers; i++) {
|
|
529
|
-
_users[i] = _addr(i);
|
|
530
|
-
_mint(_users[i], i + 1, tierPrice);
|
|
531
|
-
_delegateSelf(_users[i], i + 1);
|
|
532
|
-
vm.warp(block.timestamp + 1);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function _setupMultiPlayer() internal {
|
|
537
|
-
DefifaLaunchProjectData memory d = _launchData(4, 1 ether);
|
|
538
|
-
(_pid, _nft, _gov) = _launch(d);
|
|
539
|
-
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
540
|
-
_users = new address[](8);
|
|
541
|
-
for (uint256 i; i < 5; i++) {
|
|
542
|
-
_users[i] = _addr(100 + i);
|
|
543
|
-
_mint(_users[i], 1, 1 ether);
|
|
544
|
-
_delegateSelf(_users[i], 1);
|
|
545
|
-
vm.warp(block.timestamp + 1);
|
|
546
|
-
}
|
|
547
|
-
for (uint256 i; i < 3; i++) {
|
|
548
|
-
_users[5 + i] = _addr(200 + i);
|
|
549
|
-
_mint(_users[5 + i], i + 2, 1 ether);
|
|
550
|
-
_delegateSelf(_users[5 + i], i + 2);
|
|
551
|
-
vm.warp(block.timestamp + 1);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function _setupMultiN(uint8 nTiers, uint8 nPpt, uint256 tierPrice) internal {
|
|
556
|
-
DefifaLaunchProjectData memory d = _launchData(nTiers, tierPrice);
|
|
557
|
-
(_pid, _nft, _gov) = _launch(d);
|
|
558
|
-
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
559
|
-
uint256 total = uint256(nTiers) * uint256(nPpt);
|
|
560
|
-
_users = new address[](total);
|
|
561
|
-
uint256 idx;
|
|
562
|
-
for (uint256 t; t < nTiers; t++) {
|
|
563
|
-
for (uint256 p; p < nPpt; p++) {
|
|
564
|
-
_users[idx] = _addr(idx);
|
|
565
|
-
_mint(_users[idx], t + 1, tierPrice);
|
|
566
|
-
_delegateSelf(_users[idx], t + 1);
|
|
567
|
-
vm.warp(block.timestamp + 1);
|
|
568
|
-
idx++;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function _setupPartial(uint8 nTiers, uint256 nMint, uint256 tierPrice) internal {
|
|
574
|
-
DefifaLaunchProjectData memory d = _launchData(nTiers, tierPrice);
|
|
575
|
-
(_pid, _nft, _gov) = _launch(d);
|
|
576
|
-
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
577
|
-
_users = new address[](nMint);
|
|
578
|
-
for (uint256 i; i < nMint; i++) {
|
|
579
|
-
_users[i] = _addr(i);
|
|
580
|
-
_mint(_users[i], i + 1, tierPrice);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function _toScoring() internal {
|
|
585
|
-
// Warp 3 days forward (past mint + refund) into scoring
|
|
586
|
-
vm.warp(_tsReader.timestamp() + 3 days + 1);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// =========================================================================
|
|
590
|
-
// PRIMITIVE HELPERS
|
|
591
|
-
// =========================================================================
|
|
592
|
-
|
|
593
|
-
function _launchData(uint8 n, uint256 tierPrice) internal returns (DefifaLaunchProjectData memory) {
|
|
594
|
-
DefifaTierParams[] memory tp = new DefifaTierParams[](n);
|
|
595
|
-
for (uint256 i; i < n; i++) {
|
|
596
|
-
tp[i] = DefifaTierParams({
|
|
597
|
-
reservedRate: 1001,
|
|
598
|
-
reservedTokenBeneficiary: address(0),
|
|
599
|
-
encodedIPFSUri: bytes32(0),
|
|
600
|
-
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
601
|
-
name: "DEFIFA"
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
return DefifaLaunchProjectData({
|
|
605
|
-
name: "DEFIFA",
|
|
606
|
-
projectUri: "",
|
|
607
|
-
contractUri: "",
|
|
608
|
-
baseUri: "",
|
|
609
|
-
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
610
|
-
mintPeriodDuration: 1 days,
|
|
611
|
-
start: uint48(block.timestamp + 3 days),
|
|
612
|
-
refundPeriodDuration: 1 days,
|
|
613
|
-
store: new JB721TiersHookStore(),
|
|
614
|
-
splits: new JBSplit[](0),
|
|
615
|
-
attestationStartTime: 0,
|
|
616
|
-
attestationGracePeriod: 100_381,
|
|
617
|
-
defaultAttestationDelegate: address(0),
|
|
618
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
619
|
-
tierPrice: uint104(tierPrice),
|
|
620
|
-
tiers: tp,
|
|
621
|
-
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
622
|
-
terminal: jbMultiTerminal(),
|
|
623
|
-
minParticipation: 0,
|
|
624
|
-
scorecardTimeout: 0,
|
|
625
|
-
timelockDuration: 0
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function _launch(DefifaLaunchProjectData memory d) internal returns (uint256 p, DefifaHook n, DefifaGovernor g) {
|
|
630
|
-
g = governor;
|
|
631
|
-
p = deployer.launchGameWith(d);
|
|
632
|
-
JBRuleset memory fc = jbRulesets().currentOf(p);
|
|
633
|
-
if (fc.dataHook() == address(0)) (fc,) = jbRulesets().latestQueuedOf(p);
|
|
634
|
-
n = DefifaHook(fc.dataHook());
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function _addr(uint256 i) internal pure returns (address) {
|
|
638
|
-
return address(bytes20(keccak256(abi.encode("su", i))));
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function _mint(address user, uint256 tid, uint256 amt) internal {
|
|
642
|
-
vm.deal(user, amt);
|
|
643
|
-
uint16[] memory m = new uint16[](1);
|
|
644
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
645
|
-
m[0] = uint16(tid);
|
|
646
|
-
bytes[] memory data = new bytes[](1);
|
|
647
|
-
data[0] = abi.encode(user, m);
|
|
648
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
649
|
-
ids[0] = metadataHelper().getId("pay", address(hook));
|
|
650
|
-
vm.prank(user);
|
|
651
|
-
jbMultiTerminal().pay{value: amt}(
|
|
652
|
-
_pid, JBConstants.NATIVE_TOKEN, amt, user, 0, "", metadataHelper().createMetadata(ids, data)
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function _delegateSelf(address user, uint256 tid) internal {
|
|
657
|
-
DefifaDelegation[] memory dd = new DefifaDelegation[](1);
|
|
658
|
-
dd[0] = DefifaDelegation({delegatee: user, tierId: tid});
|
|
659
|
-
vm.prank(user);
|
|
660
|
-
_nft.setTierDelegatesTo(dd);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
function _buildScorecard(uint256 n) internal pure returns (DefifaTierCashOutWeight[] memory sc) {
|
|
664
|
-
sc = new DefifaTierCashOutWeight[](n);
|
|
665
|
-
for (uint256 i; i < n; i++) {
|
|
666
|
-
sc[i].id = i + 1;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function _attestAndRatify(DefifaTierCashOutWeight[] memory sc) internal {
|
|
671
|
-
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
672
|
-
_attestAllFor(pid);
|
|
673
|
-
_gov.ratifyScorecardFrom(_gameId, sc);
|
|
674
|
-
vm.warp(block.timestamp + 1);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
function _attestAllFor(uint256 pid) internal {
|
|
678
|
-
vm.warp(block.timestamp + _gov.attestationStartTimeOf(_gameId) + 1);
|
|
679
|
-
for (uint256 i; i < _users.length; i++) {
|
|
680
|
-
vm.prank(_users[i]);
|
|
681
|
-
try _gov.attestToScorecardFrom(_gameId, pid) {} catch {}
|
|
682
|
-
}
|
|
683
|
-
vm.warp(block.timestamp + _gov.attestationGracePeriodOf(_gameId) + 1);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function _surplus() internal view returns (uint256) {
|
|
687
|
-
return jbMultiTerminal().currentSurplusOf(_pid, new address[](0), 18, JBCurrencyIds.ETH);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function _cashOut(address user, uint256 tid, uint256 tnum) internal {
|
|
691
|
-
bytes memory meta = _cashOutMeta(tid, tnum);
|
|
692
|
-
vm.prank(user);
|
|
693
|
-
JBMultiTerminal(address(jbMultiTerminal()))
|
|
694
|
-
.cashOutTokensOf({
|
|
695
|
-
holder: user,
|
|
696
|
-
projectId: _pid,
|
|
697
|
-
cashOutCount: 0,
|
|
698
|
-
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
699
|
-
minTokensReclaimed: 0,
|
|
700
|
-
beneficiary: payable(user),
|
|
701
|
-
metadata: meta
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
function _cashOutMeta(uint256 tid, uint256 tnum) internal view returns (bytes memory) {
|
|
706
|
-
uint256[] memory cid = new uint256[](1);
|
|
707
|
-
cid[0] = (tid * 1_000_000_000) + tnum;
|
|
708
|
-
bytes[] memory data = new bytes[](1);
|
|
709
|
-
data[0] = abi.encode(cid);
|
|
710
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
711
|
-
ids[0] = metadataHelper().getId("cashOut", address(hook));
|
|
712
|
-
return metadataHelper().createMetadata(ids, data);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function _cashOutAllUsers() internal returns (uint256 total) {
|
|
716
|
-
for (uint256 i; i < _users.length; i++) {
|
|
717
|
-
uint256 bb = _users[i].balance;
|
|
718
|
-
_cashOut(_users[i], i + 1, 1);
|
|
719
|
-
total += _users[i].balance - bb;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
function _refund(address user, uint256 tid) internal {
|
|
724
|
-
JB721Tier memory tier = _nft.store().tierOf(address(_nft), tid, false);
|
|
725
|
-
uint256 nb = _nft.store().numberOfBurnedFor(address(_nft), tid);
|
|
726
|
-
// Tier fetched AFTER mint: remainingSupply already decremented, so no +1
|
|
727
|
-
uint256 tnum = tier.initialSupply - tier.remainingSupply + nb;
|
|
728
|
-
bytes memory meta = _cashOutMeta(tid, tnum);
|
|
729
|
-
vm.prank(user);
|
|
730
|
-
JBMultiTerminal(address(jbMultiTerminal()))
|
|
731
|
-
.cashOutTokensOf({
|
|
732
|
-
holder: user,
|
|
733
|
-
projectId: _pid,
|
|
734
|
-
cashOutCount: 0,
|
|
735
|
-
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
736
|
-
minTokensReclaimed: 0,
|
|
737
|
-
beneficiary: payable(user),
|
|
738
|
-
metadata: meta
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
}
|