@ballkidz/defifa 0.0.5 → 0.0.6
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/RISKS.md +7 -7
- package/STYLE_GUIDE.md +134 -43
- package/foundry.toml +0 -3
- package/package.json +6 -6
- package/remappings.txt +0 -5
- package/script/Deploy.s.sol +1 -1
- package/script/helpers/DefifaDeploymentLib.sol +1 -1
- package/src/interfaces/IDefifaDeployer.sol +0 -1
- package/src/interfaces/IDefifaGovernor.sol +0 -1
- package/src/interfaces/IDefifaTokenUriResolver.sol +0 -2
- package/src/structs/DefifaLaunchProjectData.sol +0 -2
- package/test/DefifaFeeAccounting.t.sol +4 -4
- package/test/DefifaGovernor.t.sol +4 -10
- package/test/{DefifaHook_AuditFindings.t.sol → DefifaHookRegressions.t.sol} +5 -5
- package/test/DefifaNoContest.t.sol +1 -1
- package/test/DefifaSecurity.t.sol +1 -1
- package/test/DefifaUSDC.t.sol +470 -0
- package/test/Fork.t.sol +70 -81
- package/test/regression/{M36_FulfillmentBlocksRatification.t.sol → FulfillmentBlocksRatification.t.sol} +3 -3
- package/test/regression/{M35_GracePeriodBypass.t.sol → GracePeriodBypass.t.sol} +3 -3
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import "../src/DefifaGovernor.sol";
|
|
6
|
+
import "../src/DefifaDeployer.sol";
|
|
7
|
+
import "../src/DefifaHook.sol";
|
|
8
|
+
import "../src/DefifaTokenUriResolver.sol";
|
|
9
|
+
import "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
10
|
+
|
|
11
|
+
import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
|
|
12
|
+
import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
|
|
13
|
+
import "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
14
|
+
import {JBTest} from "@bananapus/core-v6/test/helpers/JBTest.sol";
|
|
15
|
+
import "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
16
|
+
import "@bananapus/721-hook-v6/src/libraries/JB721TiersRulesetMetadataResolver.sol";
|
|
17
|
+
import "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
18
|
+
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
19
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
20
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
21
|
+
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
22
|
+
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
23
|
+
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
24
|
+
import {DefifaDelegation} from "../src/structs/DefifaDelegation.sol";
|
|
25
|
+
import {DefifaLaunchProjectData} from "../src/structs/DefifaLaunchProjectData.sol";
|
|
26
|
+
import {DefifaTierParams} from "../src/structs/DefifaTierParams.sol";
|
|
27
|
+
import {DefifaTierCashOutWeight} from "../src/structs/DefifaTierCashOutWeight.sol";
|
|
28
|
+
import {DefifaGamePhase} from "../src/enums/DefifaGamePhase.sol";
|
|
29
|
+
import {DefifaScorecardState} from "../src/enums/DefifaScorecardState.sol";
|
|
30
|
+
|
|
31
|
+
/// @notice Mock USDC token with 6 decimals.
|
|
32
|
+
contract DefifaMockUSDC is ERC20 {
|
|
33
|
+
constructor() ERC20("Mock USDC", "USDC") {}
|
|
34
|
+
|
|
35
|
+
function decimals() public pure override returns (uint8) {
|
|
36
|
+
return 6;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function mint(address to, uint256 amount) external {
|
|
40
|
+
_mint(to, amount);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// @dev Helper to read block.timestamp via an external call, bypassing the via-ir optimizer's timestamp caching.
|
|
45
|
+
contract USDCTimestampReader {
|
|
46
|
+
function timestamp() external view returns (uint256) {
|
|
47
|
+
return block.timestamp;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// @title DefifaUSDCTest
|
|
52
|
+
/// @notice Tests Defifa game lifecycle with USDC (6-decimal ERC-20) instead of native ETH.
|
|
53
|
+
/// Exercises 6-decimal accounting, fee calculations, and cash-out flows.
|
|
54
|
+
contract DefifaUSDCTest is JBTest, TestBaseWorkflow {
|
|
55
|
+
using JBRulesetMetadataResolver for JBRuleset;
|
|
56
|
+
|
|
57
|
+
USDCTimestampReader private _tsReader;
|
|
58
|
+
DefifaMockUSDC usdc;
|
|
59
|
+
|
|
60
|
+
address _protocolFeeProjectTokenAccount;
|
|
61
|
+
address _defifaProjectTokenAccount;
|
|
62
|
+
uint256 _protocolFeeProjectId;
|
|
63
|
+
uint256 _defifaProjectId;
|
|
64
|
+
uint256 _gameId = 3;
|
|
65
|
+
|
|
66
|
+
DefifaDeployer deployer;
|
|
67
|
+
DefifaHook hook;
|
|
68
|
+
DefifaGovernor governor;
|
|
69
|
+
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
70
|
+
|
|
71
|
+
// Shared test state
|
|
72
|
+
uint256 _pid;
|
|
73
|
+
DefifaHook _nft;
|
|
74
|
+
DefifaGovernor _gov;
|
|
75
|
+
address[] _users;
|
|
76
|
+
|
|
77
|
+
function setUp() public virtual override {
|
|
78
|
+
super.setUp();
|
|
79
|
+
|
|
80
|
+
_tsReader = new USDCTimestampReader();
|
|
81
|
+
usdc = new DefifaMockUSDC();
|
|
82
|
+
|
|
83
|
+
// Terminal configurations using USDC.
|
|
84
|
+
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
85
|
+
_tokens[0] = JBAccountingContext({token: address(usdc), decimals: 6, currency: uint32(uint160(address(usdc)))});
|
|
86
|
+
JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
|
|
87
|
+
tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
|
|
88
|
+
|
|
89
|
+
JBRulesetConfig[] memory rc = new JBRulesetConfig[](1);
|
|
90
|
+
rc[0] = JBRulesetConfig({
|
|
91
|
+
mustStartAtOrAfter: 0,
|
|
92
|
+
duration: 10 days,
|
|
93
|
+
weight: 1e18,
|
|
94
|
+
weightCutPercent: 0,
|
|
95
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
96
|
+
metadata: JBRulesetMetadata({
|
|
97
|
+
reservedPercent: 0,
|
|
98
|
+
cashOutTaxRate: 0,
|
|
99
|
+
baseCurrency: uint32(uint160(address(usdc))),
|
|
100
|
+
pausePay: false,
|
|
101
|
+
pauseCreditTransfers: false,
|
|
102
|
+
allowOwnerMinting: false,
|
|
103
|
+
allowSetCustomToken: false,
|
|
104
|
+
allowTerminalMigration: false,
|
|
105
|
+
allowSetTerminals: false,
|
|
106
|
+
allowSetController: false,
|
|
107
|
+
allowAddAccountingContext: false,
|
|
108
|
+
allowAddPriceFeed: false,
|
|
109
|
+
ownerMustSendPayouts: false,
|
|
110
|
+
holdFees: false,
|
|
111
|
+
useTotalSurplusForCashOuts: false,
|
|
112
|
+
useDataHookForPay: true,
|
|
113
|
+
useDataHookForCashOut: true,
|
|
114
|
+
dataHook: address(0),
|
|
115
|
+
metadata: 0
|
|
116
|
+
}),
|
|
117
|
+
splitGroups: new JBSplitGroup[](0),
|
|
118
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
_protocolFeeProjectId = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
|
|
122
|
+
vm.prank(projectOwner);
|
|
123
|
+
_protocolFeeProjectTokenAccount =
|
|
124
|
+
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
125
|
+
_defifaProjectId = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
|
|
126
|
+
vm.prank(projectOwner);
|
|
127
|
+
_defifaProjectTokenAccount =
|
|
128
|
+
address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
129
|
+
|
|
130
|
+
hook =
|
|
131
|
+
new DefifaHook(jbDirectory(), IERC20(_defifaProjectTokenAccount), IERC20(_protocolFeeProjectTokenAccount));
|
|
132
|
+
governor = new DefifaGovernor(jbController(), address(this));
|
|
133
|
+
deployer = new DefifaDeployer(
|
|
134
|
+
address(hook),
|
|
135
|
+
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
136
|
+
governor,
|
|
137
|
+
jbController(),
|
|
138
|
+
new JBAddressRegistry(),
|
|
139
|
+
_defifaProjectId,
|
|
140
|
+
_protocolFeeProjectId
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
uint8[] memory permissionIds = new uint8[](1);
|
|
144
|
+
permissionIds[0] = JBPermissionIds.SET_SPLIT_GROUPS;
|
|
145
|
+
vm.prank(projectOwner);
|
|
146
|
+
jbPermissions()
|
|
147
|
+
.setPermissionsFor(
|
|
148
|
+
projectOwner,
|
|
149
|
+
JBPermissionsData({
|
|
150
|
+
operator: address(deployer), projectId: uint64(_defifaProjectId), permissionIds: permissionIds
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
hook.transferOwnership(address(deployer));
|
|
155
|
+
governor.transferOwnership(address(deployer));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// =========================================================================
|
|
159
|
+
// USDC LAUNCH DATA HELPERS
|
|
160
|
+
// =========================================================================
|
|
161
|
+
|
|
162
|
+
function _launchDataUSDC(uint8 n, uint104 tierPrice) internal returns (DefifaLaunchProjectData memory) {
|
|
163
|
+
return _launchDataUSDCWith(n, tierPrice, 0, 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function _launchDataUSDCWith(
|
|
167
|
+
uint8 n,
|
|
168
|
+
uint104 tierPrice,
|
|
169
|
+
uint256 minParticipation,
|
|
170
|
+
uint32 scorecardTimeout
|
|
171
|
+
)
|
|
172
|
+
internal
|
|
173
|
+
returns (DefifaLaunchProjectData memory)
|
|
174
|
+
{
|
|
175
|
+
DefifaTierParams[] memory tp = new DefifaTierParams[](n);
|
|
176
|
+
for (uint256 i; i < n; i++) {
|
|
177
|
+
tp[i] = DefifaTierParams({
|
|
178
|
+
reservedRate: 1001,
|
|
179
|
+
reservedTokenBeneficiary: address(0),
|
|
180
|
+
encodedIPFSUri: bytes32(0),
|
|
181
|
+
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
182
|
+
name: "DEFIFA"
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return DefifaLaunchProjectData({
|
|
187
|
+
name: "DEFIFA_USDC",
|
|
188
|
+
projectUri: "",
|
|
189
|
+
contractUri: "",
|
|
190
|
+
baseUri: "",
|
|
191
|
+
token: JBAccountingContext({token: address(usdc), decimals: 6, currency: uint32(uint160(address(usdc)))}),
|
|
192
|
+
mintPeriodDuration: 1 days,
|
|
193
|
+
start: uint48(block.timestamp + 3 days),
|
|
194
|
+
refundPeriodDuration: 1 days,
|
|
195
|
+
store: new JB721TiersHookStore(),
|
|
196
|
+
splits: new JBSplit[](0),
|
|
197
|
+
attestationStartTime: 0,
|
|
198
|
+
attestationGracePeriod: 100_381,
|
|
199
|
+
defaultAttestationDelegate: address(0),
|
|
200
|
+
tierPrice: tierPrice,
|
|
201
|
+
tiers: tp,
|
|
202
|
+
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
203
|
+
terminal: jbMultiTerminal(),
|
|
204
|
+
minParticipation: minParticipation,
|
|
205
|
+
scorecardTimeout: scorecardTimeout
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function _launch(DefifaLaunchProjectData memory d) internal returns (uint256 p, DefifaHook n, DefifaGovernor g) {
|
|
210
|
+
g = governor;
|
|
211
|
+
p = deployer.launchGameWith(d);
|
|
212
|
+
JBRuleset memory fc = jbRulesets().currentOf(p);
|
|
213
|
+
if (fc.dataHook() == address(0)) (fc,) = jbRulesets().latestQueuedOf(p);
|
|
214
|
+
n = DefifaHook(fc.dataHook());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function _addr(uint256 i) internal pure returns (address) {
|
|
218
|
+
return address(bytes20(keccak256(abi.encode("usdc_user", i))));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function _mintUSDC(address user, uint256 tid, uint104 amt) internal {
|
|
222
|
+
usdc.mint(user, amt);
|
|
223
|
+
uint16[] memory m = new uint16[](1);
|
|
224
|
+
m[0] = uint16(tid);
|
|
225
|
+
bytes[] memory data = new bytes[](1);
|
|
226
|
+
data[0] = abi.encode(user, m);
|
|
227
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
228
|
+
ids[0] = metadataHelper().getId("pay", address(hook));
|
|
229
|
+
vm.startPrank(user);
|
|
230
|
+
usdc.approve(address(jbMultiTerminal()), amt);
|
|
231
|
+
jbMultiTerminal().pay(_pid, address(usdc), amt, user, 0, "", metadataHelper().createMetadata(ids, data));
|
|
232
|
+
vm.stopPrank();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function _delegateSelf(address user, uint256 tid) internal {
|
|
236
|
+
DefifaDelegation[] memory dd = new DefifaDelegation[](1);
|
|
237
|
+
dd[0] = DefifaDelegation({delegatee: user, tierId: tid});
|
|
238
|
+
vm.prank(user);
|
|
239
|
+
_nft.setTierDelegatesTo(dd);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function _buildScorecard(uint256 n) internal pure returns (DefifaTierCashOutWeight[] memory sc) {
|
|
243
|
+
sc = new DefifaTierCashOutWeight[](n);
|
|
244
|
+
for (uint256 i; i < n; i++) {
|
|
245
|
+
sc[i].id = i + 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function _evenScorecard(uint256 n) internal view returns (DefifaTierCashOutWeight[] memory sc) {
|
|
250
|
+
sc = _buildScorecard(n);
|
|
251
|
+
uint256 tw = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
252
|
+
uint256 assigned;
|
|
253
|
+
for (uint256 i; i < n; i++) {
|
|
254
|
+
if (i == n - 1) {
|
|
255
|
+
sc[i].cashOutWeight = tw - assigned;
|
|
256
|
+
} else {
|
|
257
|
+
sc[i].cashOutWeight = tw / n;
|
|
258
|
+
}
|
|
259
|
+
assigned += sc[i].cashOutWeight;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function _attestAndRatify(DefifaTierCashOutWeight[] memory sc) internal {
|
|
264
|
+
uint256 pid = _gov.submitScorecardFor(_gameId, sc);
|
|
265
|
+
uint256 attestStart = _gov.attestationStartTimeOf(_gameId);
|
|
266
|
+
uint256 current = _tsReader.timestamp();
|
|
267
|
+
vm.warp((attestStart > current ? attestStart : current) + 1);
|
|
268
|
+
for (uint256 i; i < _users.length; i++) {
|
|
269
|
+
vm.prank(_users[i]);
|
|
270
|
+
_gov.attestToScorecardFrom(_gameId, pid);
|
|
271
|
+
}
|
|
272
|
+
vm.warp(_tsReader.timestamp() + _gov.attestationGracePeriodOf(_gameId) + 1);
|
|
273
|
+
_gov.ratifyScorecardFrom(_gameId, sc);
|
|
274
|
+
vm.warp(_tsReader.timestamp() + 1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function _toScoring() internal {
|
|
278
|
+
vm.warp(_tsReader.timestamp() + 3 days + 1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function _setupGameUSDC(uint8 nTiers, uint104 tierPrice) internal {
|
|
282
|
+
DefifaLaunchProjectData memory d = _launchDataUSDC(nTiers, tierPrice);
|
|
283
|
+
(_pid, _nft, _gov) = _launch(d);
|
|
284
|
+
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
285
|
+
_users = new address[](nTiers);
|
|
286
|
+
for (uint256 i; i < nTiers; i++) {
|
|
287
|
+
_users[i] = _addr(i);
|
|
288
|
+
_mintUSDC(_users[i], i + 1, tierPrice);
|
|
289
|
+
_delegateSelf(_users[i], i + 1);
|
|
290
|
+
vm.warp(_tsReader.timestamp() + 1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function _balance() internal view returns (uint256) {
|
|
295
|
+
return jbMultiTerminal().STORE().balanceOf(address(jbMultiTerminal()), _pid, address(usdc));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function _surplus() internal view returns (uint256) {
|
|
299
|
+
return jbMultiTerminal()
|
|
300
|
+
.currentSurplusOf(_pid, jbMultiTerminal().accountingContextsOf(_pid), 6, uint32(uint160(address(usdc))));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function _generateTokenId(uint256 tierId, uint256 tokenNumber) internal pure returns (uint256) {
|
|
304
|
+
return (tierId * 1_000_000_000) + tokenNumber;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function _buildCashOutMetadata(bytes memory decodedData) internal view returns (bytes memory) {
|
|
308
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
309
|
+
ids[0] = metadataHelper().getId("cashOut", address(hook));
|
|
310
|
+
bytes[] memory datas = new bytes[](1);
|
|
311
|
+
datas[0] = decodedData;
|
|
312
|
+
return metadataHelper().createMetadata(ids, datas);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function _cashOutUSDC(address user, uint256 tid, uint256 tnum) internal {
|
|
316
|
+
uint256[] memory cashOutIds = new uint256[](1);
|
|
317
|
+
cashOutIds[0] = _generateTokenId(tid, tnum);
|
|
318
|
+
bytes memory cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutIds));
|
|
319
|
+
|
|
320
|
+
vm.prank(user);
|
|
321
|
+
JBMultiTerminal(address(jbMultiTerminal()))
|
|
322
|
+
.cashOutTokensOf({
|
|
323
|
+
holder: user,
|
|
324
|
+
projectId: _pid,
|
|
325
|
+
cashOutCount: 0,
|
|
326
|
+
tokenToReclaim: address(usdc),
|
|
327
|
+
minTokensReclaimed: 0,
|
|
328
|
+
beneficiary: payable(user),
|
|
329
|
+
metadata: cashOutMetadata
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function _refundUSDC(address user, uint256 tid) internal {
|
|
334
|
+
uint256[] memory cashOutIds = new uint256[](1);
|
|
335
|
+
cashOutIds[0] = _generateTokenId(tid, 1);
|
|
336
|
+
bytes memory cashOutMetadata = _buildCashOutMetadata(abi.encode(cashOutIds));
|
|
337
|
+
|
|
338
|
+
vm.prank(user);
|
|
339
|
+
JBMultiTerminal(address(jbMultiTerminal()))
|
|
340
|
+
.cashOutTokensOf({
|
|
341
|
+
holder: user,
|
|
342
|
+
projectId: _pid,
|
|
343
|
+
cashOutCount: 0,
|
|
344
|
+
tokenToReclaim: address(usdc),
|
|
345
|
+
minTokensReclaimed: 0,
|
|
346
|
+
beneficiary: payable(user),
|
|
347
|
+
metadata: cashOutMetadata
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// =========================================================================
|
|
352
|
+
// TESTS
|
|
353
|
+
// =========================================================================
|
|
354
|
+
|
|
355
|
+
/// @notice Test 1: Mint and refund with USDC.
|
|
356
|
+
function test_defifa_usdc_mintAndRefund() external {
|
|
357
|
+
uint104 tierPrice = 100e6; // 100 USDC
|
|
358
|
+
_setupGameUSDC(4, tierPrice);
|
|
359
|
+
|
|
360
|
+
// Verify MINT phase.
|
|
361
|
+
assertEq(uint256(deployer.currentGamePhaseOf(_pid)), uint256(DefifaGamePhase.MINT));
|
|
362
|
+
|
|
363
|
+
// Verify all 4 users hold NFTs.
|
|
364
|
+
for (uint256 i; i < 4; i++) {
|
|
365
|
+
assertEq(_nft.balanceOf(_users[i]), 1, "each user holds 1 NFT");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Terminal should have 400 USDC.
|
|
369
|
+
assertEq(_balance(), 400e6, "terminal balance = 400 USDC");
|
|
370
|
+
|
|
371
|
+
// Refund user 0 during MINT phase.
|
|
372
|
+
uint256 balBefore = usdc.balanceOf(_users[0]);
|
|
373
|
+
_refundUSDC(_users[0], 1);
|
|
374
|
+
assertEq(usdc.balanceOf(_users[0]) - balBefore, 100e6, "refund = 100 USDC");
|
|
375
|
+
assertEq(_nft.balanceOf(_users[0]), 0, "NFT burned on refund");
|
|
376
|
+
|
|
377
|
+
// Remaining balance = 300 USDC.
|
|
378
|
+
assertEq(_balance(), 300e6, "terminal balance = 300 USDC after refund");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// @notice Test 2: Scorecard and distribute with USDC.
|
|
382
|
+
function test_defifa_usdc_scorecardAndDistribute() external {
|
|
383
|
+
uint104 tierPrice = 100e6;
|
|
384
|
+
_setupGameUSDC(4, tierPrice);
|
|
385
|
+
|
|
386
|
+
_toScoring();
|
|
387
|
+
|
|
388
|
+
// Tier 1 = 100% weight.
|
|
389
|
+
DefifaTierCashOutWeight[] memory sc = _buildScorecard(4);
|
|
390
|
+
sc[0].cashOutWeight = _nft.TOTAL_CASHOUT_WEIGHT();
|
|
391
|
+
_attestAndRatify(sc);
|
|
392
|
+
|
|
393
|
+
assertEq(uint256(deployer.currentGamePhaseOf(_pid)), uint256(DefifaGamePhase.COMPLETE));
|
|
394
|
+
|
|
395
|
+
// Winner cashes out -> receives USDC.
|
|
396
|
+
uint256 winnerBalBefore = usdc.balanceOf(_users[0]);
|
|
397
|
+
_cashOutUSDC(_users[0], 1, 1);
|
|
398
|
+
uint256 winnerReceived = usdc.balanceOf(_users[0]) - winnerBalBefore;
|
|
399
|
+
assertGt(winnerReceived, 0, "winner received USDC");
|
|
400
|
+
|
|
401
|
+
// Losers get 0 USDC.
|
|
402
|
+
for (uint256 i = 1; i < 4; i++) {
|
|
403
|
+
uint256 bb = usdc.balanceOf(_users[i]);
|
|
404
|
+
_cashOutUSDC(_users[i], i + 1, 1);
|
|
405
|
+
assertEq(usdc.balanceOf(_users[i]), bb, "loser gets 0 USDC");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/// @notice Test 3: Fee accounting with USDC (6-decimal precision).
|
|
410
|
+
function test_defifa_usdc_feeAccounting() external {
|
|
411
|
+
uint104 tierPrice = 100e6;
|
|
412
|
+
_setupGameUSDC(4, tierPrice);
|
|
413
|
+
|
|
414
|
+
uint256 potBefore = _balance();
|
|
415
|
+
assertEq(potBefore, 400e6, "pot = 400 USDC");
|
|
416
|
+
|
|
417
|
+
// Expected fee: 7.5% (2.5% NANA + 5% DEFIFA).
|
|
418
|
+
uint256 expectedFee = (potBefore * 75_000_000) / JBConstants.SPLITS_TOTAL_PERCENT;
|
|
419
|
+
uint256 expectedSurplus = potBefore - expectedFee;
|
|
420
|
+
|
|
421
|
+
_toScoring();
|
|
422
|
+
_attestAndRatify(_evenScorecard(4));
|
|
423
|
+
|
|
424
|
+
uint256 potAfter = _balance();
|
|
425
|
+
assertEq(potAfter, expectedSurplus, "surplus after fees = pot - 7.5%");
|
|
426
|
+
|
|
427
|
+
uint256 fulfilled = deployer.fulfilledCommitmentsOf(_pid);
|
|
428
|
+
assertEq(fulfilled, expectedFee, "fulfilled = fee amount");
|
|
429
|
+
assertEq(fulfilled + potAfter, potBefore, "fee + surplus = original pot exactly (no rounding loss)");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/// @notice Test 4: No-contest with USDC (minParticipation threshold).
|
|
433
|
+
function test_defifa_usdc_noContest() external {
|
|
434
|
+
uint104 tierPrice = 100e6;
|
|
435
|
+
DefifaLaunchProjectData memory d = _launchDataUSDCWith(4, tierPrice, 500e6, 0); // 500 USDC min
|
|
436
|
+
(_pid, _nft, _gov) = _launch(d);
|
|
437
|
+
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
438
|
+
|
|
439
|
+
// Mint only 1 tier = 100 USDC < 500 USDC threshold.
|
|
440
|
+
_users = new address[](1);
|
|
441
|
+
_users[0] = _addr(0);
|
|
442
|
+
_mintUSDC(_users[0], 1, tierPrice);
|
|
443
|
+
|
|
444
|
+
_toScoring();
|
|
445
|
+
|
|
446
|
+
// balance = 100 USDC < 500 USDC → NO_CONTEST.
|
|
447
|
+
assertEq(uint256(deployer.currentGamePhaseOf(_pid)), uint256(DefifaGamePhase.NO_CONTEST));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/// @notice Test 5: Game pot reporting with USDC.
|
|
451
|
+
function test_defifa_usdc_potCalculation() external {
|
|
452
|
+
uint104 tierPrice = 100e6;
|
|
453
|
+
_setupGameUSDC(4, tierPrice);
|
|
454
|
+
|
|
455
|
+
_toScoring();
|
|
456
|
+
|
|
457
|
+
(uint256 potExcluding,,) = deployer.currentGamePotOf(_pid, false);
|
|
458
|
+
(uint256 potIncluding,,) = deployer.currentGamePotOf(_pid, true);
|
|
459
|
+
assertEq(potExcluding, 400e6, "pot excluding = 400 USDC");
|
|
460
|
+
assertEq(potIncluding, 400e6, "pot including = 400 USDC (no fulfillment yet)");
|
|
461
|
+
|
|
462
|
+
_attestAndRatify(_evenScorecard(4));
|
|
463
|
+
|
|
464
|
+
uint256 fee = deployer.fulfilledCommitmentsOf(_pid);
|
|
465
|
+
(potExcluding,,) = deployer.currentGamePotOf(_pid, false);
|
|
466
|
+
(potIncluding,,) = deployer.currentGamePotOf(_pid, true);
|
|
467
|
+
assertEq(potExcluding, 400e6 - fee, "pot excluding = surplus");
|
|
468
|
+
assertEq(potIncluding, 400e6, "pot including = original pot");
|
|
469
|
+
}
|
|
470
|
+
}
|