@bananapus/721-hook-v6 0.0.41 → 0.0.43
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/foundry.lock +1 -7
- package/foundry.toml +1 -1
- package/package.json +20 -9
- package/script/Deploy.s.sol +2 -2
- package/src/JB721Checkpoints.sol +60 -18
- package/src/JB721CheckpointsDeployer.sol +10 -5
- package/src/JB721TiersHook.sol +4 -1
- package/src/JB721TiersHookProjectDeployer.sol +68 -30
- package/src/JB721TiersHookStore.sol +1 -4
- package/src/interfaces/IJB721Checkpoints.sol +21 -14
- package/src/interfaces/IJB721CheckpointsDeployer.sol +6 -2
- package/src/interfaces/IJB721TiersHookProjectDeployer.sol +2 -0
- package/test/utils/AccessJBLib.sol +49 -0
- package/test/utils/ForTest_JB721TiersHook.sol +246 -0
- package/test/utils/TestBaseWorkflow.sol +213 -0
- package/test/utils/UnitTestSetup.sol +805 -0
- package/.gas-snapshot +0 -152
- package/ADMINISTRATION.md +0 -87
- package/ARCHITECTURE.md +0 -98
- package/AUDIT_INSTRUCTIONS.md +0 -77
- package/RISKS.md +0 -118
- package/SKILLS.md +0 -43
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -121
- package/assets/findings/nana-721-hook-v6-pashov-ai-audit-report-20260330-091257.md +0 -83
- package/slither-ci.config.json +0 -10
- package/test/721HookAttacks.t.sol +0 -408
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +0 -985
- package/test/Fork.t.sol +0 -2346
- package/test/TestAuditGaps.sol +0 -1075
- package/test/TestCheckpoints.t.sol +0 -341
- package/test/TestSafeTransferReentrancy.t.sol +0 -305
- package/test/TestVotingUnitsLifecycle.t.sol +0 -313
- package/test/audit/AuditRegressions.t.sol +0 -83
- package/test/audit/CrossCurrencySplitNoPrices.t.sol +0 -123
- package/test/audit/FreshAudit.t.sol +0 -197
- package/test/audit/FutureTierPoC.t.sol +0 -39
- package/test/audit/FutureTierRemoval.t.sol +0 -47
- package/test/audit/Pass12L18.t.sol +0 -80
- package/test/audit/PayCreditsBypassTierSplits.t.sol +0 -200
- package/test/audit/ProjectDeployerAuth.t.sol +0 -266
- package/test/audit/RepoFindings.t.sol +0 -195
- package/test/audit/ReserveActivation.t.sol +0 -87
- package/test/audit/RetroactiveReserveBeneficiaryDilution.t.sol +0 -149
- package/test/audit/SameCurrencyDecimalMismatch.t.sol +0 -249
- package/test/audit/SplitCreditsMismatch.t.sol +0 -219
- package/test/audit/SplitFailureRedistribution.t.sol +0 -143
- package/test/audit/USDTVoidReturnCompat.t.sol +0 -301
- package/test/fork/ERC20CashOutFork.t.sol +0 -633
- package/test/fork/ERC20TierSplitFork.t.sol +0 -596
- package/test/fork/IssueTokensForSplitsFork.t.sol +0 -516
- package/test/invariants/TierLifecycleInvariant.t.sol +0 -188
- package/test/invariants/TieredHookStoreInvariant.t.sol +0 -86
- package/test/invariants/handlers/TierLifecycleHandler.sol +0 -300
- package/test/invariants/handlers/TierStoreHandler.sol +0 -165
- package/test/regression/BrokenTerminalDoesNotDos.t.sol +0 -277
- package/test/regression/CacheTierLookup.t.sol +0 -190
- package/test/regression/ProjectDeployerRulesets.t.sol +0 -358
- package/test/regression/ReserveBeneficiaryOverwrite.t.sol +0 -155
- package/test/regression/SplitDistributionBugs.t.sol +0 -751
- package/test/regression/SplitNoBeneficiary.t.sol +0 -140
- package/test/unit/AuditFixes_Unit.t.sol +0 -624
- package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +0 -116
- package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +0 -144
- package/test/unit/JBBitmap.t.sol +0 -170
- package/test/unit/JBIpfsDecoder.t.sol +0 -136
- package/test/unit/TierSupplyReserveCheck.t.sol +0 -221
- package/test/unit/adjustTier_Unit.t.sol +0 -1942
- package/test/unit/deployer_Unit.t.sol +0 -114
- package/test/unit/getters_constructor_Unit.t.sol +0 -593
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +0 -452
- package/test/unit/pay_CrossCurrency_Unit.t.sol +0 -530
- package/test/unit/pay_Unit.t.sol +0 -1661
- package/test/unit/redeem_Unit.t.sol +0 -473
- package/test/unit/relayBeneficiary_Unit.t.sol +0 -182
- package/test/unit/splitHookDistribution_Unit.t.sol +0 -604
- package/test/unit/tierSplitRouting_Unit.t.sol +0 -757
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
-
import "./utils/UnitTestSetup.sol";
|
|
6
|
-
import {IJB721TiersHookStore} from "../src/interfaces/IJB721TiersHookStore.sol";
|
|
7
|
-
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
8
|
-
|
|
9
|
-
/// @title TestVotingUnitsLifecycle
|
|
10
|
-
/// @notice Tests that voting units are correctly tracked through the full NFT lifecycle:
|
|
11
|
-
/// mint, transfer, and burn. Verifies that the store's votingUnitsOf aggregation stays
|
|
12
|
-
/// consistent as token ownership changes across tiers.
|
|
13
|
-
contract TestVotingUnitsLifecycle is UnitTestSetup {
|
|
14
|
-
using stdStorage for StdStorage;
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------
|
|
17
|
-
// Test 1: Mint -> Transfer -> Burn lifecycle for voting units
|
|
18
|
-
// ---------------------------------------------------------------
|
|
19
|
-
/// @notice Verifies that custom voting units (useVotingUnits=true) are correctly tracked
|
|
20
|
-
/// through the full lifecycle: mint to user A, transfer to user B, burn by user B.
|
|
21
|
-
function test_votingUnits_mintTransferBurn_lifecycle() public {
|
|
22
|
-
// Configure a tier with custom voting units.
|
|
23
|
-
defaultTierConfig.flags.allowOwnerMint = true;
|
|
24
|
-
defaultTierConfig.reserveFrequency = 0;
|
|
25
|
-
defaultTierConfig.flags.useVotingUnits = true;
|
|
26
|
-
defaultTierConfig.votingUnits = 100;
|
|
27
|
-
|
|
28
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(1);
|
|
29
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
30
|
-
|
|
31
|
-
address userA = makeAddr("userA");
|
|
32
|
-
address userB = makeAddr("userB");
|
|
33
|
-
|
|
34
|
-
// --- Mint: NFT goes to userA ---
|
|
35
|
-
uint16[] memory tiersToMint = new uint16[](1);
|
|
36
|
-
tiersToMint[0] = 1;
|
|
37
|
-
vm.prank(owner);
|
|
38
|
-
testHook.mintFor(tiersToMint, userA);
|
|
39
|
-
|
|
40
|
-
uint256 tokenId = _generateTokenId(1, 1);
|
|
41
|
-
|
|
42
|
-
// Verify userA has 100 voting units.
|
|
43
|
-
assertEq(
|
|
44
|
-
hookStore.votingUnitsOf(address(testHook), userA), 100, "UserA should have 100 voting units after mint"
|
|
45
|
-
);
|
|
46
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), userB), 0, "UserB should have 0 voting units after mint");
|
|
47
|
-
|
|
48
|
-
// --- Transfer: NFT from userA to userB ---
|
|
49
|
-
vm.prank(userA);
|
|
50
|
-
IERC721(address(testHook)).transferFrom(userA, userB, tokenId);
|
|
51
|
-
|
|
52
|
-
// Verify voting units moved from userA to userB.
|
|
53
|
-
assertEq(
|
|
54
|
-
hookStore.votingUnitsOf(address(testHook), userA), 0, "UserA should have 0 voting units after transfer"
|
|
55
|
-
);
|
|
56
|
-
assertEq(
|
|
57
|
-
hookStore.votingUnitsOf(address(testHook), userB), 100, "UserB should have 100 voting units after transfer"
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// --- Burn: userB burns the NFT ---
|
|
61
|
-
uint256[] memory tokensToBurn = new uint256[](1);
|
|
62
|
-
tokensToBurn[0] = tokenId;
|
|
63
|
-
vm.prank(address(0)); // burn uses _burn internally via ForTest
|
|
64
|
-
testHook.burn(tokensToBurn);
|
|
65
|
-
|
|
66
|
-
// Verify both users have 0 voting units after burn.
|
|
67
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), userA), 0, "UserA should have 0 voting units after burn");
|
|
68
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), userB), 0, "UserB should have 0 voting units after burn");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ---------------------------------------------------------------
|
|
72
|
-
// Test 2: Multi-tier voting units aggregation
|
|
73
|
-
// ---------------------------------------------------------------
|
|
74
|
-
/// @notice Verifies that voting units from multiple tiers aggregate correctly
|
|
75
|
-
/// and update properly when NFTs are transferred between users.
|
|
76
|
-
function test_votingUnits_multiTier_aggregation() public {
|
|
77
|
-
// Configure tiers with different custom voting units.
|
|
78
|
-
defaultTierConfig.flags.allowOwnerMint = true;
|
|
79
|
-
defaultTierConfig.reserveFrequency = 0;
|
|
80
|
-
defaultTierConfig.flags.useVotingUnits = true;
|
|
81
|
-
|
|
82
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(3);
|
|
83
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
84
|
-
|
|
85
|
-
// Set custom voting units for each tier.
|
|
86
|
-
// Tier 1 = 100, Tier 2 = 200, Tier 3 = 500.
|
|
87
|
-
testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 1, 100);
|
|
88
|
-
testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 2, 200);
|
|
89
|
-
testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 3, 500);
|
|
90
|
-
|
|
91
|
-
address user = makeAddr("user");
|
|
92
|
-
address recipient = makeAddr("recipient");
|
|
93
|
-
|
|
94
|
-
// --- Mint one NFT from each tier to the same user ---
|
|
95
|
-
uint16[] memory tier1 = new uint16[](1);
|
|
96
|
-
tier1[0] = 1;
|
|
97
|
-
uint16[] memory tier2 = new uint16[](1);
|
|
98
|
-
tier2[0] = 2;
|
|
99
|
-
uint16[] memory tier3 = new uint16[](1);
|
|
100
|
-
tier3[0] = 3;
|
|
101
|
-
|
|
102
|
-
vm.startPrank(owner);
|
|
103
|
-
testHook.mintFor(tier1, user);
|
|
104
|
-
testHook.mintFor(tier2, user);
|
|
105
|
-
testHook.mintFor(tier3, user);
|
|
106
|
-
vm.stopPrank();
|
|
107
|
-
|
|
108
|
-
// Verify total voting units = 100 + 200 + 500 = 800.
|
|
109
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), user), 800, "User should have 800 voting units total");
|
|
110
|
-
|
|
111
|
-
// --- Transfer tier 3 NFT (500 units) to recipient ---
|
|
112
|
-
uint256 tier3TokenId = _generateTokenId(3, 1);
|
|
113
|
-
vm.prank(user);
|
|
114
|
-
IERC721(address(testHook)).transferFrom(user, recipient, tier3TokenId);
|
|
115
|
-
|
|
116
|
-
// Verify user now has 300, recipient has 500.
|
|
117
|
-
assertEq(
|
|
118
|
-
hookStore.votingUnitsOf(address(testHook), user),
|
|
119
|
-
300,
|
|
120
|
-
"User should have 300 voting units after transferring tier 3"
|
|
121
|
-
);
|
|
122
|
-
assertEq(
|
|
123
|
-
hookStore.votingUnitsOf(address(testHook), recipient),
|
|
124
|
-
500,
|
|
125
|
-
"Recipient should have 500 voting units from tier 3"
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// --- Transfer tier 1 NFT (100 units) to recipient ---
|
|
129
|
-
uint256 tier1TokenId = _generateTokenId(1, 1);
|
|
130
|
-
vm.prank(user);
|
|
131
|
-
IERC721(address(testHook)).transferFrom(user, recipient, tier1TokenId);
|
|
132
|
-
|
|
133
|
-
// Verify user now has 200, recipient has 600.
|
|
134
|
-
assertEq(
|
|
135
|
-
hookStore.votingUnitsOf(address(testHook), user),
|
|
136
|
-
200,
|
|
137
|
-
"User should have 200 voting units after transferring tier 1"
|
|
138
|
-
);
|
|
139
|
-
assertEq(
|
|
140
|
-
hookStore.votingUnitsOf(address(testHook), recipient),
|
|
141
|
-
600,
|
|
142
|
-
"Recipient should have 600 voting units from tiers 1 and 3"
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------------
|
|
147
|
-
// Test 3: Price-based voting units (useVotingUnits=false)
|
|
148
|
-
// ---------------------------------------------------------------
|
|
149
|
-
/// @notice Verifies that when useVotingUnits is false, the tier price is used as voting power.
|
|
150
|
-
function test_votingUnits_priceBasedVoting_lifecycle() public {
|
|
151
|
-
// Configure tiers WITHOUT custom voting units (price-based voting).
|
|
152
|
-
defaultTierConfig.flags.allowOwnerMint = true;
|
|
153
|
-
defaultTierConfig.reserveFrequency = 0;
|
|
154
|
-
defaultTierConfig.flags.useVotingUnits = false;
|
|
155
|
-
defaultTierConfig.votingUnits = 0;
|
|
156
|
-
|
|
157
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(3);
|
|
158
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
159
|
-
|
|
160
|
-
address user = makeAddr("user");
|
|
161
|
-
address recipient = makeAddr("recipient");
|
|
162
|
-
|
|
163
|
-
// Default tiers have prices: tier 1 = 10, tier 2 = 20, tier 3 = 30 (from _createTiers).
|
|
164
|
-
uint16[] memory tier1 = new uint16[](1);
|
|
165
|
-
tier1[0] = 1;
|
|
166
|
-
uint16[] memory tier2 = new uint16[](1);
|
|
167
|
-
tier2[0] = 2;
|
|
168
|
-
uint16[] memory tier3 = new uint16[](1);
|
|
169
|
-
tier3[0] = 3;
|
|
170
|
-
|
|
171
|
-
vm.startPrank(owner);
|
|
172
|
-
testHook.mintFor(tier1, user);
|
|
173
|
-
testHook.mintFor(tier2, user);
|
|
174
|
-
testHook.mintFor(tier3, user);
|
|
175
|
-
vm.stopPrank();
|
|
176
|
-
|
|
177
|
-
// Verify total voting units = 10 + 20 + 30 = 60 (prices).
|
|
178
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), user), 60, "User should have 60 price-based voting units");
|
|
179
|
-
|
|
180
|
-
// Transfer tier 2 (price 20).
|
|
181
|
-
uint256 tier2TokenId = _generateTokenId(2, 1);
|
|
182
|
-
vm.prank(user);
|
|
183
|
-
IERC721(address(testHook)).transferFrom(user, recipient, tier2TokenId);
|
|
184
|
-
|
|
185
|
-
assertEq(
|
|
186
|
-
hookStore.votingUnitsOf(address(testHook), user),
|
|
187
|
-
40,
|
|
188
|
-
"User should have 40 voting units after transferring tier 2"
|
|
189
|
-
);
|
|
190
|
-
assertEq(
|
|
191
|
-
hookStore.votingUnitsOf(address(testHook), recipient),
|
|
192
|
-
20,
|
|
193
|
-
"Recipient should have 20 voting units from tier 2"
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ---------------------------------------------------------------
|
|
198
|
-
// Test 4: Multiple NFTs from the same tier
|
|
199
|
-
// ---------------------------------------------------------------
|
|
200
|
-
/// @notice Verifies that voting units scale correctly when a user owns multiple NFTs from one tier.
|
|
201
|
-
function test_votingUnits_multipleMintsSameTier() public {
|
|
202
|
-
// Configure tier with custom voting units.
|
|
203
|
-
defaultTierConfig.flags.allowOwnerMint = true;
|
|
204
|
-
defaultTierConfig.reserveFrequency = 0;
|
|
205
|
-
defaultTierConfig.flags.useVotingUnits = true;
|
|
206
|
-
defaultTierConfig.votingUnits = 50;
|
|
207
|
-
|
|
208
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(1);
|
|
209
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
210
|
-
|
|
211
|
-
address user = makeAddr("user");
|
|
212
|
-
|
|
213
|
-
// Mint 3 NFTs from tier 1 to the same user.
|
|
214
|
-
uint16[] memory tiersToMint = new uint16[](3);
|
|
215
|
-
tiersToMint[0] = 1;
|
|
216
|
-
tiersToMint[1] = 1;
|
|
217
|
-
tiersToMint[2] = 1;
|
|
218
|
-
|
|
219
|
-
vm.prank(owner);
|
|
220
|
-
testHook.mintFor(tiersToMint, user);
|
|
221
|
-
|
|
222
|
-
// Verify total voting units = 50 * 3 = 150.
|
|
223
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), user), 150, "User should have 150 voting units (3 x 50)");
|
|
224
|
-
|
|
225
|
-
// Transfer one NFT away.
|
|
226
|
-
address recipient = makeAddr("recipient");
|
|
227
|
-
uint256 tokenId1 = _generateTokenId(1, 1);
|
|
228
|
-
vm.prank(user);
|
|
229
|
-
IERC721(address(testHook)).transferFrom(user, recipient, tokenId1);
|
|
230
|
-
|
|
231
|
-
// Verify voting units: user = 100, recipient = 50.
|
|
232
|
-
assertEq(
|
|
233
|
-
hookStore.votingUnitsOf(address(testHook), user),
|
|
234
|
-
100,
|
|
235
|
-
"User should have 100 voting units after transferring 1"
|
|
236
|
-
);
|
|
237
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), recipient), 50, "Recipient should have 50 voting units");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// ---------------------------------------------------------------
|
|
241
|
-
// Test 5: Voting units are zero for addresses with no NFTs
|
|
242
|
-
// ---------------------------------------------------------------
|
|
243
|
-
/// @notice Verifies that addresses with no NFTs always return 0 voting units.
|
|
244
|
-
function test_votingUnits_zeroForNonHolders() public {
|
|
245
|
-
defaultTierConfig.flags.useVotingUnits = true;
|
|
246
|
-
defaultTierConfig.votingUnits = 100;
|
|
247
|
-
|
|
248
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(5);
|
|
249
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
250
|
-
|
|
251
|
-
address nonHolder = makeAddr("nonHolder");
|
|
252
|
-
|
|
253
|
-
// Verify zero voting units for an address that never held any NFTs.
|
|
254
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), nonHolder), 0, "Non-holder should have 0 voting units");
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ---------------------------------------------------------------
|
|
258
|
-
// Test 6: Voting units with mixed tier configs
|
|
259
|
-
// ---------------------------------------------------------------
|
|
260
|
-
/// @notice Verifies that voting units work correctly when some tiers use custom voting units
|
|
261
|
-
/// and others use price-based voting. The tier with useVotingUnits=false should use price.
|
|
262
|
-
function test_votingUnits_mixedTierConfigs() public {
|
|
263
|
-
defaultTierConfig.flags.allowOwnerMint = true;
|
|
264
|
-
defaultTierConfig.reserveFrequency = 0;
|
|
265
|
-
defaultTierConfig.flags.useVotingUnits = true;
|
|
266
|
-
defaultTierConfig.votingUnits = 100;
|
|
267
|
-
|
|
268
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(3);
|
|
269
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
270
|
-
|
|
271
|
-
// Override tier 2 to NOT use custom voting units (price-based).
|
|
272
|
-
// Tier 2 price from _createTiers is 20.
|
|
273
|
-
testHook.test_store()
|
|
274
|
-
.ForTest_setTier(
|
|
275
|
-
address(testHook),
|
|
276
|
-
2,
|
|
277
|
-
JBStored721Tier({
|
|
278
|
-
price: uint104(20),
|
|
279
|
-
remainingSupply: uint32(100),
|
|
280
|
-
initialSupply: uint32(100),
|
|
281
|
-
reserveFrequency: uint16(0),
|
|
282
|
-
category: uint24(100),
|
|
283
|
-
discountPercent: uint8(0),
|
|
284
|
-
packedBools: testHook.test_store().ForTest_packBools(true, false, false, false, false, false),
|
|
285
|
-
splitPercent: 0
|
|
286
|
-
})
|
|
287
|
-
);
|
|
288
|
-
// Clear tier 2's custom voting units (so it falls back to price).
|
|
289
|
-
testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 2, 0);
|
|
290
|
-
|
|
291
|
-
address user = makeAddr("user");
|
|
292
|
-
|
|
293
|
-
// Mint one NFT from each tier.
|
|
294
|
-
uint16[] memory tier1 = new uint16[](1);
|
|
295
|
-
tier1[0] = 1;
|
|
296
|
-
uint16[] memory tier2 = new uint16[](1);
|
|
297
|
-
tier2[0] = 2;
|
|
298
|
-
uint16[] memory tier3 = new uint16[](1);
|
|
299
|
-
tier3[0] = 3;
|
|
300
|
-
|
|
301
|
-
vm.startPrank(owner);
|
|
302
|
-
testHook.mintFor(tier1, user);
|
|
303
|
-
testHook.mintFor(tier2, user);
|
|
304
|
-
testHook.mintFor(tier3, user);
|
|
305
|
-
vm.stopPrank();
|
|
306
|
-
|
|
307
|
-
// Tier 1: custom voting units = 100
|
|
308
|
-
// Tier 2: price-based = 20 (useVotingUnits=false, so uses price)
|
|
309
|
-
// Tier 3: custom voting units = 100
|
|
310
|
-
// Total: 100 + 20 + 100 = 220
|
|
311
|
-
assertEq(hookStore.votingUnitsOf(address(testHook), user), 220, "User should have 220 mixed voting units");
|
|
312
|
-
}
|
|
313
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
// Import the shared unit test setup which deploys a hook clone with 10 tiers.
|
|
5
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
6
|
-
import "../utils/UnitTestSetup.sol";
|
|
7
|
-
|
|
8
|
-
// Import IERC2981 to compute its interface ID for the supportsInterface test.
|
|
9
|
-
import {IERC2981} from "@openzeppelin/contracts/interfaces/IERC2981.sol";
|
|
10
|
-
|
|
11
|
-
/// @notice Regression tests covering three audit findings for nana-721-hook-v6.
|
|
12
|
-
contract AuditRegressions is UnitTestSetup {
|
|
13
|
-
// -----------------------------------------------------------------------
|
|
14
|
-
// 1. Double-initialization guard
|
|
15
|
-
// -----------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
/// @notice Calling initialize on an already-initialized clone must revert.
|
|
18
|
-
function test_doubleInitialization_reverts() public {
|
|
19
|
-
// The `hook` from setUp() is already initialized via the deployer.
|
|
20
|
-
// Expect a revert with AlreadyInitialized carrying the existing project ID.
|
|
21
|
-
vm.expectRevert(abi.encodeWithSelector(JB721TiersHook.JB721TiersHook_AlreadyInitialized.selector, projectId));
|
|
22
|
-
|
|
23
|
-
// Attempt to initialize the hook again with valid parameters — must revert.
|
|
24
|
-
hook.initialize(
|
|
25
|
-
projectId,
|
|
26
|
-
"AnotherName",
|
|
27
|
-
"AN",
|
|
28
|
-
baseUri,
|
|
29
|
-
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
30
|
-
contractUri,
|
|
31
|
-
JB721InitTiersConfig({
|
|
32
|
-
tiers: new JB721TierConfig[](0), currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
|
|
33
|
-
}),
|
|
34
|
-
JB721TiersHookFlags({
|
|
35
|
-
preventOverspending: false,
|
|
36
|
-
issueTokensForSplits: false,
|
|
37
|
-
noNewTiersWithReserves: false,
|
|
38
|
-
noNewTiersWithVotes: false,
|
|
39
|
-
noNewTiersWithOwnerMinting: false
|
|
40
|
-
})
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// -----------------------------------------------------------------------
|
|
45
|
-
// 2. recordSetDiscountPercentOf on a removed tier must revert
|
|
46
|
-
// -----------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
/// @notice Setting the discount percent on a tier that has been removed must revert.
|
|
49
|
-
function test_setDiscountPercent_removedTier_reverts() public {
|
|
50
|
-
// The hook from setUp() has 10 tiers (IDs 1-10). Remove tier 1.
|
|
51
|
-
uint256[] memory tierIdsToRemove = new uint256[](1);
|
|
52
|
-
|
|
53
|
-
// Select tier 1 to remove.
|
|
54
|
-
tierIdsToRemove[0] = 1;
|
|
55
|
-
|
|
56
|
-
// Remove tier 1 as the hook owner.
|
|
57
|
-
vm.prank(owner);
|
|
58
|
-
hook.adjustTiers(new JB721TierConfig[](0), tierIdsToRemove);
|
|
59
|
-
|
|
60
|
-
// Expect a revert with TierRemoved when setting discount on the removed tier.
|
|
61
|
-
vm.expectRevert(abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_TierRemoved.selector, 1));
|
|
62
|
-
|
|
63
|
-
// Attempt to set a discount on the removed tier — must revert.
|
|
64
|
-
vm.prank(owner);
|
|
65
|
-
hook.setDiscountPercentOf(1, 50);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// -----------------------------------------------------------------------
|
|
69
|
-
// 3. ERC-2981 supportsInterface returns false (support was removed)
|
|
70
|
-
// -----------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
/// @notice supportsInterface must return false for IERC2981 since royalty support was removed.
|
|
73
|
-
function test_supportsInterface_erc2981_returnsFalse() public {
|
|
74
|
-
// Compute the IERC2981 interface ID from the imported interface.
|
|
75
|
-
bytes4 erc2981InterfaceId = type(IERC2981).interfaceId;
|
|
76
|
-
|
|
77
|
-
// Query supportsInterface on the hook.
|
|
78
|
-
bool supported = hook.supportsInterface(erc2981InterfaceId);
|
|
79
|
-
|
|
80
|
-
// Assert that ERC-2981 is NOT supported.
|
|
81
|
-
assertFalse(supported, "ERC-2981 must not be supported after royalty removal");
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
-
import "../utils/UnitTestSetup.sol";
|
|
6
|
-
import {IJB721TokenUriResolver} from "../../src/interfaces/IJB721TokenUriResolver.sol";
|
|
7
|
-
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
8
|
-
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
9
|
-
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
10
|
-
import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
11
|
-
import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
|
|
12
|
-
import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
|
|
13
|
-
|
|
14
|
-
contract CrossCurrencySplitNoPrices is UnitTestSetup {
|
|
15
|
-
function test_crossCurrencySplit_withoutPrices_locksForwardedNativeFunds() public {
|
|
16
|
-
JB721TiersHook noPricesOrigin = new JB721TiersHook(
|
|
17
|
-
IJBDirectory(mockJBDirectory),
|
|
18
|
-
IJBPermissions(mockJBPermissions),
|
|
19
|
-
IJBPrices(address(0)),
|
|
20
|
-
IJBRulesets(mockJBRulesets),
|
|
21
|
-
store,
|
|
22
|
-
IJBSplits(mockJBSplits),
|
|
23
|
-
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
24
|
-
trustedForwarder
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
address noPricesProxy = makeAddr("noPricesProxy");
|
|
28
|
-
vm.etch(noPricesProxy, address(noPricesOrigin).code);
|
|
29
|
-
JB721TiersHook crossHook = JB721TiersHook(noPricesProxy);
|
|
30
|
-
|
|
31
|
-
(JB721TierConfig[] memory tierConfigs,) = _createTiers(defaultTierConfig, 1);
|
|
32
|
-
tierConfigs[0].price = 1 ether;
|
|
33
|
-
tierConfigs[0].splitPercent = 500_000_000; // 50%
|
|
34
|
-
|
|
35
|
-
crossHook.initialize(
|
|
36
|
-
projectId,
|
|
37
|
-
name,
|
|
38
|
-
symbol,
|
|
39
|
-
baseUri,
|
|
40
|
-
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
41
|
-
contractUri,
|
|
42
|
-
JB721InitTiersConfig({tiers: tierConfigs, currency: uint32(USD()), decimals: 18}),
|
|
43
|
-
JB721TiersHookFlags({
|
|
44
|
-
preventOverspending: false,
|
|
45
|
-
issueTokensForSplits: false,
|
|
46
|
-
noNewTiersWithReserves: false,
|
|
47
|
-
noNewTiersWithVotes: false,
|
|
48
|
-
noNewTiersWithOwnerMinting: false
|
|
49
|
-
})
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
53
|
-
tierIdsToMint[0] = 1;
|
|
54
|
-
bytes[] memory data = new bytes[](1);
|
|
55
|
-
data[0] = abi.encode(true, tierIdsToMint);
|
|
56
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
57
|
-
ids[0] = metadataHelper.getId("pay", crossHook.METADATA_ID_TARGET());
|
|
58
|
-
bytes memory payerMetadata = metadataHelper.createMetadata(ids, data);
|
|
59
|
-
|
|
60
|
-
(uint256 weight, JBPayHookSpecification[] memory hookSpecifications) = crossHook.beforePayRecordedWith(
|
|
61
|
-
JBBeforePayRecordedContext({
|
|
62
|
-
terminal: mockTerminalAddress,
|
|
63
|
-
payer: beneficiary,
|
|
64
|
-
amount: JBTokenAmount({
|
|
65
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
66
|
-
value: 1 ether,
|
|
67
|
-
decimals: 18,
|
|
68
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
69
|
-
}),
|
|
70
|
-
projectId: projectId,
|
|
71
|
-
rulesetId: 0,
|
|
72
|
-
beneficiary: beneficiary,
|
|
73
|
-
weight: 10e18,
|
|
74
|
-
reservedPercent: 0,
|
|
75
|
-
metadata: payerMetadata
|
|
76
|
-
})
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// When PRICES is address(0) and currencies differ, convertAndCapSplitAmounts returns 0
|
|
80
|
-
// to avoid forwarding an unconverted amount in the wrong currency denomination.
|
|
81
|
-
// This means weight is NOT reduced (full weight) and no funds are forwarded.
|
|
82
|
-
assertEq(weight, 10e18, "weight unchanged when split conversion fails due to missing prices");
|
|
83
|
-
assertEq(hookSpecifications.length, 1, "one pay hook spec");
|
|
84
|
-
assertEq(hookSpecifications[0].amount, 0, "split amount is zero when prices unavailable for conversion");
|
|
85
|
-
|
|
86
|
-
mockAndExpect(
|
|
87
|
-
address(mockJBDirectory),
|
|
88
|
-
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
89
|
-
abi.encode(true)
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
JBAfterPayRecordedContext memory payContext = JBAfterPayRecordedContext({
|
|
93
|
-
payer: beneficiary,
|
|
94
|
-
projectId: projectId,
|
|
95
|
-
rulesetId: 0,
|
|
96
|
-
amount: JBTokenAmount({
|
|
97
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
98
|
-
value: 1 ether,
|
|
99
|
-
decimals: 18,
|
|
100
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
101
|
-
}),
|
|
102
|
-
forwardedAmount: JBTokenAmount({
|
|
103
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
104
|
-
value: hookSpecifications[0].amount,
|
|
105
|
-
decimals: 18,
|
|
106
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
107
|
-
}),
|
|
108
|
-
weight: weight,
|
|
109
|
-
newlyIssuedTokenCount: 0,
|
|
110
|
-
beneficiary: beneficiary,
|
|
111
|
-
hookMetadata: hookSpecifications[0].metadata,
|
|
112
|
-
payerMetadata: payerMetadata
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
vm.deal(mockTerminalAddress, 1 ether);
|
|
116
|
-
vm.prank(mockTerminalAddress);
|
|
117
|
-
crossHook.afterPayRecordedWith{value: hookSpecifications[0].amount}(payContext);
|
|
118
|
-
|
|
119
|
-
assertEq(crossHook.balanceOf(beneficiary), 0, "no NFTs minted (currency mismatch, no prices)");
|
|
120
|
-
assertEq(crossHook.payCreditsOf(beneficiary), 0, "no credits accrued (currency mismatch, no prices)");
|
|
121
|
-
assertEq(address(crossHook).balance, 0, "no funds forwarded to hook when split conversion returns zero");
|
|
122
|
-
}
|
|
123
|
-
}
|