@bananapus/721-hook-v6 0.0.41 → 0.0.42
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/package.json +1 -1
- package/src/JB721TiersHookStore.sol +1 -4
- package/test/TestVotingUnitsLifecycle.t.sol +9 -9
- package/test/audit/CodexNemesisReserveSellout.t.sol +66 -0
- package/test/audit/ReserveSlotProtection.t.sol +273 -0
- package/test/unit/getters_constructor_Unit.t.sol +42 -42
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +60 -60
- package/test/unit/redeem_Unit.t.sol +36 -36
package/package.json
CHANGED
|
@@ -725,9 +725,6 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
725
725
|
|| reserveBeneficiaryOf({hook: hook, tierId: tierId}) == address(0)
|
|
726
726
|
) return 0;
|
|
727
727
|
|
|
728
|
-
// A sold-out tier cannot have mintable pending reserves — minting would underflow remainingSupply.
|
|
729
|
-
if (storedTier.remainingSupply == 0) return 0;
|
|
730
|
-
|
|
731
728
|
// The number of reserve NFTs which have already been minted from the tier.
|
|
732
729
|
uint256 numberOfReserveMints = numberOfReservesMintedFor[hook][tierId];
|
|
733
730
|
|
|
@@ -1232,7 +1229,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1232
1229
|
if (storedTier.remainingSupply == 0) revert JB721TiersHookStore_InsufficientSupplyRemaining(tierId);
|
|
1233
1230
|
|
|
1234
1231
|
// Mint the 721 — decrement remaining supply first so the reserve check below
|
|
1235
|
-
// sees the post-mint
|
|
1232
|
+
// sees the correct post-mint non-reserve-mint count.
|
|
1236
1233
|
unchecked {
|
|
1237
1234
|
// Keep a reference to its token ID.
|
|
1238
1235
|
tokenIds[i] = _generateTokenId({
|
|
@@ -275,15 +275,15 @@ contract TestVotingUnitsLifecycle is UnitTestSetup {
|
|
|
275
275
|
address(testHook),
|
|
276
276
|
2,
|
|
277
277
|
JBStored721Tier({
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
287
|
);
|
|
288
288
|
// Clear tier 2's custom voting units (so it falls back to price).
|
|
289
289
|
testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 2, 0);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
6
|
+
|
|
7
|
+
import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
|
|
8
|
+
import {JB721Tier} from "../../src/structs/JB721Tier.sol";
|
|
9
|
+
import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
|
|
10
|
+
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
11
|
+
|
|
12
|
+
contract CodexNemesisReserveSellout is Test {
|
|
13
|
+
JB721TiersHookStore internal store;
|
|
14
|
+
|
|
15
|
+
function setUp() public {
|
|
16
|
+
store = new JB721TiersHookStore();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// @notice Verifies that a paid mint cannot consume the last slot when it is reserved.
|
|
20
|
+
/// @dev Previously this test demonstrated the bug (paid mint succeeded). Now it confirms the fix.
|
|
21
|
+
function test_paidMintCannotConsumeReservedFinalSlot() public {
|
|
22
|
+
JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
|
|
23
|
+
tiers[0] = JB721TierConfig({
|
|
24
|
+
price: 1 ether,
|
|
25
|
+
initialSupply: 2,
|
|
26
|
+
votingUnits: 0,
|
|
27
|
+
reserveFrequency: 1,
|
|
28
|
+
reserveBeneficiary: address(0xBEEF),
|
|
29
|
+
encodedIPFSUri: bytes32(0),
|
|
30
|
+
category: 0,
|
|
31
|
+
discountPercent: 0,
|
|
32
|
+
flags: JB721TierConfigFlags({
|
|
33
|
+
allowOwnerMint: false,
|
|
34
|
+
useReserveBeneficiaryAsDefault: false,
|
|
35
|
+
transfersPausable: false,
|
|
36
|
+
useVotingUnits: false,
|
|
37
|
+
cantBeRemoved: false,
|
|
38
|
+
cantIncreaseDiscountPercent: false,
|
|
39
|
+
cantBuyWithCredits: false
|
|
40
|
+
}),
|
|
41
|
+
splitPercent: 0,
|
|
42
|
+
splits: new JBSplit[](0)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
store.recordAddTiers(tiers);
|
|
46
|
+
|
|
47
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
48
|
+
tierIds[0] = 1;
|
|
49
|
+
|
|
50
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
51
|
+
|
|
52
|
+
JB721Tier memory tier = store.tierOf(address(this), 1, false);
|
|
53
|
+
assertEq(tier.remainingSupply, 1, "one paid mint leaves one slot");
|
|
54
|
+
assertEq(store.numberOfPendingReservesFor(address(this), 1), 1, "one reserve is pending");
|
|
55
|
+
|
|
56
|
+
// With the fix, the second paid mint reverts because the remaining slot is reserved.
|
|
57
|
+
vm.expectRevert(
|
|
58
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
59
|
+
);
|
|
60
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
61
|
+
|
|
62
|
+
// Reserve beneficiary can still claim their entitled mint.
|
|
63
|
+
store.recordMintReservesFor({tierId: 1, count: 1});
|
|
64
|
+
assertEq(store.numberOfReservesMintedFor(address(this), 1), 1, "reserve beneficiary got their token");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
6
|
+
|
|
7
|
+
import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
|
|
8
|
+
import {JB721Tier} from "../../src/structs/JB721Tier.sol";
|
|
9
|
+
import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
|
|
10
|
+
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
11
|
+
|
|
12
|
+
/// @notice Tests that paid mints cannot consume slots reserved for the reserve beneficiary.
|
|
13
|
+
/// @dev Regression test for the vulnerability where `_numberOfPendingReservesFor` returns 0
|
|
14
|
+
/// when `remainingSupply == 0` (sold-out early-return), allowing the post-decrement guard
|
|
15
|
+
/// `0 < 0` to pass and permanently stealing reserved slots from the beneficiary.
|
|
16
|
+
/// The fix uses `_numberOfPendingReservesForMintGuard` which omits the sold-out early-return.
|
|
17
|
+
contract ReserveSlotProtection is Test {
|
|
18
|
+
JB721TiersHookStore internal store;
|
|
19
|
+
|
|
20
|
+
function setUp() public {
|
|
21
|
+
store = new JB721TiersHookStore();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// @notice Proves the fix: a paid mint that would consume the last reserved slot reverts.
|
|
25
|
+
/// Scenario: tier with initialSupply=2, reserveFrequency=1 (1 reserve per paid mint).
|
|
26
|
+
/// After 1 paid mint, 1 slot remains but it's reserved. Second paid mint must revert.
|
|
27
|
+
/// Post-decrement trace: remaining goes 2->1, pending=ceil(1/1)=1, check 1<1 = false -> ok.
|
|
28
|
+
/// Second mint: remaining goes 1->0, _numberOfPendingReservesForMintGuard returns 2
|
|
29
|
+
/// (ceil(2/1)-0=2), check 0<2 -> true -> reverts.
|
|
30
|
+
function test_paidMintRevertsWhenOnlyReservedSlotsRemain() public {
|
|
31
|
+
JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
|
|
32
|
+
tiers[0] = JB721TierConfig({
|
|
33
|
+
price: 1 ether,
|
|
34
|
+
initialSupply: 2,
|
|
35
|
+
votingUnits: 0,
|
|
36
|
+
reserveFrequency: 1,
|
|
37
|
+
reserveBeneficiary: address(0xBEEF),
|
|
38
|
+
encodedIPFSUri: bytes32(0),
|
|
39
|
+
category: 0,
|
|
40
|
+
discountPercent: 0,
|
|
41
|
+
flags: JB721TierConfigFlags({
|
|
42
|
+
allowOwnerMint: false,
|
|
43
|
+
useReserveBeneficiaryAsDefault: false,
|
|
44
|
+
transfersPausable: false,
|
|
45
|
+
useVotingUnits: false,
|
|
46
|
+
cantBeRemoved: false,
|
|
47
|
+
cantIncreaseDiscountPercent: false,
|
|
48
|
+
cantBuyWithCredits: false
|
|
49
|
+
}),
|
|
50
|
+
splitPercent: 0,
|
|
51
|
+
splits: new JBSplit[](0)
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
store.recordAddTiers(tiers);
|
|
55
|
+
|
|
56
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
57
|
+
tierIds[0] = 1;
|
|
58
|
+
|
|
59
|
+
// First paid mint succeeds — uses 1 of 2 slots, leaving 1 for the pending reserve.
|
|
60
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
61
|
+
|
|
62
|
+
// Verify state: 1 remaining supply, 1 pending reserve.
|
|
63
|
+
JB721Tier memory tier = store.tierOf(address(this), 1, false);
|
|
64
|
+
assertEq(tier.remainingSupply, 1, "should have 1 slot remaining after first mint");
|
|
65
|
+
assertEq(store.numberOfPendingReservesFor(address(this), 1), 1, "should have 1 pending reserve");
|
|
66
|
+
|
|
67
|
+
// Second paid mint MUST revert — the only remaining slot belongs to the reserve beneficiary.
|
|
68
|
+
vm.expectRevert(
|
|
69
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
70
|
+
);
|
|
71
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// @notice Proves that the reserve beneficiary can still mint their reserved slot after fix.
|
|
75
|
+
function test_reserveBeneficiaryCanMintAfterPaidSlotsExhausted() public {
|
|
76
|
+
JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
|
|
77
|
+
tiers[0] = JB721TierConfig({
|
|
78
|
+
price: 1 ether,
|
|
79
|
+
initialSupply: 2,
|
|
80
|
+
votingUnits: 0,
|
|
81
|
+
reserveFrequency: 1,
|
|
82
|
+
reserveBeneficiary: address(0xBEEF),
|
|
83
|
+
encodedIPFSUri: bytes32(0),
|
|
84
|
+
category: 0,
|
|
85
|
+
discountPercent: 0,
|
|
86
|
+
flags: JB721TierConfigFlags({
|
|
87
|
+
allowOwnerMint: false,
|
|
88
|
+
useReserveBeneficiaryAsDefault: false,
|
|
89
|
+
transfersPausable: false,
|
|
90
|
+
useVotingUnits: false,
|
|
91
|
+
cantBeRemoved: false,
|
|
92
|
+
cantIncreaseDiscountPercent: false,
|
|
93
|
+
cantBuyWithCredits: false
|
|
94
|
+
}),
|
|
95
|
+
splitPercent: 0,
|
|
96
|
+
splits: new JBSplit[](0)
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
store.recordAddTiers(tiers);
|
|
100
|
+
|
|
101
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
102
|
+
tierIds[0] = 1;
|
|
103
|
+
|
|
104
|
+
// First paid mint.
|
|
105
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
106
|
+
|
|
107
|
+
// Reserve beneficiary mints their entitled reserve.
|
|
108
|
+
uint256[] memory reserveTokenIds = store.recordMintReservesFor({tierId: 1, count: 1});
|
|
109
|
+
assertEq(reserveTokenIds.length, 1, "reserve beneficiary should get 1 token");
|
|
110
|
+
|
|
111
|
+
// After reserve mint, remaining supply is 0 and reserves are fulfilled.
|
|
112
|
+
JB721Tier memory tier = store.tierOf(address(this), 1, false);
|
|
113
|
+
assertEq(tier.remainingSupply, 0, "tier should be fully minted");
|
|
114
|
+
assertEq(store.numberOfReservesMintedFor(address(this), 1), 1, "1 reserve should be minted");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// @notice Tests a larger tier: initialSupply=10, reserveFrequency=2.
|
|
118
|
+
/// With frequency=2, every 2 paid mints earn 1 reserve (rounded up).
|
|
119
|
+
/// Post-decrement analysis for the 7th mint:
|
|
120
|
+
/// remaining goes 4->3, nonReserveMints=7, pending=ceil(7/2)-0=4.
|
|
121
|
+
/// Check: 3 < 4 -> true -> reverts. Correct!
|
|
122
|
+
/// So mints 1-6 succeed, mint 7 reverts.
|
|
123
|
+
function test_largerTierReserveProtection() public {
|
|
124
|
+
JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
|
|
125
|
+
tiers[0] = JB721TierConfig({
|
|
126
|
+
price: 0.1 ether,
|
|
127
|
+
initialSupply: 10,
|
|
128
|
+
votingUnits: 0,
|
|
129
|
+
reserveFrequency: 2,
|
|
130
|
+
reserveBeneficiary: address(0xCAFE),
|
|
131
|
+
encodedIPFSUri: bytes32(0),
|
|
132
|
+
category: 0,
|
|
133
|
+
discountPercent: 0,
|
|
134
|
+
flags: JB721TierConfigFlags({
|
|
135
|
+
allowOwnerMint: false,
|
|
136
|
+
useReserveBeneficiaryAsDefault: false,
|
|
137
|
+
transfersPausable: false,
|
|
138
|
+
useVotingUnits: false,
|
|
139
|
+
cantBeRemoved: false,
|
|
140
|
+
cantIncreaseDiscountPercent: false,
|
|
141
|
+
cantBuyWithCredits: false
|
|
142
|
+
}),
|
|
143
|
+
splitPercent: 0,
|
|
144
|
+
splits: new JBSplit[](0)
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
store.recordAddTiers(tiers);
|
|
148
|
+
|
|
149
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
150
|
+
tierIds[0] = 1;
|
|
151
|
+
|
|
152
|
+
// Mint 6 paid NFTs. After 6 mints: remaining=4, pending=ceil(6/2)=3.
|
|
153
|
+
for (uint256 i; i < 6; i++) {
|
|
154
|
+
store.recordMint({amount: 0.1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
JB721Tier memory tier = store.tierOf(address(this), 1, false);
|
|
158
|
+
assertEq(tier.remainingSupply, 4, "should have 4 remaining after 6 mints");
|
|
159
|
+
assertEq(store.numberOfPendingReservesFor(address(this), 1), 3, "should have 3 pending reserves");
|
|
160
|
+
|
|
161
|
+
// 7th paid mint should revert: after decrement, remaining=3, pending=ceil(7/2)=4. 3<4 -> reverts.
|
|
162
|
+
vm.expectRevert(
|
|
163
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
164
|
+
);
|
|
165
|
+
store.recordMint({amount: 0.1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// @notice Verifies that when reserves are minted between paid mints, paid mints can continue.
|
|
169
|
+
/// initialSupply=4, reserveFrequency=1.
|
|
170
|
+
function test_paidMintsResumeAfterReservesMinted() public {
|
|
171
|
+
JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
|
|
172
|
+
tiers[0] = JB721TierConfig({
|
|
173
|
+
price: 1 ether,
|
|
174
|
+
initialSupply: 4,
|
|
175
|
+
votingUnits: 0,
|
|
176
|
+
reserveFrequency: 1,
|
|
177
|
+
reserveBeneficiary: address(0xBEEF),
|
|
178
|
+
encodedIPFSUri: bytes32(0),
|
|
179
|
+
category: 0,
|
|
180
|
+
discountPercent: 0,
|
|
181
|
+
flags: JB721TierConfigFlags({
|
|
182
|
+
allowOwnerMint: false,
|
|
183
|
+
useReserveBeneficiaryAsDefault: false,
|
|
184
|
+
transfersPausable: false,
|
|
185
|
+
useVotingUnits: false,
|
|
186
|
+
cantBeRemoved: false,
|
|
187
|
+
cantIncreaseDiscountPercent: false,
|
|
188
|
+
cantBuyWithCredits: false
|
|
189
|
+
}),
|
|
190
|
+
splitPercent: 0,
|
|
191
|
+
splits: new JBSplit[](0)
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
store.recordAddTiers(tiers);
|
|
195
|
+
|
|
196
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
197
|
+
tierIds[0] = 1;
|
|
198
|
+
|
|
199
|
+
// Paid mint 1: remaining 4->3, nonReserveMints=1, pending=ceil(1/1)=1. Check: 3<1? No. OK.
|
|
200
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
201
|
+
|
|
202
|
+
// Paid mint 2: remaining 3->2, nonReserveMints=2, pending=ceil(2/1)=2. Check: 2<2? No. OK.
|
|
203
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
204
|
+
|
|
205
|
+
// Paid mint 3: remaining 2->1, nonReserveMints=3, pending=ceil(3/1)=3. Check: 1<3? Yes! Revert.
|
|
206
|
+
vm.expectRevert(
|
|
207
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
208
|
+
);
|
|
209
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
210
|
+
|
|
211
|
+
// Mint 1 reserve — frees up a slot. Now reservesMinted=1.
|
|
212
|
+
store.recordMintReservesFor({tierId: 1, count: 1});
|
|
213
|
+
|
|
214
|
+
// State: remaining=1, reservesMinted=1, nonReserveMints=2.
|
|
215
|
+
// Paid mint attempt: remaining 1->0, nonReserveMints=3, pending=ceil(3/1)-1=2. Check: 0<2? Yes! Revert.
|
|
216
|
+
vm.expectRevert(
|
|
217
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
218
|
+
);
|
|
219
|
+
store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
220
|
+
|
|
221
|
+
// Mint remaining reserve.
|
|
222
|
+
store.recordMintReservesFor({tierId: 1, count: 1});
|
|
223
|
+
|
|
224
|
+
// Tier fully minted — 0 remaining.
|
|
225
|
+
JB721Tier memory tier = store.tierOf(address(this), 1, false);
|
|
226
|
+
assertEq(tier.remainingSupply, 0, "tier should be fully minted");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// @notice Without reserves, all NFTs in a tier should be mintable (no off-by-one).
|
|
230
|
+
function test_noReservesFullSupplyMintable() public {
|
|
231
|
+
JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
|
|
232
|
+
tiers[0] = JB721TierConfig({
|
|
233
|
+
price: 0.1 ether,
|
|
234
|
+
initialSupply: 5,
|
|
235
|
+
votingUnits: 0,
|
|
236
|
+
reserveFrequency: 0,
|
|
237
|
+
reserveBeneficiary: address(0),
|
|
238
|
+
encodedIPFSUri: bytes32(0),
|
|
239
|
+
category: 0,
|
|
240
|
+
discountPercent: 0,
|
|
241
|
+
flags: JB721TierConfigFlags({
|
|
242
|
+
allowOwnerMint: false,
|
|
243
|
+
useReserveBeneficiaryAsDefault: false,
|
|
244
|
+
transfersPausable: false,
|
|
245
|
+
useVotingUnits: false,
|
|
246
|
+
cantBeRemoved: false,
|
|
247
|
+
cantIncreaseDiscountPercent: false,
|
|
248
|
+
cantBuyWithCredits: false
|
|
249
|
+
}),
|
|
250
|
+
splitPercent: 0,
|
|
251
|
+
splits: new JBSplit[](0)
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
store.recordAddTiers(tiers);
|
|
255
|
+
|
|
256
|
+
uint16[] memory tierIds = new uint16[](1);
|
|
257
|
+
tierIds[0] = 1;
|
|
258
|
+
|
|
259
|
+
// Mint all 5 — should succeed since no reserves to protect.
|
|
260
|
+
for (uint256 i; i < 5; i++) {
|
|
261
|
+
store.recordMint({amount: 0.1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
JB721Tier memory tier = store.tierOf(address(this), 1, false);
|
|
265
|
+
assertEq(tier.remainingSupply, 0, "fully minted");
|
|
266
|
+
|
|
267
|
+
// 6th mint should revert (supply exhausted).
|
|
268
|
+
vm.expectRevert(
|
|
269
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
|
|
270
|
+
);
|
|
271
|
+
store.recordMint({amount: 0.1 ether, tierIds: tierIds, isOwnerMint: false});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -181,15 +181,15 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
|
|
|
181
181
|
address(hook),
|
|
182
182
|
i + 1,
|
|
183
183
|
JBStored721Tier({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
price: uint104((i + 1) * 10),
|
|
185
|
+
remainingSupply: uint32(100 - (i + 1)),
|
|
186
|
+
initialSupply: uint32(100),
|
|
187
|
+
reserveFrequency: uint16(0),
|
|
188
|
+
category: uint24(100),
|
|
189
|
+
discountPercent: uint8(0),
|
|
190
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
|
|
191
|
+
splitPercent: 0
|
|
192
|
+
})
|
|
193
193
|
);
|
|
194
194
|
}
|
|
195
195
|
|
|
@@ -235,18 +235,18 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
|
|
|
235
235
|
address(hook),
|
|
236
236
|
i + 1,
|
|
237
237
|
JBStored721Tier({
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
price: uint104((i + 1) * 10),
|
|
239
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
240
|
+
remainingSupply: uint32(initialSupply - totalMinted),
|
|
241
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
242
|
+
initialSupply: uint32(initialSupply),
|
|
243
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
244
|
+
reserveFrequency: uint16(reserveFrequency),
|
|
245
|
+
category: uint24(100),
|
|
246
|
+
discountPercent: uint8(0),
|
|
247
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
|
|
248
|
+
splitPercent: 0
|
|
249
|
+
})
|
|
250
250
|
);
|
|
251
251
|
// Manually set the number of reserve mints for each tier.
|
|
252
252
|
hook.test_store().ForTest_setReservesMintedFor(address(hook), i + 1, reservedMinted);
|
|
@@ -280,15 +280,15 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
|
|
|
280
280
|
address(hook),
|
|
281
281
|
1,
|
|
282
282
|
JBStored721Tier({
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
283
|
+
price: uint104(10),
|
|
284
|
+
remainingSupply: uint32(10),
|
|
285
|
+
initialSupply: uint32(20),
|
|
286
|
+
reserveFrequency: uint16(100),
|
|
287
|
+
category: uint24(100),
|
|
288
|
+
discountPercent: uint8(0),
|
|
289
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, true, false, false, false),
|
|
290
|
+
splitPercent: 0
|
|
291
|
+
})
|
|
292
292
|
);
|
|
293
293
|
// Clear the voting units mapping for tier 1 (ForTest_setTier only overwrites the packed struct).
|
|
294
294
|
hook.test_store().ForTest_setTierVotingUnits(address(hook), 1, 0);
|
|
@@ -438,18 +438,18 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
|
|
|
438
438
|
address(hook),
|
|
439
439
|
i,
|
|
440
440
|
JBStored721Tier({
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
441
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
442
|
+
price: uint104(i * 10),
|
|
443
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
444
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
445
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
446
|
+
initialSupply: uint32(10 * i),
|
|
447
|
+
reserveFrequency: uint16(0),
|
|
448
|
+
category: uint24(100),
|
|
449
|
+
discountPercent: uint8(0),
|
|
450
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
|
|
451
|
+
splitPercent: 0
|
|
452
|
+
})
|
|
453
453
|
);
|
|
454
454
|
// Calculate the theoretical weight for the current tier. 10 the price multiplier.
|
|
455
455
|
theoreticalWeight += (10 * i - 5 * i) * i * 10;
|
|
@@ -27,18 +27,18 @@ contract Test_mintFor_mintReservesFor_Unit is UnitTestSetup {
|
|
|
27
27
|
address(hook),
|
|
28
28
|
i + 1,
|
|
29
29
|
JBStored721Tier({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
price: uint104((i + 1) * 10),
|
|
31
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
32
|
+
remainingSupply: uint32(initialSupply - totalMinted),
|
|
33
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
34
|
+
initialSupply: uint32(initialSupply),
|
|
35
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
36
|
+
reserveFrequency: uint16(reserveFrequency),
|
|
37
|
+
category: uint24(100),
|
|
38
|
+
discountPercent: uint8(0),
|
|
39
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, true, false, false, false),
|
|
40
|
+
splitPercent: 0
|
|
41
|
+
})
|
|
42
42
|
);
|
|
43
43
|
hook.test_store().ForTest_setReservesMintedFor(address(hook), i + 1, reservedMinted);
|
|
44
44
|
}
|
|
@@ -77,18 +77,18 @@ contract Test_mintFor_mintReservesFor_Unit is UnitTestSetup {
|
|
|
77
77
|
address(hook),
|
|
78
78
|
1,
|
|
79
79
|
JBStored721Tier({
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
price: uint104(10),
|
|
81
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
82
|
+
remainingSupply: uint32(initialSupply),
|
|
83
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
84
|
+
initialSupply: uint32(initialSupply),
|
|
85
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
86
|
+
reserveFrequency: uint16(reserveFrequency),
|
|
87
|
+
category: uint24(100),
|
|
88
|
+
discountPercent: uint8(0),
|
|
89
|
+
packedBools: hook.test_store().ForTest_packBools(true, false, true, false, false, false),
|
|
90
|
+
splitPercent: 0
|
|
91
|
+
})
|
|
92
92
|
);
|
|
93
93
|
|
|
94
94
|
// Mint the initial tiers.
|
|
@@ -184,18 +184,18 @@ contract Test_mintFor_mintReservesFor_Unit is UnitTestSetup {
|
|
|
184
184
|
address(hook),
|
|
185
185
|
i + 1,
|
|
186
186
|
JBStored721Tier({
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
187
|
+
price: uint104((i + 1) * 10),
|
|
188
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
189
|
+
remainingSupply: uint32(initialSupply - totalMinted),
|
|
190
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
191
|
+
initialSupply: uint32(initialSupply),
|
|
192
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
193
|
+
reserveFrequency: uint16(reserveFrequency),
|
|
194
|
+
category: uint24(100),
|
|
195
|
+
discountPercent: uint8(0),
|
|
196
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, true, false, false, false),
|
|
197
|
+
splitPercent: 0
|
|
198
|
+
})
|
|
199
199
|
);
|
|
200
200
|
|
|
201
201
|
// Set the number of reserve NFTs already minted for the tier.
|
|
@@ -286,18 +286,18 @@ contract Test_mintFor_mintReservesFor_Unit is UnitTestSetup {
|
|
|
286
286
|
address(hook),
|
|
287
287
|
i + 1,
|
|
288
288
|
JBStored721Tier({
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
289
|
+
price: uint104((i + 1) * 10),
|
|
290
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
291
|
+
remainingSupply: uint32(initialSupply - totalMinted),
|
|
292
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
293
|
+
initialSupply: uint32(initialSupply),
|
|
294
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
295
|
+
reserveFrequency: uint16(reserveFrequency),
|
|
296
|
+
category: uint24(100),
|
|
297
|
+
discountPercent: uint8(0),
|
|
298
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, true, false, false, false),
|
|
299
|
+
splitPercent: 0
|
|
300
|
+
})
|
|
301
301
|
);
|
|
302
302
|
hook.test_store().ForTest_setReservesMintedFor(address(hook), i + 1, reservedMinted);
|
|
303
303
|
}
|
|
@@ -327,18 +327,18 @@ contract Test_mintFor_mintReservesFor_Unit is UnitTestSetup {
|
|
|
327
327
|
address(hook),
|
|
328
328
|
i + 1,
|
|
329
329
|
JBStored721Tier({
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
330
|
+
price: uint104((i + 1) * 10),
|
|
331
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
332
|
+
remainingSupply: uint32(initialSupply - totalMinted),
|
|
333
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
334
|
+
initialSupply: uint32(initialSupply),
|
|
335
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
336
|
+
reserveFrequency: uint16(reserveFrequency),
|
|
337
|
+
category: uint24(100),
|
|
338
|
+
discountPercent: uint8(0),
|
|
339
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, true, false, false, false),
|
|
340
|
+
splitPercent: 0
|
|
341
|
+
})
|
|
342
342
|
);
|
|
343
343
|
hook.test_store().ForTest_setReservesMintedFor(address(hook), i + 1, reservedMinted);
|
|
344
344
|
}
|
|
@@ -19,18 +19,18 @@ contract Test_cashOut_Unit is UnitTestSetup {
|
|
|
19
19
|
address(hook),
|
|
20
20
|
i,
|
|
21
21
|
JBStored721Tier({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
23
|
+
price: uint104(i * 10),
|
|
24
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
25
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
26
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
27
|
+
initialSupply: uint32(10 * i),
|
|
28
|
+
reserveFrequency: uint16(0),
|
|
29
|
+
category: uint24(100),
|
|
30
|
+
discountPercent: uint8(0),
|
|
31
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
|
|
32
|
+
splitPercent: 0
|
|
33
|
+
})
|
|
34
34
|
);
|
|
35
35
|
totalWeight += (10 * i - 5 * i) * i * 10;
|
|
36
36
|
}
|
|
@@ -94,18 +94,18 @@ contract Test_cashOut_Unit is UnitTestSetup {
|
|
|
94
94
|
address(hook),
|
|
95
95
|
i,
|
|
96
96
|
JBStored721Tier({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
98
|
+
price: uint104(i * 10),
|
|
99
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
100
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
101
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
102
|
+
initialSupply: uint32(10 * i),
|
|
103
|
+
reserveFrequency: uint16(0),
|
|
104
|
+
category: uint24(100),
|
|
105
|
+
discountPercent: uint8(0),
|
|
106
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
|
|
107
|
+
splitPercent: 0
|
|
108
|
+
})
|
|
109
109
|
);
|
|
110
110
|
totalWeight += (10 * i - 5 * i) * i * 10;
|
|
111
111
|
}
|
|
@@ -155,18 +155,18 @@ contract Test_cashOut_Unit is UnitTestSetup {
|
|
|
155
155
|
address(hook),
|
|
156
156
|
i,
|
|
157
157
|
JBStored721Tier({
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
158
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
159
|
+
price: uint104(i * 10),
|
|
160
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
161
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
162
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
163
|
+
initialSupply: uint32(10 * i),
|
|
164
|
+
reserveFrequency: uint16(0),
|
|
165
|
+
category: uint24(100),
|
|
166
|
+
discountPercent: uint8(0),
|
|
167
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
|
|
168
|
+
splitPercent: 0
|
|
169
|
+
})
|
|
170
170
|
);
|
|
171
171
|
totalWeight += (10 * i - 5 * i) * i * 10;
|
|
172
172
|
}
|