@bananapus/721-hook-v6 0.0.42 → 0.0.45
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 +61 -19
- package/src/JB721CheckpointsDeployer.sol +10 -5
- package/src/JB721TiersHook.sol +66 -53
- package/src/JB721TiersHookDeployer.sol +8 -5
- package/src/JB721TiersHookProjectDeployer.sol +87 -46
- package/src/JB721TiersHookStore.sol +137 -107
- package/src/abstract/JB721Hook.sol +8 -6
- package/src/interfaces/IJB721Checkpoints.sol +21 -14
- package/src/interfaces/IJB721CheckpointsDeployer.sol +7 -3
- package/src/interfaces/IJB721TiersHook.sol +3 -3
- package/src/interfaces/IJB721TiersHookProjectDeployer.sol +4 -2
- package/src/interfaces/IJB721TiersHookStore.sol +11 -11
- package/src/libraries/JB721TiersHookLib.sol +1 -1
- package/src/structs/JB721TiersHookFlags.sol +1 -1
- package/src/structs/JBPayDataHookRulesetMetadata.sol +1 -1
- 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/CodexNemesisReserveSellout.t.sol +0 -66
- 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/ReserveSlotProtection.t.sol +0 -273
- 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,221 +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
|
-
|
|
7
|
-
/// @title M6_TierSupplyCheck
|
|
8
|
-
/// @notice Tests that the supply check accounts for pending reserves when minting paid NFTs.
|
|
9
|
-
/// Without the `1 +` in the supply check, the last available slot can be consumed by a paid mint, making
|
|
10
|
-
/// pending reserves unmintable (recordMintReservesFor reverts decrementing remainingSupply past zero).
|
|
11
|
-
contract M6_TierSupplyCheck is UnitTestSetup {
|
|
12
|
-
using stdStorage for StdStorage;
|
|
13
|
-
|
|
14
|
-
/// @dev Mock the directory to accept `mockTerminalAddress` as a terminal for `projectId`.
|
|
15
|
-
function _mockTerminalAuth() internal {
|
|
16
|
-
mockAndExpect(
|
|
17
|
-
mockJBDirectory,
|
|
18
|
-
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
19
|
-
abi.encode(true)
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/// @dev Create a pay context that requests minting specific tier IDs.
|
|
24
|
-
function _buildPayContext(
|
|
25
|
-
address targetHook,
|
|
26
|
-
uint256 value,
|
|
27
|
-
uint16[] memory tierIds
|
|
28
|
-
)
|
|
29
|
-
internal
|
|
30
|
-
view
|
|
31
|
-
returns (JBAfterPayRecordedContext memory)
|
|
32
|
-
{
|
|
33
|
-
bytes[] memory data = new bytes[](1);
|
|
34
|
-
data[0] = abi.encode(false, tierIds);
|
|
35
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
36
|
-
ids[0] = metadataHelper.getId("pay", targetHook);
|
|
37
|
-
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
38
|
-
|
|
39
|
-
return JBAfterPayRecordedContext({
|
|
40
|
-
payer: beneficiary,
|
|
41
|
-
projectId: projectId,
|
|
42
|
-
rulesetId: 0,
|
|
43
|
-
amount: JBTokenAmount({
|
|
44
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
45
|
-
value: value,
|
|
46
|
-
decimals: 18,
|
|
47
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
48
|
-
}),
|
|
49
|
-
forwardedAmount: JBTokenAmount({
|
|
50
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
51
|
-
value: 0,
|
|
52
|
-
decimals: 18,
|
|
53
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
54
|
-
}),
|
|
55
|
-
weight: 10 ** 18,
|
|
56
|
-
newlyIssuedTokenCount: 0,
|
|
57
|
-
beneficiary: beneficiary,
|
|
58
|
-
hookMetadata: bytes(""),
|
|
59
|
-
payerMetadata: hookMetadata
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/// @dev Helper: mint `count` NFTs from tier 1 via pay.
|
|
64
|
-
function _mintPaid(ForTest_JB721TiersHook targetHook, uint256 count) internal {
|
|
65
|
-
uint16[] memory tierIds = new uint16[](count);
|
|
66
|
-
for (uint256 i; i < count; i++) {
|
|
67
|
-
tierIds[i] = 1;
|
|
68
|
-
}
|
|
69
|
-
JBAfterPayRecordedContext memory ctx = _buildPayContext(address(targetHook), count * 10, tierIds); // price=10
|
|
70
|
-
// per NFT
|
|
71
|
-
vm.prank(mockTerminalAddress);
|
|
72
|
-
targetHook.afterPayRecordedWith(ctx);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// =========================================================================
|
|
76
|
-
// Test 1: Prove the edge case — paid mint would steal reserves' last slot
|
|
77
|
-
// =========================================================================
|
|
78
|
-
/// @notice With reserveFrequency=2 and initialSupply=10:
|
|
79
|
-
/// After 6 paid mints → 4 remaining, 3 pending reserves.
|
|
80
|
-
/// Without the fix, a 7th paid mint would pass (4 > 3) leaving only 3 remaining for 4 pending reserves.
|
|
81
|
-
/// With the fix, the 7th mint decrements first (remaining→3), then checks 3 < ceil(7/2)=4 → reverts.
|
|
82
|
-
function test_M6_paidMintCannotStealReserveSlot() public {
|
|
83
|
-
// Configure: small supply, reserve every 2 mints.
|
|
84
|
-
defaultTierConfig.price = uint104(10);
|
|
85
|
-
defaultTierConfig.initialSupply = uint32(10);
|
|
86
|
-
defaultTierConfig.reserveFrequency = uint16(2);
|
|
87
|
-
defaultTierConfig.reserveBeneficiary = reserveBeneficiary;
|
|
88
|
-
|
|
89
|
-
ForTest_JB721TiersHook targetHook = _initializeForTestHook(1);
|
|
90
|
-
_mockTerminalAuth();
|
|
91
|
-
|
|
92
|
-
// Mint 6 paid NFTs. State: remaining=4, nonReserveMints=6, pending=ceil(6/2)=3.
|
|
93
|
-
_mintPaid(targetHook, 6);
|
|
94
|
-
|
|
95
|
-
JB721Tier memory tier = targetHook.STORE().tierOf(address(targetHook), 1, false);
|
|
96
|
-
assertEq(tier.remainingSupply, 4, "Should have 4 remaining after 6 mints");
|
|
97
|
-
|
|
98
|
-
uint256 pending = targetHook.STORE().numberOfPendingReservesFor(address(targetHook), 1);
|
|
99
|
-
assertEq(pending, 3, "Should have 3 pending reserves (ceil(6/2)=3)");
|
|
100
|
-
|
|
101
|
-
// The 7th paid mint should revert: remaining(4) <= 1 + pending(3) = 4.
|
|
102
|
-
// Without the fix (just `<=` pending), this would pass since 4 > 3.
|
|
103
|
-
uint16[] memory oneMore = new uint16[](1);
|
|
104
|
-
oneMore[0] = 1;
|
|
105
|
-
JBAfterPayRecordedContext memory ctx = _buildPayContext(address(targetHook), 10, oneMore);
|
|
106
|
-
|
|
107
|
-
vm.prank(mockTerminalAddress);
|
|
108
|
-
vm.expectRevert(
|
|
109
|
-
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
110
|
-
);
|
|
111
|
-
targetHook.afterPayRecordedWith(ctx);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// =========================================================================
|
|
115
|
-
// Test 2: Reserves remain fully mintable after paid mints
|
|
116
|
-
// =========================================================================
|
|
117
|
-
/// @notice After minting paid NFTs up to the allowed limit, all pending reserves should be mintable.
|
|
118
|
-
function test_M6_reservesFullyMintableAfterPaidMints() public {
|
|
119
|
-
defaultTierConfig.price = uint104(10);
|
|
120
|
-
defaultTierConfig.initialSupply = uint32(10);
|
|
121
|
-
defaultTierConfig.reserveFrequency = uint16(2);
|
|
122
|
-
defaultTierConfig.reserveBeneficiary = reserveBeneficiary;
|
|
123
|
-
|
|
124
|
-
ForTest_JB721TiersHook targetHook = _initializeForTestHook(1);
|
|
125
|
-
_mockTerminalAuth();
|
|
126
|
-
|
|
127
|
-
// Mint 6 paid NFTs (the maximum allowed given the fix).
|
|
128
|
-
_mintPaid(targetHook, 6);
|
|
129
|
-
|
|
130
|
-
// Mint all pending reserves — this must succeed.
|
|
131
|
-
uint256 pending = targetHook.STORE().numberOfPendingReservesFor(address(targetHook), 1);
|
|
132
|
-
assertEq(pending, 3, "3 pending reserves");
|
|
133
|
-
|
|
134
|
-
vm.prank(owner);
|
|
135
|
-
targetHook.mintPendingReservesFor(1, pending);
|
|
136
|
-
|
|
137
|
-
// Verify: reserve beneficiary got the reserves.
|
|
138
|
-
assertEq(targetHook.balanceOf(reserveBeneficiary), pending, "Reserve beneficiary should have all reserves");
|
|
139
|
-
|
|
140
|
-
// Verify: remaining supply is 1 (10 - 6 paid - 3 reserves = 1).
|
|
141
|
-
JB721Tier memory tier = targetHook.STORE().tierOf(address(targetHook), 1, false);
|
|
142
|
-
assertEq(tier.remainingSupply, 1, "Should have 1 remaining (10 - 6 - 3)");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// =========================================================================
|
|
146
|
-
// Test 3: Boundary — reserves exactly fill remaining supply after max paid mints
|
|
147
|
-
// =========================================================================
|
|
148
|
-
/// @notice With reserveFrequency=5, after 16 paid mints of 20 supply:
|
|
149
|
-
/// remaining=4, pending=ceil(16/5)=4. The 17th mint reverts (4 <= 1+4=5).
|
|
150
|
-
/// All 4 pending reserves are still fully mintable.
|
|
151
|
-
function test_M6_noMintWhenRemainingEqualsReserves() public {
|
|
152
|
-
defaultTierConfig.price = uint104(10);
|
|
153
|
-
defaultTierConfig.initialSupply = uint32(20);
|
|
154
|
-
defaultTierConfig.reserveFrequency = uint16(5);
|
|
155
|
-
defaultTierConfig.reserveBeneficiary = reserveBeneficiary;
|
|
156
|
-
|
|
157
|
-
ForTest_JB721TiersHook targetHook = _initializeForTestHook(1);
|
|
158
|
-
_mockTerminalAuth();
|
|
159
|
-
|
|
160
|
-
// Mint 16 paid NFTs in two batches to stay under gas limits.
|
|
161
|
-
_mintPaid(targetHook, 10);
|
|
162
|
-
_mintPaid(targetHook, 6);
|
|
163
|
-
|
|
164
|
-
// State: remaining=4, nonReserveMints=16, pending=ceil(16/5)=4.
|
|
165
|
-
uint256 pending = targetHook.STORE().numberOfPendingReservesFor(address(targetHook), 1);
|
|
166
|
-
assertEq(pending, 4, "Should have 4 pending reserves (ceil(16/5)=4)");
|
|
167
|
-
|
|
168
|
-
JB721Tier memory tier = targetHook.STORE().tierOf(address(targetHook), 1, false);
|
|
169
|
-
assertEq(tier.remainingSupply, 4, "Should have 4 remaining");
|
|
170
|
-
|
|
171
|
-
// 17th mint: after decrement remaining would be 3, but pending would be ceil(17/5)=4. 3 < 4 → reverts.
|
|
172
|
-
uint16[] memory oneMore = new uint16[](1);
|
|
173
|
-
oneMore[0] = 1;
|
|
174
|
-
JBAfterPayRecordedContext memory ctx = _buildPayContext(address(targetHook), 10, oneMore);
|
|
175
|
-
|
|
176
|
-
vm.prank(mockTerminalAddress);
|
|
177
|
-
vm.expectRevert(
|
|
178
|
-
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
179
|
-
);
|
|
180
|
-
targetHook.afterPayRecordedWith(ctx);
|
|
181
|
-
|
|
182
|
-
// But reserves should still be fully mintable — remaining(4) covers all pending(4).
|
|
183
|
-
vm.prank(owner);
|
|
184
|
-
targetHook.mintPendingReservesFor(1, pending);
|
|
185
|
-
assertEq(targetHook.balanceOf(reserveBeneficiary), 4, "All reserves fully minted");
|
|
186
|
-
|
|
187
|
-
// Final state: 0 remaining.
|
|
188
|
-
tier = targetHook.STORE().tierOf(address(targetHook), 1, false);
|
|
189
|
-
assertEq(tier.remainingSupply, 0, "Fully exhausted");
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// =========================================================================
|
|
193
|
-
// Test 4: No reserves — full supply mintable
|
|
194
|
-
// =========================================================================
|
|
195
|
-
/// @notice Without reserves, all NFTs in a tier should be mintable (no off-by-one).
|
|
196
|
-
function test_M6_noReserves_fullSupplyMintable() public {
|
|
197
|
-
defaultTierConfig.price = uint104(10);
|
|
198
|
-
defaultTierConfig.initialSupply = uint32(5);
|
|
199
|
-
defaultTierConfig.reserveFrequency = uint16(0);
|
|
200
|
-
defaultTierConfig.reserveBeneficiary = address(0);
|
|
201
|
-
|
|
202
|
-
ForTest_JB721TiersHook targetHook = _initializeForTestHook(1);
|
|
203
|
-
_mockTerminalAuth();
|
|
204
|
-
|
|
205
|
-
// Mint all 5 — should succeed since no reserves to protect.
|
|
206
|
-
_mintPaid(targetHook, 5);
|
|
207
|
-
|
|
208
|
-
JB721Tier memory tier = targetHook.STORE().tierOf(address(targetHook), 1, false);
|
|
209
|
-
assertEq(tier.remainingSupply, 0, "Fully minted");
|
|
210
|
-
assertEq(targetHook.balanceOf(beneficiary), 5, "Beneficiary has all 5");
|
|
211
|
-
|
|
212
|
-
// One more should revert (supply exhausted).
|
|
213
|
-
uint16[] memory oneMore = new uint16[](1);
|
|
214
|
-
oneMore[0] = 1;
|
|
215
|
-
JBAfterPayRecordedContext memory ctx = _buildPayContext(address(targetHook), 10, oneMore);
|
|
216
|
-
|
|
217
|
-
vm.prank(mockTerminalAddress);
|
|
218
|
-
vm.expectRevert();
|
|
219
|
-
targetHook.afterPayRecordedWith(ctx);
|
|
220
|
-
}
|
|
221
|
-
}
|