@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,319 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import {Test} from "forge-std/Test.sol";
|
|
5
|
-
import {DefifaGovernor} from "../src/DefifaGovernor.sol";
|
|
6
|
-
import {DefifaDeployer} from "../src/DefifaDeployer.sol";
|
|
7
|
-
import {DefifaHook} from "../src/DefifaHook.sol";
|
|
8
|
-
import {DefifaTokenUriResolver} from "../src/DefifaTokenUriResolver.sol";
|
|
9
|
-
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
10
|
-
|
|
11
|
-
import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
|
|
12
|
-
import {TestBaseWorkflow} from "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
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
|
-
import {DefifaLaunchProjectData} from "../src/structs/DefifaLaunchProjectData.sol";
|
|
18
|
-
import {DefifaTierParams} from "../src/structs/DefifaTierParams.sol";
|
|
19
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
20
|
-
import {IJB721TokenUriResolver} from "@bananapus/721-hook-v6/src/interfaces/IJB721TokenUriResolver.sol";
|
|
21
|
-
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
22
|
-
import {IJBMultiTerminal} from "@bananapus/core-v6/src/interfaces/IJBMultiTerminal.sol";
|
|
23
|
-
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
24
|
-
import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
|
|
25
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
26
|
-
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
27
|
-
import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
28
|
-
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
29
|
-
import {JBMultiTerminal} from "@bananapus/core-v6/src/JBMultiTerminal.sol";
|
|
30
|
-
import {JBRulesetConfig, JBTerminalConfig} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
31
|
-
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
32
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
33
|
-
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
34
|
-
import {ITypeface} from "lib/typeface/contracts/interfaces/ITypeface.sol";
|
|
35
|
-
|
|
36
|
-
/// @title MintCostHandler
|
|
37
|
-
/// @notice Stateful fuzz handler that performs pay and refund operations,
|
|
38
|
-
/// tracking the expected _totalMintCost alongside.
|
|
39
|
-
contract MintCostHandler is Test {
|
|
40
|
-
DefifaHook public nft;
|
|
41
|
-
IJB721TiersHookStore public store;
|
|
42
|
-
uint256 public pid;
|
|
43
|
-
MetadataResolverHelper public metaHelper;
|
|
44
|
-
IJBMultiTerminal public terminal;
|
|
45
|
-
address public hookCodeOrigin;
|
|
46
|
-
|
|
47
|
-
uint256 public expectedMintCost;
|
|
48
|
-
uint256 public tierPrice;
|
|
49
|
-
uint8 public nTiers;
|
|
50
|
-
uint256 public mintCount;
|
|
51
|
-
uint256 public burnCount;
|
|
52
|
-
|
|
53
|
-
struct TokenInfo {
|
|
54
|
-
address holder;
|
|
55
|
-
uint256 tierId;
|
|
56
|
-
uint256 tokenNumber;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
TokenInfo[] public liveTokens;
|
|
60
|
-
|
|
61
|
-
constructor(
|
|
62
|
-
DefifaHook _nft,
|
|
63
|
-
uint256 _pid,
|
|
64
|
-
MetadataResolverHelper _metaHelper,
|
|
65
|
-
IJBMultiTerminal _terminal,
|
|
66
|
-
address _hookCodeOrigin,
|
|
67
|
-
uint256 _tierPrice,
|
|
68
|
-
uint8 _nTiers
|
|
69
|
-
) {
|
|
70
|
-
nft = _nft;
|
|
71
|
-
pid = _pid;
|
|
72
|
-
metaHelper = _metaHelper;
|
|
73
|
-
terminal = _terminal;
|
|
74
|
-
hookCodeOrigin = _hookCodeOrigin;
|
|
75
|
-
tierPrice = _tierPrice;
|
|
76
|
-
nTiers = _nTiers;
|
|
77
|
-
store = _nft.store();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/// @notice Mint a new NFT for a random tier.
|
|
81
|
-
function mint(uint256 tierSeed) external {
|
|
82
|
-
uint256 tierId = bound(tierSeed, 1, nTiers);
|
|
83
|
-
|
|
84
|
-
// Check remaining supply
|
|
85
|
-
JB721Tier memory tier = store.tierOf(address(nft), tierId, false);
|
|
86
|
-
if (tier.remainingSupply == 0) return;
|
|
87
|
-
|
|
88
|
-
address user = _userAddr(mintCount + 1000);
|
|
89
|
-
vm.deal(user, tierPrice);
|
|
90
|
-
|
|
91
|
-
uint16[] memory m = new uint16[](1);
|
|
92
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
93
|
-
m[0] = uint16(tierId);
|
|
94
|
-
bytes[] memory data = new bytes[](1);
|
|
95
|
-
data[0] = abi.encode(user, m);
|
|
96
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
97
|
-
ids[0] = metaHelper.getId("pay", hookCodeOrigin);
|
|
98
|
-
|
|
99
|
-
vm.prank(user);
|
|
100
|
-
terminal.pay{value: tierPrice}(
|
|
101
|
-
pid, JBConstants.NATIVE_TOKEN, tierPrice, user, 0, "", metaHelper.createMetadata(ids, data)
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
uint256 nb = store.numberOfBurnedFor(address(nft), tierId);
|
|
105
|
-
uint256 tokenNumber = tier.initialSupply - tier.remainingSupply + 1 + nb;
|
|
106
|
-
|
|
107
|
-
liveTokens.push(TokenInfo({holder: user, tierId: tierId, tokenNumber: tokenNumber}));
|
|
108
|
-
|
|
109
|
-
expectedMintCost += tierPrice;
|
|
110
|
-
mintCount++;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/// @notice Refund (cashout during mint phase) — burns the NFT and returns exact price.
|
|
114
|
-
function refund(uint256 indexSeed) external {
|
|
115
|
-
if (liveTokens.length == 0) return;
|
|
116
|
-
uint256 idx = bound(indexSeed, 0, liveTokens.length - 1);
|
|
117
|
-
|
|
118
|
-
TokenInfo memory info = liveTokens[idx];
|
|
119
|
-
uint256 tokenId = (info.tierId * 1_000_000_000) + info.tokenNumber;
|
|
120
|
-
|
|
121
|
-
uint256[] memory cid = new uint256[](1);
|
|
122
|
-
cid[0] = tokenId;
|
|
123
|
-
bytes[] memory data = new bytes[](1);
|
|
124
|
-
data[0] = abi.encode(cid);
|
|
125
|
-
bytes4[] memory bids = new bytes4[](1);
|
|
126
|
-
bids[0] = metaHelper.getId("cashOut", hookCodeOrigin);
|
|
127
|
-
|
|
128
|
-
vm.prank(info.holder);
|
|
129
|
-
JBMultiTerminal(address(terminal))
|
|
130
|
-
.cashOutTokensOf({
|
|
131
|
-
holder: info.holder,
|
|
132
|
-
projectId: pid,
|
|
133
|
-
cashOutCount: 0,
|
|
134
|
-
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
135
|
-
minTokensReclaimed: 0,
|
|
136
|
-
beneficiary: payable(info.holder),
|
|
137
|
-
metadata: metaHelper.createMetadata(bids, data)
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
expectedMintCost -= tierPrice;
|
|
141
|
-
burnCount++;
|
|
142
|
-
|
|
143
|
-
// Swap-and-pop
|
|
144
|
-
liveTokens[idx] = liveTokens[liveTokens.length - 1];
|
|
145
|
-
liveTokens.pop();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function liveTokenCount() external view returns (uint256) {
|
|
149
|
-
return liveTokens.length;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function _userAddr(uint256 i) internal pure returns (address) {
|
|
153
|
-
return address(bytes20(keccak256(abi.encode("inv_user", i))));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/// @title DefifaMintCostInvariantTest
|
|
158
|
-
/// @notice Invariant: _totalMintCost == sum of tier prices of all live (non-burned) NFTs.
|
|
159
|
-
contract DefifaMintCostInvariantTest is JBTest, TestBaseWorkflow {
|
|
160
|
-
using JBRulesetMetadataResolver for JBRuleset;
|
|
161
|
-
|
|
162
|
-
uint256 _protocolFeeProjectId;
|
|
163
|
-
uint256 _defifaProjectId;
|
|
164
|
-
address projectOwner = address(bytes20(keccak256("projectOwner")));
|
|
165
|
-
|
|
166
|
-
DefifaDeployer deployer;
|
|
167
|
-
DefifaHook hookImpl;
|
|
168
|
-
DefifaGovernor governor;
|
|
169
|
-
DefifaHook nft;
|
|
170
|
-
|
|
171
|
-
/// @dev Storage slot of _totalMintCost in DefifaHook (from `forge inspect DefifaHook storage-layout`).
|
|
172
|
-
uint256 constant TOTAL_MINT_COST_SLOT = 141;
|
|
173
|
-
|
|
174
|
-
uint8 constant N_TIERS = 4;
|
|
175
|
-
uint256 constant TIER_PRICE = 1 ether;
|
|
176
|
-
|
|
177
|
-
MintCostHandler handler;
|
|
178
|
-
|
|
179
|
-
function setUp() public virtual override {
|
|
180
|
-
super.setUp();
|
|
181
|
-
|
|
182
|
-
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
183
|
-
_tokens[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH});
|
|
184
|
-
JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
|
|
185
|
-
tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: _tokens});
|
|
186
|
-
JBRulesetConfig[] memory rc = new JBRulesetConfig[](1);
|
|
187
|
-
rc[0] = JBRulesetConfig({
|
|
188
|
-
mustStartAtOrAfter: 0,
|
|
189
|
-
duration: 10 days,
|
|
190
|
-
weight: 1e18,
|
|
191
|
-
weightCutPercent: 0,
|
|
192
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
193
|
-
metadata: JBRulesetMetadata({
|
|
194
|
-
reservedPercent: 0,
|
|
195
|
-
cashOutTaxRate: 0,
|
|
196
|
-
baseCurrency: JBCurrencyIds.ETH,
|
|
197
|
-
pausePay: false,
|
|
198
|
-
pauseCreditTransfers: false,
|
|
199
|
-
allowOwnerMinting: false,
|
|
200
|
-
allowSetCustomToken: false,
|
|
201
|
-
allowTerminalMigration: false,
|
|
202
|
-
allowSetTerminals: false,
|
|
203
|
-
allowSetController: false,
|
|
204
|
-
allowAddAccountingContext: false,
|
|
205
|
-
allowAddPriceFeed: false,
|
|
206
|
-
ownerMustSendPayouts: false,
|
|
207
|
-
holdFees: false,
|
|
208
|
-
useTotalSurplusForCashOuts: false,
|
|
209
|
-
useDataHookForPay: true,
|
|
210
|
-
useDataHookForCashOut: true,
|
|
211
|
-
dataHook: address(0),
|
|
212
|
-
metadata: 0
|
|
213
|
-
}),
|
|
214
|
-
splitGroups: new JBSplitGroup[](0),
|
|
215
|
-
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
_protocolFeeProjectId = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
|
|
219
|
-
vm.prank(projectOwner);
|
|
220
|
-
address _nanaToken =
|
|
221
|
-
address(jbController().deployERC20For(_protocolFeeProjectId, "Bananapus", "NANA", bytes32(0)));
|
|
222
|
-
|
|
223
|
-
_defifaProjectId = jbController().launchProjectFor(projectOwner, "", rc, tc, "");
|
|
224
|
-
vm.prank(projectOwner);
|
|
225
|
-
address _defifaToken = address(jbController().deployERC20For(_defifaProjectId, "Defifa", "DEFIFA", bytes32(0)));
|
|
226
|
-
|
|
227
|
-
hookImpl = new DefifaHook(jbDirectory(), IERC20(_defifaToken), IERC20(_nanaToken));
|
|
228
|
-
governor = new DefifaGovernor(jbController(), address(this));
|
|
229
|
-
deployer = new DefifaDeployer(
|
|
230
|
-
address(hookImpl),
|
|
231
|
-
new DefifaTokenUriResolver(ITypeface(address(0))),
|
|
232
|
-
governor,
|
|
233
|
-
jbController(),
|
|
234
|
-
new JBAddressRegistry(),
|
|
235
|
-
_protocolFeeProjectId,
|
|
236
|
-
_defifaProjectId
|
|
237
|
-
);
|
|
238
|
-
hookImpl.transferOwnership(address(deployer));
|
|
239
|
-
governor.transferOwnership(address(deployer));
|
|
240
|
-
|
|
241
|
-
// Launch the game with a long mint period for invariant testing
|
|
242
|
-
DefifaTierParams[] memory tp = new DefifaTierParams[](N_TIERS);
|
|
243
|
-
for (uint256 i; i < N_TIERS; i++) {
|
|
244
|
-
tp[i] = DefifaTierParams({
|
|
245
|
-
reservedRate: 1001,
|
|
246
|
-
reservedTokenBeneficiary: address(0),
|
|
247
|
-
encodedIPFSUri: bytes32(0),
|
|
248
|
-
shouldUseReservedTokenBeneficiaryAsDefault: false,
|
|
249
|
-
name: "DEFIFA"
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
DefifaLaunchProjectData memory d = DefifaLaunchProjectData({
|
|
254
|
-
name: "DEFIFA",
|
|
255
|
-
projectUri: "",
|
|
256
|
-
contractUri: "",
|
|
257
|
-
baseUri: "",
|
|
258
|
-
token: JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: JBCurrencyIds.ETH}),
|
|
259
|
-
mintPeriodDuration: 100 days,
|
|
260
|
-
start: uint48(block.timestamp + 200 days),
|
|
261
|
-
refundPeriodDuration: 100 days,
|
|
262
|
-
store: new JB721TiersHookStore(),
|
|
263
|
-
splits: new JBSplit[](0),
|
|
264
|
-
attestationStartTime: 0,
|
|
265
|
-
attestationGracePeriod: 100_381,
|
|
266
|
-
defaultAttestationDelegate: address(0),
|
|
267
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
268
|
-
tierPrice: uint104(TIER_PRICE),
|
|
269
|
-
tiers: tp,
|
|
270
|
-
defaultTokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
271
|
-
terminal: jbMultiTerminal(),
|
|
272
|
-
minParticipation: 0,
|
|
273
|
-
scorecardTimeout: 0,
|
|
274
|
-
timelockDuration: 0
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
uint256 pid = deployer.launchGameWith(d);
|
|
278
|
-
|
|
279
|
-
// Get the deployed hook clone
|
|
280
|
-
JBRuleset memory fc = jbRulesets().currentOf(pid);
|
|
281
|
-
if (fc.dataHook() == address(0)) (fc,) = jbRulesets().latestQueuedOf(pid);
|
|
282
|
-
nft = DefifaHook(fc.dataHook());
|
|
283
|
-
|
|
284
|
-
// Warp to mint phase
|
|
285
|
-
vm.warp(d.start - d.mintPeriodDuration - d.refundPeriodDuration);
|
|
286
|
-
|
|
287
|
-
handler =
|
|
288
|
-
new MintCostHandler(nft, pid, metadataHelper(), jbMultiTerminal(), address(hookImpl), TIER_PRICE, N_TIERS);
|
|
289
|
-
|
|
290
|
-
targetContract(address(handler));
|
|
291
|
-
|
|
292
|
-
bytes4[] memory selectors = new bytes4[](2);
|
|
293
|
-
selectors[0] = MintCostHandler.mint.selector;
|
|
294
|
-
selectors[1] = MintCostHandler.refund.selector;
|
|
295
|
-
targetSelector(FuzzSelector({addr: address(handler), selectors: selectors}));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/// @notice Read _totalMintCost directly from storage.
|
|
299
|
-
function _readTotalMintCost() internal view returns (uint256) {
|
|
300
|
-
return uint256(vm.load(address(nft), bytes32(TOTAL_MINT_COST_SLOT)));
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/// @notice INVARIANT: _totalMintCost always equals the handler's tracked expected value.
|
|
304
|
-
function invariant_totalMintCostMatchesExpected() external view {
|
|
305
|
-
assertEq(_readTotalMintCost(), handler.expectedMintCost(), "totalMintCost drift");
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/// @notice INVARIANT: _totalMintCost == tierPrice * live token count.
|
|
309
|
-
function invariant_totalMintCostEqualsPriceTimesLiveTokens() external view {
|
|
310
|
-
assertEq(_readTotalMintCost(), TIER_PRICE * handler.liveTokenCount(), "totalMintCost != price * liveTokens");
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/// @notice INVARIANT: mints - burns == live tokens.
|
|
314
|
-
function invariant_tokenCountConsistency() external view {
|
|
315
|
-
assertEq(
|
|
316
|
-
handler.mintCount() - handler.burnCount(), handler.liveTokenCount(), "mintCount - burnCount != liveTokens"
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|