@bananapus/721-hook-v6 0.0.1
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/.gas-snapshot +152 -0
- package/LICENSE +21 -0
- package/README.md +253 -0
- package/SKILLS.md +140 -0
- package/docs/book.css +13 -0
- package/docs/book.toml +12 -0
- package/docs/solidity.min.js +74 -0
- package/docs/src/README.md +253 -0
- package/docs/src/SUMMARY.md +38 -0
- package/docs/src/src/JB721TiersHook.sol/contract.JB721TiersHook.md +645 -0
- package/docs/src/src/JB721TiersHookDeployer.sol/contract.JB721TiersHookDeployer.md +99 -0
- package/docs/src/src/JB721TiersHookProjectDeployer.sol/contract.JB721TiersHookProjectDeployer.md +288 -0
- package/docs/src/src/JB721TiersHookStore.sol/contract.JB721TiersHookStore.md +1096 -0
- package/docs/src/src/README.md +11 -0
- package/docs/src/src/abstract/ERC721.sol/abstract.ERC721.md +430 -0
- package/docs/src/src/abstract/JB721Hook.sol/abstract.JB721Hook.md +309 -0
- package/docs/src/src/abstract/README.md +5 -0
- package/docs/src/src/interfaces/IJB721Hook.sol/interface.IJB721Hook.md +29 -0
- package/docs/src/src/interfaces/IJB721TiersHook.sol/interface.IJB721TiersHook.md +203 -0
- package/docs/src/src/interfaces/IJB721TiersHookDeployer.sol/interface.IJB721TiersHookDeployer.md +25 -0
- package/docs/src/src/interfaces/IJB721TiersHookProjectDeployer.sol/interface.IJB721TiersHookProjectDeployer.md +64 -0
- package/docs/src/src/interfaces/IJB721TiersHookStore.sol/interface.IJB721TiersHookStore.md +265 -0
- package/docs/src/src/interfaces/IJB721TokenUriResolver.sol/interface.IJB721TokenUriResolver.md +12 -0
- package/docs/src/src/interfaces/README.md +9 -0
- package/docs/src/src/libraries/JB721Constants.sol/library.JB721Constants.md +14 -0
- package/docs/src/src/libraries/JB721TiersRulesetMetadataResolver.sol/library.JB721TiersRulesetMetadataResolver.md +68 -0
- package/docs/src/src/libraries/JBBitmap.sol/library.JBBitmap.md +82 -0
- package/docs/src/src/libraries/JBIpfsDecoder.sol/library.JBIpfsDecoder.md +61 -0
- package/docs/src/src/libraries/README.md +7 -0
- package/docs/src/src/structs/JB721InitTiersConfig.sol/struct.JB721InitTiersConfig.md +27 -0
- package/docs/src/src/structs/JB721Tier.sol/struct.JB721Tier.md +59 -0
- package/docs/src/src/structs/JB721TierConfig.sol/struct.JB721TierConfig.md +60 -0
- package/docs/src/src/structs/JB721TiersHookFlags.sol/struct.JB721TiersHookFlags.md +26 -0
- package/docs/src/src/structs/JB721TiersMintReservesConfig.sol/struct.JB721TiersMintReservesConfig.md +16 -0
- package/docs/src/src/structs/JB721TiersRulesetMetadata.sol/struct.JB721TiersRulesetMetadata.md +20 -0
- package/docs/src/src/structs/JB721TiersSetDiscountPercentConfig.sol/struct.JB721TiersSetDiscountPercentConfig.md +16 -0
- package/docs/src/src/structs/JBBitmapWord.sol/struct.JBBitmapWord.md +19 -0
- package/docs/src/src/structs/JBDeploy721TiersHookConfig.sol/struct.JBDeploy721TiersHookConfig.md +34 -0
- package/docs/src/src/structs/JBLaunchProjectConfig.sol/struct.JBLaunchProjectConfig.md +23 -0
- package/docs/src/src/structs/JBLaunchRulesetsConfig.sol/struct.JBLaunchRulesetsConfig.md +22 -0
- package/docs/src/src/structs/JBPayDataHookRulesetConfig.sol/struct.JBPayDataHookRulesetConfig.md +51 -0
- package/docs/src/src/structs/JBPayDataHookRulesetMetadata.sol/struct.JBPayDataHookRulesetMetadata.md +66 -0
- package/docs/src/src/structs/JBQueueRulesetsConfig.sol/struct.JBQueueRulesetsConfig.md +21 -0
- package/docs/src/src/structs/JBStored721Tier.sol/struct.JBStored721Tier.md +42 -0
- package/docs/src/src/structs/README.md +18 -0
- package/foundry.lock +11 -0
- package/foundry.toml +22 -0
- package/package.json +31 -0
- package/remappings.txt +1 -0
- package/script/Deploy.s.sol +140 -0
- package/script/helpers/Hook721DeploymentLib.sol +81 -0
- package/slither-ci.config.json +10 -0
- package/sphinx.lock +476 -0
- package/src/JB721TiersHook.sol +765 -0
- package/src/JB721TiersHookDeployer.sol +114 -0
- package/src/JB721TiersHookProjectDeployer.sol +413 -0
- package/src/JB721TiersHookStore.sol +1195 -0
- package/src/abstract/ERC721.sol +484 -0
- package/src/abstract/JB721Hook.sol +279 -0
- package/src/interfaces/IJB721Hook.sol +21 -0
- package/src/interfaces/IJB721TiersHook.sol +135 -0
- package/src/interfaces/IJB721TiersHookDeployer.sol +22 -0
- package/src/interfaces/IJB721TiersHookProjectDeployer.sol +76 -0
- package/src/interfaces/IJB721TiersHookStore.sol +220 -0
- package/src/interfaces/IJB721TokenUriResolver.sol +10 -0
- package/src/libraries/JB721Constants.sol +7 -0
- package/src/libraries/JB721TiersRulesetMetadataResolver.sol +44 -0
- package/src/libraries/JBBitmap.sol +57 -0
- package/src/libraries/JBIpfsDecoder.sol +95 -0
- package/src/structs/JB721InitTiersConfig.sol +20 -0
- package/src/structs/JB721Tier.sol +39 -0
- package/src/structs/JB721TierConfig.sol +40 -0
- package/src/structs/JB721TiersHookFlags.sol +17 -0
- package/src/structs/JB721TiersMintReservesConfig.sol +9 -0
- package/src/structs/JB721TiersRulesetMetadata.sol +12 -0
- package/src/structs/JB721TiersSetDiscountPercentConfig.sol +9 -0
- package/src/structs/JBBitmapWord.sol +11 -0
- package/src/structs/JBDeploy721TiersHookConfig.sol +25 -0
- package/src/structs/JBLaunchProjectConfig.sol +18 -0
- package/src/structs/JBLaunchRulesetsConfig.sol +17 -0
- package/src/structs/JBPayDataHookRulesetConfig.sol +44 -0
- package/src/structs/JBPayDataHookRulesetMetadata.sol +46 -0
- package/src/structs/JBQueueRulesetsConfig.sol +13 -0
- package/src/structs/JBStored721Tier.sol +24 -0
- package/test/721HookAttacks.t.sol +396 -0
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +944 -0
- package/test/invariants/TierLifecycleInvariant.t.sol +187 -0
- package/test/invariants/TieredHookStoreInvariant.t.sol +81 -0
- package/test/invariants/handlers/TierLifecycleHandler.sol +262 -0
- package/test/invariants/handlers/TierStoreHandler.sol +155 -0
- package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +141 -0
- package/test/unit/JBBitmap.t.sol +169 -0
- package/test/unit/JBIpfsDecoder.t.sol +131 -0
- package/test/unit/M6_TierSupplyCheck.t.sol +220 -0
- package/test/unit/adjustTier_Unit.t.sol +1740 -0
- package/test/unit/deployer_Unit.t.sol +103 -0
- package/test/unit/getters_constructor_Unit.t.sol +548 -0
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +443 -0
- package/test/unit/pay_Unit.t.sol +1537 -0
- package/test/unit/redeem_Unit.t.sol +459 -0
|
@@ -0,0 +1,1740 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import "../utils/UnitTestSetup.sol";
|
|
5
|
+
|
|
6
|
+
contract Test_adjustTier_Unit is UnitTestSetup {
|
|
7
|
+
using stdStorage for StdStorage;
|
|
8
|
+
|
|
9
|
+
function test_adjustTiers_removeTiersMultipleTimes(
|
|
10
|
+
uint256 initialNumberOfTiers,
|
|
11
|
+
uint256 numberOfTiersToAdd,
|
|
12
|
+
uint256 seed
|
|
13
|
+
)
|
|
14
|
+
public
|
|
15
|
+
{
|
|
16
|
+
// Include adding X new tiers with 0 current tiers.
|
|
17
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 10);
|
|
18
|
+
|
|
19
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 4, 14);
|
|
20
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
21
|
+
|
|
22
|
+
// Sort tiers in ascending order.
|
|
23
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
24
|
+
|
|
25
|
+
// Initialize the hook with default tiers.
|
|
26
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
27
|
+
|
|
28
|
+
// Create the configs for the new tiers to add.
|
|
29
|
+
(JB721TierConfig[] memory tierConfigs,) =
|
|
30
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
31
|
+
|
|
32
|
+
// Remove 2 tiers and add the new ones. `_addDeleteTiers` calls `adjustTiers`.
|
|
33
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
34
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 2, tierConfigs);
|
|
35
|
+
|
|
36
|
+
// Remove another 2 tiers but don't add any new ones.
|
|
37
|
+
_addDeleteTiers(hook, tiersLeft, 2, new JB721TierConfig[](0));
|
|
38
|
+
|
|
39
|
+
// Fecth a maximum of 100 tiers (will downsize).
|
|
40
|
+
JB721Tier[] memory storedTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
41
|
+
// If the tiers have same category, then the the tiers added last will have a lower index in the array.
|
|
42
|
+
// Otherwise, the tiers would be sorted by categories.
|
|
43
|
+
for (uint256 i = 1; i < storedTiers.length; i++) {
|
|
44
|
+
if (storedTiers[i - 1].category == storedTiers[i].category) {
|
|
45
|
+
assertGt(storedTiers[i - 1].id, storedTiers[i].id, "Sorting error (if same category).");
|
|
46
|
+
} else {
|
|
47
|
+
assertLt(storedTiers[i - 1].category, storedTiers[i].category, "Sorting error (if different category).");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function test_tiersOf_recentlyAddedTiersFetchedFirstWhenSortedAfterTiersCleaned(
|
|
53
|
+
uint256 initialNumberOfTiers,
|
|
54
|
+
uint256 numberOfTiersToAdd,
|
|
55
|
+
uint256 seed
|
|
56
|
+
)
|
|
57
|
+
public
|
|
58
|
+
{
|
|
59
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 3, 14);
|
|
60
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 1, 15);
|
|
61
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
62
|
+
|
|
63
|
+
// Sort tiers in ascending order.
|
|
64
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
65
|
+
|
|
66
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
67
|
+
|
|
68
|
+
// Create the new tiers to add.
|
|
69
|
+
uint256 currentNumberOfTiers = initialNumberOfTiers;
|
|
70
|
+
|
|
71
|
+
(JB721TierConfig[] memory tierConfigsToAdd,) =
|
|
72
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
73
|
+
|
|
74
|
+
currentNumberOfTiers = _addDeleteTiers(hook, currentNumberOfTiers, 0, tierConfigsToAdd);
|
|
75
|
+
currentNumberOfTiers = _addDeleteTiers(hook, currentNumberOfTiers, 2, tierConfigsToAdd);
|
|
76
|
+
|
|
77
|
+
JB721Tier[] memory storedTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
78
|
+
|
|
79
|
+
assertEq(storedTiers.length, currentNumberOfTiers, "Length mismatch.");
|
|
80
|
+
// If the tiers have same category, then the tiers added last will have a lower index in the array.
|
|
81
|
+
// Otherwise, the tiers would be sorted by categories.
|
|
82
|
+
for (uint256 i = 1; i < storedTiers.length; i++) {
|
|
83
|
+
if (storedTiers[i - 1].category == storedTiers[i].category) {
|
|
84
|
+
assertGt(storedTiers[i - 1].id, storedTiers[i].id, "Sorting error (if same category).");
|
|
85
|
+
} else {
|
|
86
|
+
assertLt(storedTiers[i - 1].category, storedTiers[i].category, "Sorting error (if different category).");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function test_tiersOf_recentlyAddedTiersFetchedFirstWhenSorted(
|
|
92
|
+
uint256 initialNumberOfTiers,
|
|
93
|
+
uint256 numberOfTiersToAdd,
|
|
94
|
+
uint256 seed
|
|
95
|
+
)
|
|
96
|
+
public
|
|
97
|
+
{
|
|
98
|
+
// Include adding X new tiers with 0 current tiers.
|
|
99
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 10);
|
|
100
|
+
|
|
101
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 4, 14);
|
|
102
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
103
|
+
|
|
104
|
+
// Sort tiers in ascending order.
|
|
105
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
106
|
+
|
|
107
|
+
// Initialize the hook with default tiers.
|
|
108
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
109
|
+
|
|
110
|
+
// Create the new tiers to add.
|
|
111
|
+
(JB721TierConfig[] memory tierConfigs,) =
|
|
112
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
113
|
+
|
|
114
|
+
// Add the new tiers.
|
|
115
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
116
|
+
|
|
117
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigs);
|
|
118
|
+
|
|
119
|
+
JB721Tier[] memory storedTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
120
|
+
// If the tiers have same category, then the tiers added last will have a lower index in the array.
|
|
121
|
+
// Otherwise, the tiers would be sorted by categories.
|
|
122
|
+
for (uint256 i = 1; i < storedTiers.length; i++) {
|
|
123
|
+
if (storedTiers[i - 1].category == storedTiers[i].category) {
|
|
124
|
+
assertGt(storedTiers[i - 1].id, storedTiers[i].id, "Sorting error (if same category).");
|
|
125
|
+
} else {
|
|
126
|
+
assertLt(storedTiers[i - 1].category, storedTiers[i].category, "Sorting error (if different category).");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function test_adjustTiers_addNewTiersWithNonSequentialCategories(
|
|
132
|
+
uint256 initialNumberOfTiers,
|
|
133
|
+
uint256 numberOfTiersToAdd,
|
|
134
|
+
uint256 seed
|
|
135
|
+
)
|
|
136
|
+
public
|
|
137
|
+
{
|
|
138
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 2, 10);
|
|
139
|
+
|
|
140
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 4, 14);
|
|
141
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
142
|
+
|
|
143
|
+
// Sort tiers in ascending order.
|
|
144
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
145
|
+
|
|
146
|
+
// Initialize the hook with default tiers.
|
|
147
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
148
|
+
|
|
149
|
+
JB721Tier[] memory defaultStoredTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
150
|
+
|
|
151
|
+
// Create the new tiers to add.
|
|
152
|
+
(JB721TierConfig[] memory tierConfigsToAdd, JB721Tier[] memory tiersToAdd) =
|
|
153
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd, 2);
|
|
154
|
+
|
|
155
|
+
// Add the new tiers.
|
|
156
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
157
|
+
|
|
158
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigsToAdd);
|
|
159
|
+
|
|
160
|
+
JB721Tier[] memory storedTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
161
|
+
|
|
162
|
+
// Check: was the expected number of tiers returned?
|
|
163
|
+
assertEq(storedTiers.length, tiersLeft, "Length mismatch.");
|
|
164
|
+
|
|
165
|
+
// Check: are all tiers in the new tiers (unsorted)?
|
|
166
|
+
assertTrue(_isIn(defaultStoredTiers, storedTiers), "Original tiers not stored."); // Original tiers
|
|
167
|
+
assertTrue(_isIn(tiersToAdd, storedTiers), "Added tiers not stored."); // New tiers
|
|
168
|
+
|
|
169
|
+
// Check: are all the tiers sorted?
|
|
170
|
+
for (uint256 i = 1; i < storedTiers.length; i++) {
|
|
171
|
+
assertLt(storedTiers[i - 1].category, storedTiers[i].category, "Sorting error.");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function test_adjustTiers_addNewTiers(
|
|
176
|
+
uint256 initialNumberOfTiers,
|
|
177
|
+
uint256 numberOfTiersToAdd,
|
|
178
|
+
uint256 seed
|
|
179
|
+
)
|
|
180
|
+
public
|
|
181
|
+
{
|
|
182
|
+
// Include adding X new tiers with 0 current tiers.
|
|
183
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 10);
|
|
184
|
+
|
|
185
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 4, 14);
|
|
186
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
187
|
+
|
|
188
|
+
// Sort tiers in ascending order.
|
|
189
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
190
|
+
|
|
191
|
+
// Initialize the hook with default tiers.
|
|
192
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
193
|
+
|
|
194
|
+
JB721Tier[] memory intialTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
195
|
+
|
|
196
|
+
// Create the new tiers to add.
|
|
197
|
+
(JB721TierConfig[] memory tierConfigs, JB721Tier[] memory tiersAdded) =
|
|
198
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
199
|
+
|
|
200
|
+
// Add the new tiers.
|
|
201
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
202
|
+
|
|
203
|
+
_addDeleteTiers(hook, tiersLeft, 0, tierConfigs);
|
|
204
|
+
|
|
205
|
+
JB721Tier[] memory storedTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
206
|
+
|
|
207
|
+
// Check: was the expected number of tiers returned?
|
|
208
|
+
assertEq(storedTiers.length, intialTiers.length + tiersAdded.length, "Length mismatch.");
|
|
209
|
+
|
|
210
|
+
// Check: are all tiers in the new tiers (unsorted)?
|
|
211
|
+
assertTrue(_isIn(intialTiers, storedTiers), "original tiers not found"); // Original tiers
|
|
212
|
+
assertTrue(_isIn(tiersAdded, storedTiers), "new tiers not found"); // New tiers
|
|
213
|
+
|
|
214
|
+
// Check: are all the tiers sorted?
|
|
215
|
+
for (uint256 i = 1; i < storedTiers.length; i++) {
|
|
216
|
+
assertLe(storedTiers[i - 1].category, storedTiers[i].category, "Sorting error");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function test_adjustTiers_withSameCategoryMultipleTimes(
|
|
221
|
+
uint256 initialNumberOfTiers,
|
|
222
|
+
uint256 numberOfTiersToAdd,
|
|
223
|
+
uint256 seed
|
|
224
|
+
)
|
|
225
|
+
public
|
|
226
|
+
{
|
|
227
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 2, 10);
|
|
228
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 4, 14);
|
|
229
|
+
|
|
230
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
231
|
+
|
|
232
|
+
// Sort tiers in ascending order.
|
|
233
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
234
|
+
|
|
235
|
+
// Initialize the hook with default tiers of a given category.
|
|
236
|
+
defaultTierConfig.category = 100;
|
|
237
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
238
|
+
|
|
239
|
+
// Create new tiers to add with a new category.
|
|
240
|
+
defaultTierConfig.category = 101;
|
|
241
|
+
(JB721TierConfig[] memory tierConfigsToAdd, JB721Tier[] memory tiersToAdd) =
|
|
242
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
243
|
+
|
|
244
|
+
// Add the new tiers.
|
|
245
|
+
_addDeleteTiers(hook, 0, 0, tierConfigsToAdd);
|
|
246
|
+
|
|
247
|
+
(tierConfigsToAdd, tiersToAdd) = _createTiers(defaultTierConfig, numberOfTiersToAdd);
|
|
248
|
+
|
|
249
|
+
_addDeleteTiers(hook, 0, 0, tierConfigsToAdd);
|
|
250
|
+
|
|
251
|
+
// Check: are all tiers stored?
|
|
252
|
+
JB721Tier[] memory storedTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
253
|
+
assertEq(storedTiers.length, initialNumberOfTiers + pricesForTiersToAdd.length * 2);
|
|
254
|
+
|
|
255
|
+
// Check: are all tiers in the new tiers (unsorted)?
|
|
256
|
+
uint256[] memory categories = new uint256[](1);
|
|
257
|
+
categories[0] = 101;
|
|
258
|
+
|
|
259
|
+
JB721Tier[] memory stored101Tiers =
|
|
260
|
+
hook.STORE().tiersOf(address(hook), categories, false, 0, pricesForTiersToAdd.length * 2);
|
|
261
|
+
|
|
262
|
+
assertEq(stored101Tiers.length, pricesForTiersToAdd.length * 2);
|
|
263
|
+
|
|
264
|
+
// Check: are all the tiers in the initial tiers?
|
|
265
|
+
categories[0] = 100;
|
|
266
|
+
JB721Tier[] memory stored100Tiers = hook.STORE().tiersOf(address(hook), categories, false, 0, 100);
|
|
267
|
+
assertEq(stored100Tiers.length, initialNumberOfTiers);
|
|
268
|
+
|
|
269
|
+
// Check: are the tiers sorted correctly?
|
|
270
|
+
for (uint256 i; i < pricesForTiersToAdd.length; i++) {
|
|
271
|
+
assertGt(stored101Tiers[i].id, stored101Tiers[i + pricesForTiersToAdd.length].id);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function test_adjustTiers_withDifferentCategories(
|
|
276
|
+
uint256 initialNumberOfTiers,
|
|
277
|
+
uint256 numberOfTiersToAdd,
|
|
278
|
+
uint256 seed
|
|
279
|
+
)
|
|
280
|
+
public
|
|
281
|
+
{
|
|
282
|
+
// Include adding X new tiers with 0 current tiers.
|
|
283
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 1, 10);
|
|
284
|
+
|
|
285
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 1, 10);
|
|
286
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
287
|
+
|
|
288
|
+
// Sort tiers in ascending order.
|
|
289
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
290
|
+
|
|
291
|
+
// Initialize the hook with default tiers.
|
|
292
|
+
defaultTierConfig.category = 100;
|
|
293
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
294
|
+
|
|
295
|
+
// Create new tiers to add.
|
|
296
|
+
defaultTierConfig.category = 101;
|
|
297
|
+
(JB721TierConfig[] memory tierConfigs, JB721Tier[] memory tiersAdded) =
|
|
298
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
299
|
+
|
|
300
|
+
// Add the new tiers.
|
|
301
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
302
|
+
|
|
303
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigs);
|
|
304
|
+
|
|
305
|
+
defaultTierConfig.category = 102;
|
|
306
|
+
(tierConfigs, tiersAdded) =
|
|
307
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
308
|
+
|
|
309
|
+
// Add the new tiers.
|
|
310
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigs);
|
|
311
|
+
|
|
312
|
+
JB721Tier[] memory allStoredTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, 100);
|
|
313
|
+
|
|
314
|
+
uint256[] memory categories = new uint256[](1);
|
|
315
|
+
categories[0] = 102;
|
|
316
|
+
JB721Tier[] memory stored102Tiers = hook.STORE().tiersOf(address(hook), categories, false, 0, 100);
|
|
317
|
+
|
|
318
|
+
// Check: does the number of stored 102 tiers match the number of tiers that were added?
|
|
319
|
+
assertEq(stored102Tiers.length, pricesForTiersToAdd.length);
|
|
320
|
+
|
|
321
|
+
// Ensure that each stored 102 tier has category `102`.
|
|
322
|
+
for (uint256 i = 0; i < stored102Tiers.length; i++) {
|
|
323
|
+
assertEq(stored102Tiers[i].category, uint8(102));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
categories[0] = 101;
|
|
327
|
+
|
|
328
|
+
JB721Tier[] memory stored101Tiers = hook.STORE().tiersOf(address(hook), categories, false, 0, 100);
|
|
329
|
+
|
|
330
|
+
// Check: does the number of stored 101 tiers match the number of tiers that were added?
|
|
331
|
+
assertEq(stored101Tiers.length, pricesForTiersToAdd.length);
|
|
332
|
+
|
|
333
|
+
// Check: does each stored 101 tier have category `101`?
|
|
334
|
+
for (uint256 i = 0; i < stored101Tiers.length; i++) {
|
|
335
|
+
assertEq(stored101Tiers[i].category, uint8(101));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Ensure that the tiers are sorted.
|
|
339
|
+
for (uint256 i = 1; i < initialNumberOfTiers + pricesForTiersToAdd.length * 2; i++) {
|
|
340
|
+
assertGt(allStoredTiers[i].id, allStoredTiers[i - 1].id);
|
|
341
|
+
assertLe(allStoredTiers[i - 1].category, allStoredTiers[i].category);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function test_adjustTiers_withZeroCategory(
|
|
346
|
+
uint256 initialNumberOfTiers,
|
|
347
|
+
uint256 numberOfTiersToAdd,
|
|
348
|
+
uint256 seed
|
|
349
|
+
)
|
|
350
|
+
public
|
|
351
|
+
{
|
|
352
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 2, 10);
|
|
353
|
+
|
|
354
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 4, 14);
|
|
355
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
356
|
+
|
|
357
|
+
// Sort tiers in ascending order.
|
|
358
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
359
|
+
|
|
360
|
+
// Use category 0 for the default tiers.
|
|
361
|
+
defaultTierConfig.category = 0;
|
|
362
|
+
|
|
363
|
+
// Initialize the hook with default tiers.
|
|
364
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
365
|
+
|
|
366
|
+
// Create new tiers to add.
|
|
367
|
+
defaultTierConfig.category = 5;
|
|
368
|
+
(JB721TierConfig[] memory tierConfigsToAdd,) =
|
|
369
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd, 2);
|
|
370
|
+
|
|
371
|
+
// Add the new tiers.
|
|
372
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
373
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigsToAdd);
|
|
374
|
+
|
|
375
|
+
// Get all stored tiers (category 0 gets all).
|
|
376
|
+
uint256[] memory categories = new uint256[](1);
|
|
377
|
+
categories[0] = 0;
|
|
378
|
+
JB721Tier[] memory allStoredTiers = hook.STORE().tiersOf(address(hook), categories, false, 0, 100);
|
|
379
|
+
|
|
380
|
+
// Check: does the number of stored tiers match the number of tiers that were added?
|
|
381
|
+
assertEq(allStoredTiers.length, initialNumberOfTiers);
|
|
382
|
+
// Check: does each stored tier have category `0`?
|
|
383
|
+
for (uint256 i = 0; i < allStoredTiers.length; i++) {
|
|
384
|
+
assertEq(allStoredTiers[i].category, uint8(0));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function test_adjustTiers_withDifferentCategoriesAndFetchedTogether(
|
|
389
|
+
uint256 initialNumberOfTiers,
|
|
390
|
+
uint256 numberOfTiersToAdd,
|
|
391
|
+
uint256 seed
|
|
392
|
+
)
|
|
393
|
+
public
|
|
394
|
+
{
|
|
395
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 1, 14);
|
|
396
|
+
|
|
397
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 1, 14);
|
|
398
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
399
|
+
|
|
400
|
+
// Sort tiers in ascending order.
|
|
401
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
402
|
+
|
|
403
|
+
// Initialize the hook with default tiers of category 100.
|
|
404
|
+
defaultTierConfig.category = 100;
|
|
405
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
406
|
+
|
|
407
|
+
// Create new tiers to add (with category 101).
|
|
408
|
+
defaultTierConfig.category = 101;
|
|
409
|
+
(JB721TierConfig[] memory tierConfigsToAdd,) =
|
|
410
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
411
|
+
|
|
412
|
+
// Add the 101 tiers.
|
|
413
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
414
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigsToAdd);
|
|
415
|
+
|
|
416
|
+
// Create new tiers to add (with category 102).
|
|
417
|
+
defaultTierConfig.category = 102;
|
|
418
|
+
(tierConfigsToAdd,) =
|
|
419
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
420
|
+
|
|
421
|
+
// Add the 102 tiers.
|
|
422
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigsToAdd);
|
|
423
|
+
|
|
424
|
+
// Get the stored tiers with categories 100-102.
|
|
425
|
+
uint256[] memory categories = new uint256[](3);
|
|
426
|
+
categories[0] = 102;
|
|
427
|
+
categories[1] = 100;
|
|
428
|
+
categories[2] = 101;
|
|
429
|
+
JB721Tier[] memory allStoredTiers = hook.STORE().tiersOf(address(hook), categories, false, 0, 100);
|
|
430
|
+
|
|
431
|
+
// Check: are the correct number of tiers stored?
|
|
432
|
+
assertEq(
|
|
433
|
+
allStoredTiers.length, initialNumberOfTiers + pricesForTiersToAdd.length * 2, "Wrong total number of tiers."
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
uint256 maxIndexForTier100 = allStoredTiers.length - pricesForTiersToAdd.length;
|
|
437
|
+
|
|
438
|
+
// Check: do the tiers have the correct categories?
|
|
439
|
+
for (uint256 i = 0; i < pricesForTiersToAdd.length; i++) {
|
|
440
|
+
assertEq(allStoredTiers[i].category, uint8(102), "Wrong, first category (102).");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (uint256 i = pricesForTiersToAdd.length; i < maxIndexForTier100; i++) {
|
|
444
|
+
assertEq(allStoredTiers[i].category, uint8(100), "Wrong, second category (100).");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
for (uint256 i = maxIndexForTier100; i < allStoredTiers.length; i++) {
|
|
448
|
+
assertEq(allStoredTiers[i].category, uint8(101), "Wrong, third category (101).");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function test_adjustTiers_addNewTiers_fetchSpecificTier(
|
|
453
|
+
uint256 initialNumberOfTiers,
|
|
454
|
+
uint256 numberOfTiersToAdd,
|
|
455
|
+
uint256 seed
|
|
456
|
+
)
|
|
457
|
+
public
|
|
458
|
+
{
|
|
459
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 1, 14);
|
|
460
|
+
|
|
461
|
+
numberOfTiersToAdd = bound(numberOfTiersToAdd, 1, 14);
|
|
462
|
+
uint16[] memory pricesForTiersToAdd = _createArray(numberOfTiersToAdd, seed);
|
|
463
|
+
|
|
464
|
+
// Sort tiers in ascending order.
|
|
465
|
+
pricesForTiersToAdd = _sortArray(pricesForTiersToAdd);
|
|
466
|
+
|
|
467
|
+
// Initialize hook with default tiers from category 100.
|
|
468
|
+
defaultTierConfig.category = 100;
|
|
469
|
+
JB721TiersHook hook = _initHookDefaultTiers(initialNumberOfTiers);
|
|
470
|
+
|
|
471
|
+
// Create new tiers to add (with category 101).
|
|
472
|
+
defaultTierConfig.category = 101;
|
|
473
|
+
(JB721TierConfig[] memory tierConfigsToAdd,) =
|
|
474
|
+
_createTiers(defaultTierConfig, numberOfTiersToAdd, initialNumberOfTiers, pricesForTiersToAdd);
|
|
475
|
+
|
|
476
|
+
// Add the new tiers
|
|
477
|
+
uint256 tiersLeft = initialNumberOfTiers;
|
|
478
|
+
tiersLeft = _addDeleteTiers(hook, tiersLeft, 0, tierConfigsToAdd);
|
|
479
|
+
|
|
480
|
+
// Get the tiers from category 101.
|
|
481
|
+
uint256[] memory categories = new uint256[](1);
|
|
482
|
+
categories[0] = 101;
|
|
483
|
+
JB721Tier[] memory storedTiers = hook.STORE()
|
|
484
|
+
.tiersOf(address(hook), categories, false, 0, initialNumberOfTiers + pricesForTiersToAdd.length);
|
|
485
|
+
|
|
486
|
+
// Check: does the number of stored tiers match the number of tiers that were added?
|
|
487
|
+
assertEq(storedTiers.length, pricesForTiersToAdd.length);
|
|
488
|
+
|
|
489
|
+
// Check: do the tiers have category 101?
|
|
490
|
+
for (uint256 i = 0; i < storedTiers.length; i++) {
|
|
491
|
+
assertEq(storedTiers[i].category, uint8(101));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function test_adjustTiers_removeTiers(
|
|
496
|
+
uint256 initialNumberOfTiers,
|
|
497
|
+
uint256 seed,
|
|
498
|
+
uint256 numberOfTiersToRemove
|
|
499
|
+
)
|
|
500
|
+
public
|
|
501
|
+
{
|
|
502
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 14);
|
|
503
|
+
numberOfTiersToRemove = bound(numberOfTiersToRemove, 0, initialNumberOfTiers);
|
|
504
|
+
|
|
505
|
+
// Create random tiers to remove.
|
|
506
|
+
uint256[] memory tiersToRemove = new uint256[](numberOfTiersToRemove);
|
|
507
|
+
|
|
508
|
+
// Use the `seed` to generate new random tiers, and iterate on `i` to fill the `tiersToRemove` array.
|
|
509
|
+
for (uint256 i; i < numberOfTiersToRemove;) {
|
|
510
|
+
uint256 newTierCandidate = uint256(keccak256(abi.encode(seed))) % initialNumberOfTiers + 1;
|
|
511
|
+
bool invalidTier;
|
|
512
|
+
if (newTierCandidate != 0) {
|
|
513
|
+
for (uint256 j; j < numberOfTiersToRemove; j++) {
|
|
514
|
+
// Same value twice?
|
|
515
|
+
if (newTierCandidate == tiersToRemove[j]) {
|
|
516
|
+
invalidTier = true;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (!invalidTier) {
|
|
521
|
+
tiersToRemove[i] = newTierCandidate;
|
|
522
|
+
i++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Overflow to loop over (seed is fuzzed, and could start at `max(uint256)`).
|
|
526
|
+
unchecked {
|
|
527
|
+
seed++;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Order the tiers to remove for event matching (which are ordered too).
|
|
532
|
+
tiersToRemove = _sortArray(tiersToRemove);
|
|
533
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
534
|
+
JB721Tier[] memory tiers = new JB721Tier[](initialNumberOfTiers);
|
|
535
|
+
|
|
536
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
537
|
+
tierConfigs[i] = JB721TierConfig({
|
|
538
|
+
price: uint104((i + 1) * 10),
|
|
539
|
+
initialSupply: uint32(100),
|
|
540
|
+
votingUnits: uint16(0),
|
|
541
|
+
reserveFrequency: uint16(i),
|
|
542
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
543
|
+
encodedIPFSUri: tokenUris[0],
|
|
544
|
+
category: uint24(100),
|
|
545
|
+
discountPercent: uint8(0),
|
|
546
|
+
allowOwnerMint: false,
|
|
547
|
+
useReserveBeneficiaryAsDefault: false,
|
|
548
|
+
transfersPausable: false,
|
|
549
|
+
useVotingUnits: true,
|
|
550
|
+
cannotBeRemoved: false,
|
|
551
|
+
cannotIncreaseDiscountPercent: false
|
|
552
|
+
});
|
|
553
|
+
tiers[i] = JB721Tier({
|
|
554
|
+
id: uint32(i + 1),
|
|
555
|
+
price: tierConfigs[i].price,
|
|
556
|
+
remainingSupply: tierConfigs[i].initialSupply,
|
|
557
|
+
initialSupply: tierConfigs[i].initialSupply,
|
|
558
|
+
votingUnits: tierConfigs[i].votingUnits,
|
|
559
|
+
reserveFrequency: tierConfigs[i].reserveFrequency,
|
|
560
|
+
reserveBeneficiary: i == 0 ? address(0) : tierConfigs[i].reserveBeneficiary,
|
|
561
|
+
encodedIPFSUri: tierConfigs[i].encodedIPFSUri,
|
|
562
|
+
category: tierConfigs[i].category,
|
|
563
|
+
discountPercent: tierConfigs[i].discountPercent,
|
|
564
|
+
allowOwnerMint: tierConfigs[i].allowOwnerMint,
|
|
565
|
+
transfersPausable: tierConfigs[i].transfersPausable,
|
|
566
|
+
cannotBeRemoved: tierConfigs[i].cannotBeRemoved,
|
|
567
|
+
cannotIncreaseDiscountPercent: tierConfigs[i].cannotIncreaseDiscountPercent,
|
|
568
|
+
resolvedUri: ""
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
572
|
+
ForTest_JB721TiersHook hook = new ForTest_JB721TiersHook(
|
|
573
|
+
projectId,
|
|
574
|
+
IJBDirectory(mockJBDirectory),
|
|
575
|
+
name,
|
|
576
|
+
symbol,
|
|
577
|
+
IJBRulesets(mockJBRulesets),
|
|
578
|
+
baseUri,
|
|
579
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
580
|
+
contractUri,
|
|
581
|
+
tierConfigs,
|
|
582
|
+
IJB721TiersHookStore(address(store)),
|
|
583
|
+
JB721TiersHookFlags({
|
|
584
|
+
preventOverspending: false,
|
|
585
|
+
noNewTiersWithReserves: false,
|
|
586
|
+
noNewTiersWithVotes: false,
|
|
587
|
+
noNewTiersWithOwnerMinting: true
|
|
588
|
+
})
|
|
589
|
+
);
|
|
590
|
+
hook.transferOwnership(owner);
|
|
591
|
+
|
|
592
|
+
// Will be resized later.
|
|
593
|
+
JB721TierConfig[] memory tierConfigsRemaining = new JB721TierConfig[](initialNumberOfTiers);
|
|
594
|
+
JB721Tier[] memory tiersRemaining = new JB721Tier[](initialNumberOfTiers);
|
|
595
|
+
for (uint256 i; i < tiers.length; i++) {
|
|
596
|
+
tierConfigsRemaining[i] = tierConfigs[i];
|
|
597
|
+
tiersRemaining[i] = tiers[i];
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Iterate through the remaining tiers and remove the ones in `tiersToRemove`.
|
|
601
|
+
// Do this by "swapping" the tier to remove with the last element in the array, and then "popping" that last
|
|
602
|
+
// element.
|
|
603
|
+
for (uint256 i; i < tiersRemaining.length;) {
|
|
604
|
+
bool swappedAndPopped;
|
|
605
|
+
for (uint256 j; j < tiersToRemove.length; j++) {
|
|
606
|
+
if (tiersRemaining[i].id == tiersToRemove[j]) {
|
|
607
|
+
// Swap and pop removed tiers.
|
|
608
|
+
tiersRemaining[i] = tiersRemaining[tiersRemaining.length - 1];
|
|
609
|
+
tierConfigsRemaining[i] = tierConfigsRemaining[tierConfigsRemaining.length - 1];
|
|
610
|
+
// Remove the last elelment / reduce array length by 1.
|
|
611
|
+
assembly ("memory-safe") {
|
|
612
|
+
mstore(tiersRemaining, sub(mload(tiersRemaining), 1))
|
|
613
|
+
mstore(tierConfigsRemaining, sub(mload(tierConfigsRemaining), 1))
|
|
614
|
+
}
|
|
615
|
+
swappedAndPopped = true;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (!swappedAndPopped) i++;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Check: was `RemoveTier` emitted with the correct values?
|
|
623
|
+
for (uint256 i; i < tiersToRemove.length; i++) {
|
|
624
|
+
vm.expectEmit(true, false, false, true, address(hook));
|
|
625
|
+
emit RemoveTier(tiersToRemove[i], owner);
|
|
626
|
+
}
|
|
627
|
+
vm.prank(owner);
|
|
628
|
+
hook.adjustTiers(new JB721TierConfig[](0), tiersToRemove);
|
|
629
|
+
{
|
|
630
|
+
uint256 finalNumberOfTiers = initialNumberOfTiers - tiersToRemove.length;
|
|
631
|
+
JB721Tier[] memory storedTiers =
|
|
632
|
+
hook.test_store().tiersOf(address(hook), new uint256[](0), false, 0, finalNumberOfTiers);
|
|
633
|
+
// Check: are the expected number of tiers stored?
|
|
634
|
+
assertEq(storedTiers.length, finalNumberOfTiers);
|
|
635
|
+
// Check: are all of the remaining tiers still stored?
|
|
636
|
+
assertTrue(_isIn(tiersRemaining, storedTiers));
|
|
637
|
+
// Check: have all the removed tiers tiers been removed from storage?
|
|
638
|
+
assertTrue(_isIn(storedTiers, tiersRemaining));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function test_adjustTiers_addAndRemoveTiers() public {
|
|
643
|
+
uint256 initialNumberOfTiers = 5;
|
|
644
|
+
uint256 numberOfTiersToAdd = 5;
|
|
645
|
+
uint256 numberOfTiersToRemove = 3;
|
|
646
|
+
uint256[] memory tiersToAdd = new uint256[](numberOfTiersToAdd);
|
|
647
|
+
tiersToAdd[0] = 1;
|
|
648
|
+
tiersToAdd[1] = 4;
|
|
649
|
+
tiersToAdd[2] = 5;
|
|
650
|
+
tiersToAdd[3] = 6;
|
|
651
|
+
tiersToAdd[4] = 10;
|
|
652
|
+
uint256[] memory tierIdsToRemove = new uint256[](numberOfTiersToRemove);
|
|
653
|
+
tierIdsToRemove[0] = 1;
|
|
654
|
+
tierIdsToRemove[1] = 3;
|
|
655
|
+
tierIdsToRemove[2] = 4;
|
|
656
|
+
// Initial tiers configs and data.
|
|
657
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
658
|
+
JB721Tier[] memory tiers = new JB721Tier[](initialNumberOfTiers);
|
|
659
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
660
|
+
tierConfigs[i] = JB721TierConfig({
|
|
661
|
+
price: uint104((i + 1) * 10),
|
|
662
|
+
initialSupply: uint32(100),
|
|
663
|
+
votingUnits: uint16(0),
|
|
664
|
+
reserveFrequency: uint16(i),
|
|
665
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
666
|
+
encodedIPFSUri: tokenUris[0],
|
|
667
|
+
category: uint24(100),
|
|
668
|
+
discountPercent: uint8(0),
|
|
669
|
+
allowOwnerMint: false,
|
|
670
|
+
useReserveBeneficiaryAsDefault: false,
|
|
671
|
+
transfersPausable: false,
|
|
672
|
+
useVotingUnits: true,
|
|
673
|
+
cannotBeRemoved: false,
|
|
674
|
+
cannotIncreaseDiscountPercent: false
|
|
675
|
+
});
|
|
676
|
+
tiers[i] = JB721Tier({
|
|
677
|
+
id: uint32(i + 1),
|
|
678
|
+
price: tierConfigs[i].price,
|
|
679
|
+
remainingSupply: tierConfigs[i].initialSupply,
|
|
680
|
+
initialSupply: tierConfigs[i].initialSupply,
|
|
681
|
+
votingUnits: tierConfigs[i].votingUnits,
|
|
682
|
+
reserveFrequency: tierConfigs[i].reserveFrequency,
|
|
683
|
+
reserveBeneficiary: i == 0 ? address(0) : tierConfigs[i].reserveBeneficiary,
|
|
684
|
+
encodedIPFSUri: tierConfigs[i].encodedIPFSUri,
|
|
685
|
+
category: tierConfigs[i].category,
|
|
686
|
+
discountPercent: tierConfigs[i].discountPercent,
|
|
687
|
+
allowOwnerMint: tierConfigs[i].allowOwnerMint,
|
|
688
|
+
transfersPausable: tierConfigs[i].transfersPausable,
|
|
689
|
+
cannotBeRemoved: tierConfigs[i].cannotBeRemoved,
|
|
690
|
+
cannotIncreaseDiscountPercent: tierConfigs[i].cannotIncreaseDiscountPercent,
|
|
691
|
+
resolvedUri: ""
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
// Deploy the hook and its store with the initial tiers.
|
|
695
|
+
vm.etch(hook_i, address(hook).code);
|
|
696
|
+
JB721TiersHook hook = JB721TiersHook(hook_i);
|
|
697
|
+
hook.initialize(
|
|
698
|
+
projectId,
|
|
699
|
+
name,
|
|
700
|
+
symbol,
|
|
701
|
+
baseUri,
|
|
702
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
703
|
+
contractUri,
|
|
704
|
+
JB721InitTiersConfig({
|
|
705
|
+
tiers: tierConfigs,
|
|
706
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
707
|
+
decimals: 18,
|
|
708
|
+
prices: IJBPrices(address(0))
|
|
709
|
+
}),
|
|
710
|
+
JB721TiersHookFlags({
|
|
711
|
+
preventOverspending: false,
|
|
712
|
+
noNewTiersWithReserves: false,
|
|
713
|
+
noNewTiersWithVotes: false,
|
|
714
|
+
noNewTiersWithOwnerMinting: true
|
|
715
|
+
})
|
|
716
|
+
);
|
|
717
|
+
hook.transferOwnership(owner);
|
|
718
|
+
|
|
719
|
+
// -- Build expected removed/remaining tiers -- //
|
|
720
|
+
JB721TierConfig[] memory tierConfigsRemaining = new JB721TierConfig[](2);
|
|
721
|
+
JB721Tier[] memory tiersRemaining = new JB721Tier[](2);
|
|
722
|
+
uint256 arrayIndex;
|
|
723
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
724
|
+
// Tiers which will remain.
|
|
725
|
+
if (i + 1 != 1 && i + 1 != 3 && i + 1 != 4) {
|
|
726
|
+
tierConfigsRemaining[arrayIndex] = JB721TierConfig({
|
|
727
|
+
price: uint104((i + 1) * 10),
|
|
728
|
+
initialSupply: uint32(100),
|
|
729
|
+
votingUnits: uint16(0),
|
|
730
|
+
reserveFrequency: uint16(i),
|
|
731
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
732
|
+
encodedIPFSUri: tokenUris[0],
|
|
733
|
+
category: uint24(100),
|
|
734
|
+
discountPercent: uint8(0),
|
|
735
|
+
allowOwnerMint: false,
|
|
736
|
+
useReserveBeneficiaryAsDefault: false,
|
|
737
|
+
transfersPausable: false,
|
|
738
|
+
useVotingUnits: true,
|
|
739
|
+
cannotBeRemoved: false,
|
|
740
|
+
cannotIncreaseDiscountPercent: false
|
|
741
|
+
});
|
|
742
|
+
tiersRemaining[arrayIndex] = JB721Tier({
|
|
743
|
+
id: uint32(i + 1),
|
|
744
|
+
price: tierConfigsRemaining[arrayIndex].price,
|
|
745
|
+
remainingSupply: tierConfigsRemaining[arrayIndex].initialSupply,
|
|
746
|
+
initialSupply: tierConfigsRemaining[arrayIndex].initialSupply,
|
|
747
|
+
votingUnits: tierConfigsRemaining[arrayIndex].votingUnits,
|
|
748
|
+
reserveFrequency: tierConfigsRemaining[arrayIndex].reserveFrequency,
|
|
749
|
+
reserveBeneficiary: i == 0 ? address(0) : tierConfigsRemaining[arrayIndex].reserveBeneficiary,
|
|
750
|
+
encodedIPFSUri: tierConfigsRemaining[arrayIndex].encodedIPFSUri,
|
|
751
|
+
category: tierConfigsRemaining[arrayIndex].category,
|
|
752
|
+
discountPercent: tierConfigsRemaining[arrayIndex].discountPercent,
|
|
753
|
+
allowOwnerMint: tierConfigsRemaining[arrayIndex].allowOwnerMint,
|
|
754
|
+
transfersPausable: tierConfigsRemaining[arrayIndex].transfersPausable,
|
|
755
|
+
cannotBeRemoved: tierConfigsRemaining[arrayIndex].cannotBeRemoved,
|
|
756
|
+
cannotIncreaseDiscountPercent: tierConfigsRemaining[arrayIndex].cannotIncreaseDiscountPercent,
|
|
757
|
+
resolvedUri: ""
|
|
758
|
+
});
|
|
759
|
+
arrayIndex++;
|
|
760
|
+
} else {
|
|
761
|
+
// Otherwise, part of the tiers removed:
|
|
762
|
+
// Check: was `RemoveTier` emitted with the correct values?
|
|
763
|
+
vm.expectEmit(true, false, false, true, address(hook));
|
|
764
|
+
emit RemoveTier(i + 1, owner);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// -- Build expected added tiers -- //
|
|
769
|
+
JB721TierConfig[] memory tierConfigsToAdd = new JB721TierConfig[](numberOfTiersToAdd);
|
|
770
|
+
JB721Tier[] memory tiersAdded = new JB721Tier[](numberOfTiersToAdd);
|
|
771
|
+
for (uint256 i; i < numberOfTiersToAdd; i++) {
|
|
772
|
+
tierConfigsToAdd[i] = JB721TierConfig({
|
|
773
|
+
price: uint104(tiersToAdd[i]) * 11,
|
|
774
|
+
initialSupply: uint32(100),
|
|
775
|
+
votingUnits: uint16(0),
|
|
776
|
+
reserveFrequency: uint16(0),
|
|
777
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
778
|
+
encodedIPFSUri: tokenUris[0],
|
|
779
|
+
category: uint24(100 + i),
|
|
780
|
+
discountPercent: uint8(0),
|
|
781
|
+
allowOwnerMint: false,
|
|
782
|
+
useReserveBeneficiaryAsDefault: false,
|
|
783
|
+
transfersPausable: false,
|
|
784
|
+
useVotingUnits: true,
|
|
785
|
+
cannotBeRemoved: false,
|
|
786
|
+
cannotIncreaseDiscountPercent: false
|
|
787
|
+
});
|
|
788
|
+
tiersAdded[i] = JB721Tier({
|
|
789
|
+
id: uint32(tiers.length + (i + 1)),
|
|
790
|
+
price: tierConfigsToAdd[i].price,
|
|
791
|
+
remainingSupply: tierConfigsToAdd[i].initialSupply,
|
|
792
|
+
initialSupply: tierConfigsToAdd[i].initialSupply,
|
|
793
|
+
votingUnits: tierConfigsToAdd[i].votingUnits,
|
|
794
|
+
reserveFrequency: tierConfigsToAdd[i].reserveFrequency,
|
|
795
|
+
reserveBeneficiary: address(0),
|
|
796
|
+
encodedIPFSUri: tierConfigsToAdd[i].encodedIPFSUri,
|
|
797
|
+
category: tierConfigsToAdd[i].category,
|
|
798
|
+
discountPercent: tierConfigsToAdd[i].discountPercent,
|
|
799
|
+
allowOwnerMint: tierConfigsToAdd[i].allowOwnerMint,
|
|
800
|
+
transfersPausable: tierConfigsToAdd[i].transfersPausable,
|
|
801
|
+
cannotBeRemoved: tierConfigsToAdd[i].cannotBeRemoved,
|
|
802
|
+
cannotIncreaseDiscountPercent: tierConfigsToAdd[i].cannotIncreaseDiscountPercent,
|
|
803
|
+
resolvedUri: ""
|
|
804
|
+
});
|
|
805
|
+
vm.expectEmit(true, true, true, true, address(hook));
|
|
806
|
+
emit AddTier(tiersAdded[i].id, tierConfigsToAdd[i], owner);
|
|
807
|
+
}
|
|
808
|
+
vm.prank(owner);
|
|
809
|
+
hook.adjustTiers(tierConfigsToAdd, tierIdsToRemove);
|
|
810
|
+
JB721Tier[] memory storedTiers = hook.STORE()
|
|
811
|
+
.tiersOf(
|
|
812
|
+
address(hook),
|
|
813
|
+
new uint256[](0),
|
|
814
|
+
false,
|
|
815
|
+
0,
|
|
816
|
+
7 // 7 tiers remaining - hard-coded to avoid stack too deep.
|
|
817
|
+
);
|
|
818
|
+
// Check: are the expected number of tiers stored?
|
|
819
|
+
assertEq(storedTiers.length, 7);
|
|
820
|
+
// Check: are all non-deleted and added tiers in the new tiers (unsorted)?
|
|
821
|
+
assertTrue(_isIn(tiersRemaining, storedTiers)); // Original tiers
|
|
822
|
+
assertTrue(_isIn(tiersAdded, storedTiers)); // New tiers
|
|
823
|
+
// Check: are all the deleted tiers removed?
|
|
824
|
+
assertFalse(_isIn(tiers, storedTiers)); // Will emit `_isIn: incomplete inclusion but without failing assertion`
|
|
825
|
+
// Check: are all the tiers sorted?
|
|
826
|
+
for (uint256 j = 1; j < storedTiers.length; j++) {
|
|
827
|
+
assertLe(storedTiers[j - 1].category, storedTiers[j].category);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function test_adjustTiers_revertIfAddingWithVotingPower(
|
|
832
|
+
uint256 initialNumberOfTiers,
|
|
833
|
+
uint256 numberTiersToAdd
|
|
834
|
+
)
|
|
835
|
+
public
|
|
836
|
+
{
|
|
837
|
+
// Include adding X new tiers with 0 current tiers.
|
|
838
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 15);
|
|
839
|
+
numberTiersToAdd = bound(numberTiersToAdd, 1, 15);
|
|
840
|
+
|
|
841
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
842
|
+
JB721Tier[] memory tiers = new JB721Tier[](initialNumberOfTiers);
|
|
843
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
844
|
+
tierConfigs[i] = JB721TierConfig({
|
|
845
|
+
price: uint104((i + 1) * 10),
|
|
846
|
+
initialSupply: uint32(100),
|
|
847
|
+
votingUnits: uint16(0),
|
|
848
|
+
reserveFrequency: uint16(i),
|
|
849
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
850
|
+
encodedIPFSUri: tokenUris[0],
|
|
851
|
+
category: uint24(100),
|
|
852
|
+
discountPercent: uint8(0),
|
|
853
|
+
allowOwnerMint: false,
|
|
854
|
+
useReserveBeneficiaryAsDefault: false,
|
|
855
|
+
transfersPausable: false,
|
|
856
|
+
useVotingUnits: true,
|
|
857
|
+
cannotBeRemoved: false,
|
|
858
|
+
cannotIncreaseDiscountPercent: false
|
|
859
|
+
});
|
|
860
|
+
tiers[i] = JB721Tier({
|
|
861
|
+
id: uint32(i + 1),
|
|
862
|
+
price: tierConfigs[i].price,
|
|
863
|
+
remainingSupply: tierConfigs[i].initialSupply,
|
|
864
|
+
initialSupply: tierConfigs[i].initialSupply,
|
|
865
|
+
votingUnits: tierConfigs[i].votingUnits,
|
|
866
|
+
reserveFrequency: tierConfigs[i].reserveFrequency,
|
|
867
|
+
reserveBeneficiary: tierConfigs[i].reserveBeneficiary,
|
|
868
|
+
encodedIPFSUri: tierConfigs[i].encodedIPFSUri,
|
|
869
|
+
category: tierConfigs[i].category,
|
|
870
|
+
discountPercent: tierConfigs[i].discountPercent,
|
|
871
|
+
allowOwnerMint: tierConfigs[i].allowOwnerMint,
|
|
872
|
+
transfersPausable: tierConfigs[i].transfersPausable,
|
|
873
|
+
cannotBeRemoved: tierConfigs[i].cannotBeRemoved,
|
|
874
|
+
cannotIncreaseDiscountPercent: tierConfigs[i].cannotIncreaseDiscountPercent,
|
|
875
|
+
resolvedUri: ""
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
879
|
+
ForTest_JB721TiersHook hook = new ForTest_JB721TiersHook(
|
|
880
|
+
projectId,
|
|
881
|
+
IJBDirectory(mockJBDirectory),
|
|
882
|
+
name,
|
|
883
|
+
symbol,
|
|
884
|
+
IJBRulesets(mockJBRulesets),
|
|
885
|
+
baseUri,
|
|
886
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
887
|
+
contractUri,
|
|
888
|
+
tierConfigs,
|
|
889
|
+
IJB721TiersHookStore(address(store)),
|
|
890
|
+
JB721TiersHookFlags({
|
|
891
|
+
preventOverspending: false,
|
|
892
|
+
noNewTiersWithReserves: false,
|
|
893
|
+
noNewTiersWithVotes: true,
|
|
894
|
+
noNewTiersWithOwnerMinting: true
|
|
895
|
+
})
|
|
896
|
+
);
|
|
897
|
+
hook.transferOwnership(owner);
|
|
898
|
+
JB721TierConfig[] memory tierConfigsToAdd = new JB721TierConfig[](numberTiersToAdd);
|
|
899
|
+
JB721Tier[] memory tiersAdded = new JB721Tier[](numberTiersToAdd);
|
|
900
|
+
for (uint256 i; i < numberTiersToAdd; i++) {
|
|
901
|
+
tierConfigsToAdd[i] = JB721TierConfig({
|
|
902
|
+
price: uint104(0),
|
|
903
|
+
initialSupply: uint32(100),
|
|
904
|
+
votingUnits: uint16(i + 1),
|
|
905
|
+
reserveFrequency: uint16(i),
|
|
906
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
907
|
+
encodedIPFSUri: tokenUris[0],
|
|
908
|
+
category: uint24(100),
|
|
909
|
+
discountPercent: uint8(0),
|
|
910
|
+
allowOwnerMint: false,
|
|
911
|
+
useReserveBeneficiaryAsDefault: false,
|
|
912
|
+
transfersPausable: false,
|
|
913
|
+
useVotingUnits: true,
|
|
914
|
+
cannotBeRemoved: false,
|
|
915
|
+
cannotIncreaseDiscountPercent: false
|
|
916
|
+
});
|
|
917
|
+
tiersAdded[i] = JB721Tier({
|
|
918
|
+
id: uint32(tiers.length + (i + 1)),
|
|
919
|
+
price: tierConfigsToAdd[i].price,
|
|
920
|
+
remainingSupply: tierConfigsToAdd[i].initialSupply,
|
|
921
|
+
initialSupply: tierConfigsToAdd[i].initialSupply,
|
|
922
|
+
votingUnits: tierConfigsToAdd[i].votingUnits,
|
|
923
|
+
reserveFrequency: tierConfigsToAdd[i].reserveFrequency,
|
|
924
|
+
reserveBeneficiary: tierConfigsToAdd[i].reserveBeneficiary,
|
|
925
|
+
encodedIPFSUri: tierConfigsToAdd[i].encodedIPFSUri,
|
|
926
|
+
category: tierConfigsToAdd[i].category,
|
|
927
|
+
discountPercent: tierConfigsToAdd[i].discountPercent,
|
|
928
|
+
allowOwnerMint: tierConfigsToAdd[i].allowOwnerMint,
|
|
929
|
+
transfersPausable: tierConfigsToAdd[i].transfersPausable,
|
|
930
|
+
cannotBeRemoved: tierConfigsToAdd[i].cannotBeRemoved,
|
|
931
|
+
cannotIncreaseDiscountPercent: tierConfigsToAdd[i].cannotIncreaseDiscountPercent,
|
|
932
|
+
resolvedUri: ""
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
vm.expectRevert(
|
|
936
|
+
abi.encodeWithSelector(
|
|
937
|
+
JB721TiersHookStore.JB721TiersHookStore_VotingUnitsNotAllowed.selector, initialNumberOfTiers + 1
|
|
938
|
+
)
|
|
939
|
+
);
|
|
940
|
+
vm.prank(owner);
|
|
941
|
+
hook.adjustTiers(tierConfigsToAdd, new uint256[](0));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function test_adjustTiers_revertIfAddingWithReserveFrequency(
|
|
945
|
+
uint256 initialNumberOfTiers,
|
|
946
|
+
uint256 numberTiersToAdd
|
|
947
|
+
)
|
|
948
|
+
public
|
|
949
|
+
{
|
|
950
|
+
// Include adding X new tiers with 0 current tiers.
|
|
951
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 15);
|
|
952
|
+
numberTiersToAdd = bound(numberTiersToAdd, 1, 15);
|
|
953
|
+
|
|
954
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
955
|
+
JB721Tier[] memory tiers = new JB721Tier[](initialNumberOfTiers);
|
|
956
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
957
|
+
tierConfigs[i] = JB721TierConfig({
|
|
958
|
+
price: uint104((i + 1) * 10),
|
|
959
|
+
initialSupply: uint32(100),
|
|
960
|
+
votingUnits: uint16(0),
|
|
961
|
+
reserveFrequency: uint16(i),
|
|
962
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
963
|
+
encodedIPFSUri: tokenUris[0],
|
|
964
|
+
category: uint24(100),
|
|
965
|
+
discountPercent: uint8(0),
|
|
966
|
+
allowOwnerMint: false,
|
|
967
|
+
useReserveBeneficiaryAsDefault: false,
|
|
968
|
+
transfersPausable: false,
|
|
969
|
+
useVotingUnits: true,
|
|
970
|
+
cannotBeRemoved: false,
|
|
971
|
+
cannotIncreaseDiscountPercent: false
|
|
972
|
+
});
|
|
973
|
+
tiers[i] = JB721Tier({
|
|
974
|
+
id: uint32(i + 1),
|
|
975
|
+
price: tierConfigs[i].price,
|
|
976
|
+
remainingSupply: tierConfigs[i].initialSupply,
|
|
977
|
+
initialSupply: tierConfigs[i].initialSupply,
|
|
978
|
+
votingUnits: tierConfigs[i].votingUnits,
|
|
979
|
+
reserveFrequency: tierConfigs[i].reserveFrequency,
|
|
980
|
+
reserveBeneficiary: tierConfigs[i].reserveBeneficiary,
|
|
981
|
+
encodedIPFSUri: tierConfigs[i].encodedIPFSUri,
|
|
982
|
+
category: tierConfigs[i].category,
|
|
983
|
+
discountPercent: tierConfigs[i].discountPercent,
|
|
984
|
+
allowOwnerMint: tierConfigs[i].allowOwnerMint,
|
|
985
|
+
transfersPausable: tierConfigs[i].transfersPausable,
|
|
986
|
+
cannotBeRemoved: tierConfigs[i].cannotBeRemoved,
|
|
987
|
+
cannotIncreaseDiscountPercent: tierConfigs[i].cannotIncreaseDiscountPercent,
|
|
988
|
+
resolvedUri: ""
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
992
|
+
ForTest_JB721TiersHook hook = new ForTest_JB721TiersHook(
|
|
993
|
+
projectId,
|
|
994
|
+
IJBDirectory(mockJBDirectory),
|
|
995
|
+
name,
|
|
996
|
+
symbol,
|
|
997
|
+
IJBRulesets(mockJBRulesets),
|
|
998
|
+
baseUri,
|
|
999
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1000
|
+
contractUri,
|
|
1001
|
+
tierConfigs,
|
|
1002
|
+
IJB721TiersHookStore(address(store)),
|
|
1003
|
+
JB721TiersHookFlags({
|
|
1004
|
+
preventOverspending: false,
|
|
1005
|
+
noNewTiersWithReserves: true, // <-- This is the flag we're testing.
|
|
1006
|
+
noNewTiersWithVotes: false,
|
|
1007
|
+
noNewTiersWithOwnerMinting: true
|
|
1008
|
+
})
|
|
1009
|
+
);
|
|
1010
|
+
hook.transferOwnership(owner);
|
|
1011
|
+
JB721TierConfig[] memory tierConfigsToAdd = new JB721TierConfig[](numberTiersToAdd);
|
|
1012
|
+
JB721Tier[] memory tiersAdded = new JB721Tier[](numberTiersToAdd);
|
|
1013
|
+
for (uint256 i; i < numberTiersToAdd; i++) {
|
|
1014
|
+
tierConfigsToAdd[i] = JB721TierConfig({
|
|
1015
|
+
price: uint104((i + 1) * 100),
|
|
1016
|
+
initialSupply: uint32(100),
|
|
1017
|
+
votingUnits: uint16(0),
|
|
1018
|
+
reserveFrequency: uint16(i + 1),
|
|
1019
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1020
|
+
encodedIPFSUri: tokenUris[0],
|
|
1021
|
+
category: uint24(100),
|
|
1022
|
+
discountPercent: uint8(0),
|
|
1023
|
+
allowOwnerMint: false,
|
|
1024
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1025
|
+
transfersPausable: false,
|
|
1026
|
+
useVotingUnits: true,
|
|
1027
|
+
cannotBeRemoved: false,
|
|
1028
|
+
cannotIncreaseDiscountPercent: false
|
|
1029
|
+
});
|
|
1030
|
+
tiersAdded[i] = JB721Tier({
|
|
1031
|
+
id: uint32(tiers.length + (i + 1)),
|
|
1032
|
+
price: tierConfigsToAdd[i].price,
|
|
1033
|
+
remainingSupply: tierConfigsToAdd[i].initialSupply,
|
|
1034
|
+
initialSupply: tierConfigsToAdd[i].initialSupply,
|
|
1035
|
+
votingUnits: tierConfigsToAdd[i].votingUnits,
|
|
1036
|
+
reserveFrequency: tierConfigsToAdd[i].reserveFrequency,
|
|
1037
|
+
reserveBeneficiary: tierConfigsToAdd[i].reserveBeneficiary,
|
|
1038
|
+
encodedIPFSUri: tierConfigsToAdd[i].encodedIPFSUri,
|
|
1039
|
+
category: tierConfigsToAdd[i].category,
|
|
1040
|
+
discountPercent: tierConfigsToAdd[i].discountPercent,
|
|
1041
|
+
allowOwnerMint: tierConfigsToAdd[i].allowOwnerMint,
|
|
1042
|
+
transfersPausable: tierConfigsToAdd[i].transfersPausable,
|
|
1043
|
+
cannotBeRemoved: tierConfigsToAdd[i].cannotBeRemoved,
|
|
1044
|
+
cannotIncreaseDiscountPercent: tierConfigsToAdd[i].cannotIncreaseDiscountPercent,
|
|
1045
|
+
resolvedUri: ""
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Expect the `adjustTiers` call to revert because of the `noNewTiersWithReserves` flag.
|
|
1050
|
+
vm.expectRevert(
|
|
1051
|
+
abi.encodeWithSelector(
|
|
1052
|
+
JB721TiersHookStore.JB721TiersHookStore_ReserveFrequencyNotAllowed.selector, initialNumberOfTiers + 1
|
|
1053
|
+
)
|
|
1054
|
+
);
|
|
1055
|
+
vm.prank(owner);
|
|
1056
|
+
hook.adjustTiers(tierConfigsToAdd, new uint256[](0));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function test_adjustTiers_revertIfCannotRemoveTier() public {
|
|
1060
|
+
uint256 initialNumberOfTiers = 2;
|
|
1061
|
+
uint256 numberOfTiersToRemove = 1;
|
|
1062
|
+
uint256[] memory tierIdsToRemove = new uint256[](numberOfTiersToRemove);
|
|
1063
|
+
tierIdsToRemove[0] = 1;
|
|
1064
|
+
// Initial tiers configs and data.
|
|
1065
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
1066
|
+
tierConfigs[0] = JB721TierConfig({
|
|
1067
|
+
price: 10,
|
|
1068
|
+
initialSupply: uint32(100),
|
|
1069
|
+
votingUnits: uint16(0),
|
|
1070
|
+
reserveFrequency: uint16(0),
|
|
1071
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1072
|
+
encodedIPFSUri: tokenUris[0],
|
|
1073
|
+
category: uint24(100),
|
|
1074
|
+
discountPercent: uint8(0),
|
|
1075
|
+
allowOwnerMint: false,
|
|
1076
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1077
|
+
transfersPausable: false,
|
|
1078
|
+
useVotingUnits: true,
|
|
1079
|
+
cannotBeRemoved: true,
|
|
1080
|
+
cannotIncreaseDiscountPercent: false
|
|
1081
|
+
});
|
|
1082
|
+
tierConfigs[1] = JB721TierConfig({
|
|
1083
|
+
price: 10,
|
|
1084
|
+
initialSupply: uint32(100),
|
|
1085
|
+
votingUnits: uint16(0),
|
|
1086
|
+
reserveFrequency: uint16(0),
|
|
1087
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1088
|
+
encodedIPFSUri: tokenUris[0],
|
|
1089
|
+
category: uint24(100),
|
|
1090
|
+
discountPercent: uint8(0),
|
|
1091
|
+
allowOwnerMint: false,
|
|
1092
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1093
|
+
transfersPausable: false,
|
|
1094
|
+
useVotingUnits: true,
|
|
1095
|
+
cannotBeRemoved: false,
|
|
1096
|
+
cannotIncreaseDiscountPercent: false
|
|
1097
|
+
});
|
|
1098
|
+
// Deploy the hook and its store with the initial tiers.
|
|
1099
|
+
vm.etch(hook_i, address(hook).code);
|
|
1100
|
+
JB721TiersHook hook = JB721TiersHook(hook_i);
|
|
1101
|
+
hook.initialize(
|
|
1102
|
+
projectId,
|
|
1103
|
+
name,
|
|
1104
|
+
symbol,
|
|
1105
|
+
baseUri,
|
|
1106
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1107
|
+
contractUri,
|
|
1108
|
+
JB721InitTiersConfig({
|
|
1109
|
+
tiers: tierConfigs,
|
|
1110
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1111
|
+
decimals: 18,
|
|
1112
|
+
prices: IJBPrices(address(0))
|
|
1113
|
+
}),
|
|
1114
|
+
JB721TiersHookFlags({
|
|
1115
|
+
preventOverspending: false,
|
|
1116
|
+
noNewTiersWithReserves: false,
|
|
1117
|
+
noNewTiersWithVotes: false,
|
|
1118
|
+
noNewTiersWithOwnerMinting: true
|
|
1119
|
+
})
|
|
1120
|
+
);
|
|
1121
|
+
hook.transferOwnership(owner);
|
|
1122
|
+
|
|
1123
|
+
// Expect the `adjustTiers` call to revert because cannot remove tier.
|
|
1124
|
+
vm.expectRevert(abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_CantRemoveTier.selector, 1));
|
|
1125
|
+
vm.prank(owner);
|
|
1126
|
+
hook.adjustTiers(new JB721TierConfig[](0), tierIdsToRemove);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function test_adjustTiers_revertIfEmptyQuantity(uint256 initialNumberOfTiers, uint256 numberTiersToAdd) public {
|
|
1130
|
+
// Include adding X new tiers with 0 current tiers.
|
|
1131
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 15);
|
|
1132
|
+
numberTiersToAdd = bound(numberTiersToAdd, 1, 15);
|
|
1133
|
+
|
|
1134
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
1135
|
+
JB721Tier[] memory tiers = new JB721Tier[](initialNumberOfTiers);
|
|
1136
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
1137
|
+
tierConfigs[i] = JB721TierConfig({
|
|
1138
|
+
price: uint104((i + 1) * 10),
|
|
1139
|
+
initialSupply: uint32(100),
|
|
1140
|
+
votingUnits: uint16(0),
|
|
1141
|
+
reserveFrequency: uint16(i),
|
|
1142
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1143
|
+
encodedIPFSUri: tokenUris[0],
|
|
1144
|
+
category: uint24(100),
|
|
1145
|
+
discountPercent: uint8(0),
|
|
1146
|
+
allowOwnerMint: false,
|
|
1147
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1148
|
+
transfersPausable: false,
|
|
1149
|
+
useVotingUnits: true,
|
|
1150
|
+
cannotBeRemoved: false,
|
|
1151
|
+
cannotIncreaseDiscountPercent: false
|
|
1152
|
+
});
|
|
1153
|
+
tiers[i] = JB721Tier({
|
|
1154
|
+
id: uint32(i + 1),
|
|
1155
|
+
price: tierConfigs[i].price,
|
|
1156
|
+
remainingSupply: tierConfigs[i].initialSupply,
|
|
1157
|
+
initialSupply: tierConfigs[i].initialSupply,
|
|
1158
|
+
votingUnits: tierConfigs[i].votingUnits,
|
|
1159
|
+
reserveFrequency: tierConfigs[i].reserveFrequency,
|
|
1160
|
+
reserveBeneficiary: tierConfigs[i].reserveBeneficiary,
|
|
1161
|
+
encodedIPFSUri: tierConfigs[i].encodedIPFSUri,
|
|
1162
|
+
category: tierConfigs[i].category,
|
|
1163
|
+
discountPercent: tierConfigs[i].discountPercent,
|
|
1164
|
+
allowOwnerMint: tierConfigs[i].allowOwnerMint,
|
|
1165
|
+
transfersPausable: tierConfigs[i].transfersPausable,
|
|
1166
|
+
cannotBeRemoved: tierConfigs[i].cannotBeRemoved,
|
|
1167
|
+
cannotIncreaseDiscountPercent: tierConfigs[i].cannotIncreaseDiscountPercent,
|
|
1168
|
+
resolvedUri: ""
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
1172
|
+
ForTest_JB721TiersHook hook = new ForTest_JB721TiersHook(
|
|
1173
|
+
projectId,
|
|
1174
|
+
IJBDirectory(mockJBDirectory),
|
|
1175
|
+
name,
|
|
1176
|
+
symbol,
|
|
1177
|
+
IJBRulesets(mockJBRulesets),
|
|
1178
|
+
baseUri,
|
|
1179
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1180
|
+
contractUri,
|
|
1181
|
+
tierConfigs,
|
|
1182
|
+
IJB721TiersHookStore(address(store)),
|
|
1183
|
+
JB721TiersHookFlags({
|
|
1184
|
+
preventOverspending: false,
|
|
1185
|
+
noNewTiersWithReserves: false,
|
|
1186
|
+
noNewTiersWithVotes: false,
|
|
1187
|
+
noNewTiersWithOwnerMinting: true
|
|
1188
|
+
})
|
|
1189
|
+
);
|
|
1190
|
+
hook.transferOwnership(owner);
|
|
1191
|
+
JB721TierConfig[] memory tierConfigsToAdd = new JB721TierConfig[](numberTiersToAdd);
|
|
1192
|
+
JB721Tier[] memory tiersAdded = new JB721Tier[](numberTiersToAdd);
|
|
1193
|
+
for (uint256 i; i < numberTiersToAdd; i++) {
|
|
1194
|
+
tierConfigsToAdd[i] = JB721TierConfig({
|
|
1195
|
+
price: uint104((i + 1) * 100),
|
|
1196
|
+
initialSupply: uint32(0), // <-- This is the value we're testing.
|
|
1197
|
+
votingUnits: uint16(0),
|
|
1198
|
+
reserveFrequency: uint16(0),
|
|
1199
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1200
|
+
encodedIPFSUri: tokenUris[0],
|
|
1201
|
+
category: uint24(100),
|
|
1202
|
+
discountPercent: uint8(0),
|
|
1203
|
+
allowOwnerMint: false,
|
|
1204
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1205
|
+
transfersPausable: false,
|
|
1206
|
+
useVotingUnits: false,
|
|
1207
|
+
cannotBeRemoved: false,
|
|
1208
|
+
cannotIncreaseDiscountPercent: false
|
|
1209
|
+
});
|
|
1210
|
+
tiersAdded[i] = JB721Tier({
|
|
1211
|
+
id: uint32(tiers.length + (i + 1)),
|
|
1212
|
+
price: tierConfigsToAdd[i].price,
|
|
1213
|
+
remainingSupply: tierConfigsToAdd[i].initialSupply,
|
|
1214
|
+
initialSupply: tierConfigsToAdd[i].initialSupply,
|
|
1215
|
+
votingUnits: tierConfigsToAdd[i].votingUnits,
|
|
1216
|
+
reserveFrequency: tierConfigsToAdd[i].reserveFrequency,
|
|
1217
|
+
reserveBeneficiary: tierConfigsToAdd[i].reserveBeneficiary,
|
|
1218
|
+
encodedIPFSUri: tierConfigsToAdd[i].encodedIPFSUri,
|
|
1219
|
+
category: tierConfigsToAdd[i].category,
|
|
1220
|
+
discountPercent: tierConfigsToAdd[i].discountPercent,
|
|
1221
|
+
allowOwnerMint: tierConfigsToAdd[i].allowOwnerMint,
|
|
1222
|
+
transfersPausable: tierConfigsToAdd[i].transfersPausable,
|
|
1223
|
+
cannotBeRemoved: tierConfigsToAdd[i].cannotBeRemoved,
|
|
1224
|
+
cannotIncreaseDiscountPercent: tierConfigsToAdd[i].cannotIncreaseDiscountPercent,
|
|
1225
|
+
resolvedUri: ""
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Expect the `adjustTiers` call to revert because of the `initialSupply` of 0.
|
|
1230
|
+
vm.expectRevert(
|
|
1231
|
+
abi.encodeWithSelector(
|
|
1232
|
+
JB721TiersHookStore.JB721TiersHookStore_ZeroInitialSupply.selector, initialNumberOfTiers + 1
|
|
1233
|
+
)
|
|
1234
|
+
);
|
|
1235
|
+
vm.prank(owner);
|
|
1236
|
+
hook.adjustTiers(tierConfigsToAdd, new uint256[](0));
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function test_adjustTiers_revertIfInvalidCategorySortOrder(
|
|
1240
|
+
uint256 initialNumberOfTiers,
|
|
1241
|
+
uint256 numberTiersToAdd
|
|
1242
|
+
)
|
|
1243
|
+
public
|
|
1244
|
+
{
|
|
1245
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 15);
|
|
1246
|
+
numberTiersToAdd = bound(numberTiersToAdd, 2, 15);
|
|
1247
|
+
|
|
1248
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(initialNumberOfTiers);
|
|
1249
|
+
|
|
1250
|
+
JB721TierConfig[] memory tierConfigsToAdd = new JB721TierConfig[](numberTiersToAdd);
|
|
1251
|
+
for (uint256 i; i < numberTiersToAdd; i++) {
|
|
1252
|
+
tierConfigsToAdd[i] = JB721TierConfig({
|
|
1253
|
+
price: uint104((i + 1) * 100),
|
|
1254
|
+
initialSupply: uint32(100),
|
|
1255
|
+
votingUnits: uint16(0),
|
|
1256
|
+
reserveFrequency: uint16(i),
|
|
1257
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1258
|
+
encodedIPFSUri: tokenUris[0],
|
|
1259
|
+
category: uint24(100),
|
|
1260
|
+
discountPercent: uint8(0),
|
|
1261
|
+
allowOwnerMint: false,
|
|
1262
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1263
|
+
transfersPausable: false,
|
|
1264
|
+
cannotBeRemoved: false,
|
|
1265
|
+
useVotingUnits: true,
|
|
1266
|
+
cannotIncreaseDiscountPercent: false
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
// Set the second to last tier to have a category of `99`, which is less than the last tier's category of `100`.
|
|
1270
|
+
tierConfigsToAdd[numberTiersToAdd - 1].category = uint8(99);
|
|
1271
|
+
|
|
1272
|
+
// Expect the `adjustTiers` call to revert because of the invalid category sort order.
|
|
1273
|
+
vm.expectRevert(
|
|
1274
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InvalidCategorySortOrder.selector, 99, 100)
|
|
1275
|
+
);
|
|
1276
|
+
vm.prank(owner);
|
|
1277
|
+
hook.adjustTiers(tierConfigsToAdd, new uint256[](0));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function test_adjustTiers_revertIfMoreVotingUnitsNotAllowedWithPriceChange(
|
|
1281
|
+
uint256 initialNumberOfTiers,
|
|
1282
|
+
uint256 numberTiersToAdd
|
|
1283
|
+
)
|
|
1284
|
+
public
|
|
1285
|
+
{
|
|
1286
|
+
// Include adding X new tiers with 0 current tiers.
|
|
1287
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 15);
|
|
1288
|
+
numberTiersToAdd = bound(numberTiersToAdd, 1, 15);
|
|
1289
|
+
|
|
1290
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
1291
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
1292
|
+
tierConfigs[i] = JB721TierConfig({
|
|
1293
|
+
price: uint104((i + 1) * 10),
|
|
1294
|
+
initialSupply: uint32(100),
|
|
1295
|
+
votingUnits: uint16(0),
|
|
1296
|
+
reserveFrequency: uint16(i),
|
|
1297
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1298
|
+
encodedIPFSUri: tokenUris[0],
|
|
1299
|
+
category: uint24(100),
|
|
1300
|
+
discountPercent: uint8(0),
|
|
1301
|
+
allowOwnerMint: false,
|
|
1302
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1303
|
+
transfersPausable: false,
|
|
1304
|
+
useVotingUnits: false,
|
|
1305
|
+
cannotBeRemoved: false,
|
|
1306
|
+
cannotIncreaseDiscountPercent: false
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
1311
|
+
ForTest_JB721TiersHook hook = new ForTest_JB721TiersHook(
|
|
1312
|
+
projectId,
|
|
1313
|
+
IJBDirectory(mockJBDirectory),
|
|
1314
|
+
name,
|
|
1315
|
+
symbol,
|
|
1316
|
+
IJBRulesets(mockJBRulesets),
|
|
1317
|
+
baseUri,
|
|
1318
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1319
|
+
contractUri,
|
|
1320
|
+
tierConfigs,
|
|
1321
|
+
IJB721TiersHookStore(address(store)),
|
|
1322
|
+
JB721TiersHookFlags({
|
|
1323
|
+
preventOverspending: false,
|
|
1324
|
+
noNewTiersWithReserves: false,
|
|
1325
|
+
noNewTiersWithVotes: true, // <-- This is the flag we're testing.
|
|
1326
|
+
noNewTiersWithOwnerMinting: true
|
|
1327
|
+
})
|
|
1328
|
+
);
|
|
1329
|
+
hook.transferOwnership(owner);
|
|
1330
|
+
|
|
1331
|
+
JB721TierConfig[] memory tierConfigsToAdd = new JB721TierConfig[](numberTiersToAdd);
|
|
1332
|
+
for (uint256 i; i < numberTiersToAdd; i++) {
|
|
1333
|
+
tierConfigsToAdd[i] = JB721TierConfig({
|
|
1334
|
+
price: uint104((i + 1) * 100),
|
|
1335
|
+
initialSupply: uint32(100),
|
|
1336
|
+
votingUnits: uint16(0),
|
|
1337
|
+
reserveFrequency: uint16(i),
|
|
1338
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1339
|
+
encodedIPFSUri: tokenUris[0],
|
|
1340
|
+
category: uint24(100),
|
|
1341
|
+
discountPercent: uint8(0),
|
|
1342
|
+
allowOwnerMint: false,
|
|
1343
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1344
|
+
transfersPausable: false,
|
|
1345
|
+
useVotingUnits: false, // <-- If false, voting power is based on tier price
|
|
1346
|
+
cannotBeRemoved: false,
|
|
1347
|
+
cannotIncreaseDiscountPercent: false
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Expect the `adjustTiers` call to revert because of the `noNewTiersWithVotes` flag.
|
|
1352
|
+
vm.expectRevert(
|
|
1353
|
+
abi.encodeWithSelector(
|
|
1354
|
+
JB721TiersHookStore.JB721TiersHookStore_VotingUnitsNotAllowed.selector, initialNumberOfTiers + 1
|
|
1355
|
+
)
|
|
1356
|
+
);
|
|
1357
|
+
vm.prank(owner);
|
|
1358
|
+
hook.adjustTiers(tierConfigsToAdd, new uint256[](0));
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function test_adjustTiers_revertIfMoreVotingUnitsNotAllowedUsingVotingUnits(
|
|
1362
|
+
uint256 initialNumberOfTiers,
|
|
1363
|
+
uint256 numberTiersToAdd
|
|
1364
|
+
)
|
|
1365
|
+
public
|
|
1366
|
+
{
|
|
1367
|
+
// Include adding X new tiers with 0 current tiers.
|
|
1368
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 0, 15);
|
|
1369
|
+
numberTiersToAdd = bound(numberTiersToAdd, 1, 15);
|
|
1370
|
+
|
|
1371
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
1372
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
1373
|
+
tierConfigs[i] = JB721TierConfig({
|
|
1374
|
+
price: uint104((i + 1) * 10),
|
|
1375
|
+
initialSupply: uint32(100),
|
|
1376
|
+
votingUnits: uint16(0),
|
|
1377
|
+
reserveFrequency: uint16(i),
|
|
1378
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1379
|
+
encodedIPFSUri: tokenUris[0],
|
|
1380
|
+
category: uint24(100),
|
|
1381
|
+
discountPercent: uint8(0),
|
|
1382
|
+
allowOwnerMint: false,
|
|
1383
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1384
|
+
transfersPausable: false,
|
|
1385
|
+
useVotingUnits: true,
|
|
1386
|
+
cannotBeRemoved: false,
|
|
1387
|
+
cannotIncreaseDiscountPercent: false
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
1392
|
+
ForTest_JB721TiersHook hook = new ForTest_JB721TiersHook(
|
|
1393
|
+
projectId,
|
|
1394
|
+
IJBDirectory(mockJBDirectory),
|
|
1395
|
+
name,
|
|
1396
|
+
symbol,
|
|
1397
|
+
IJBRulesets(mockJBRulesets),
|
|
1398
|
+
baseUri,
|
|
1399
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1400
|
+
contractUri,
|
|
1401
|
+
tierConfigs,
|
|
1402
|
+
IJB721TiersHookStore(address(store)),
|
|
1403
|
+
JB721TiersHookFlags({
|
|
1404
|
+
preventOverspending: false,
|
|
1405
|
+
noNewTiersWithReserves: false,
|
|
1406
|
+
noNewTiersWithVotes: true, // <-- This is the flag we're testing.
|
|
1407
|
+
noNewTiersWithOwnerMinting: true
|
|
1408
|
+
})
|
|
1409
|
+
);
|
|
1410
|
+
hook.transferOwnership(owner);
|
|
1411
|
+
|
|
1412
|
+
JB721TierConfig[] memory tierConfigsToAdd = new JB721TierConfig[](numberTiersToAdd);
|
|
1413
|
+
for (uint256 i; i < numberTiersToAdd; i++) {
|
|
1414
|
+
tierConfigsToAdd[i] = JB721TierConfig({
|
|
1415
|
+
price: uint104((i + 1) * 100),
|
|
1416
|
+
initialSupply: uint32(100),
|
|
1417
|
+
votingUnits: uint16(0),
|
|
1418
|
+
reserveFrequency: uint16(i),
|
|
1419
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1420
|
+
encodedIPFSUri: tokenUris[0],
|
|
1421
|
+
category: uint24(100),
|
|
1422
|
+
discountPercent: uint8(0),
|
|
1423
|
+
allowOwnerMint: false,
|
|
1424
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1425
|
+
transfersPausable: false,
|
|
1426
|
+
useVotingUnits: true, // <-- If false, voting power is based on tier price
|
|
1427
|
+
cannotBeRemoved: false,
|
|
1428
|
+
cannotIncreaseDiscountPercent: false
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// One new tier has a non-0 voting power
|
|
1433
|
+
tierConfigsToAdd[numberTiersToAdd - 1].votingUnits = uint16(1);
|
|
1434
|
+
|
|
1435
|
+
// Expect the `adjustTiers` call to revert because of the `noNewTiersWithVotes` flag.
|
|
1436
|
+
vm.expectRevert(
|
|
1437
|
+
abi.encodeWithSelector(
|
|
1438
|
+
JB721TiersHookStore.JB721TiersHookStore_VotingUnitsNotAllowed.selector,
|
|
1439
|
+
initialNumberOfTiers + numberTiersToAdd
|
|
1440
|
+
)
|
|
1441
|
+
);
|
|
1442
|
+
vm.prank(owner);
|
|
1443
|
+
hook.adjustTiers(tierConfigsToAdd, new uint256[](0));
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function test_cleanTiers_removeInactiveTiers(
|
|
1447
|
+
uint256 initialNumberOfTiers,
|
|
1448
|
+
uint256 seed,
|
|
1449
|
+
uint256 numberOfTiersToRemove
|
|
1450
|
+
)
|
|
1451
|
+
public
|
|
1452
|
+
{
|
|
1453
|
+
// Include adding X new tiers with 0 current tiers.
|
|
1454
|
+
initialNumberOfTiers = bound(initialNumberOfTiers, 1, 15);
|
|
1455
|
+
numberOfTiersToRemove = bound(numberOfTiersToRemove, 0, initialNumberOfTiers - 1);
|
|
1456
|
+
|
|
1457
|
+
// Create random tiers to remove.
|
|
1458
|
+
uint256[] memory tiersToRemove = new uint256[](numberOfTiersToRemove);
|
|
1459
|
+
// Use `seed` to generate new random tiers, and iterate on `i` to fill the `tiersToRemove` array.
|
|
1460
|
+
for (uint256 i; i < numberOfTiersToRemove;) {
|
|
1461
|
+
uint256 newTierCandidate = uint256(keccak256(abi.encode(seed))) % initialNumberOfTiers;
|
|
1462
|
+
bool invalidTier;
|
|
1463
|
+
if (newTierCandidate != 0) {
|
|
1464
|
+
for (uint256 j; j < numberOfTiersToRemove; j++) {
|
|
1465
|
+
// Same value twice?
|
|
1466
|
+
if (newTierCandidate == tiersToRemove[j]) {
|
|
1467
|
+
invalidTier = true;
|
|
1468
|
+
break;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
if (!invalidTier) {
|
|
1472
|
+
tiersToRemove[i] = newTierCandidate;
|
|
1473
|
+
i++;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
// Overflow to loop over (the seed is fuzzed, and may start at max(uint256)).
|
|
1477
|
+
unchecked {
|
|
1478
|
+
seed++;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
// Order the tiers to remove for event matching (which are ordered too).
|
|
1482
|
+
tiersToRemove = _sortArray(tiersToRemove);
|
|
1483
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](initialNumberOfTiers);
|
|
1484
|
+
JB721Tier[] memory tiers = new JB721Tier[](initialNumberOfTiers);
|
|
1485
|
+
for (uint256 i; i < initialNumberOfTiers; i++) {
|
|
1486
|
+
tierConfigs[i] = JB721TierConfig({
|
|
1487
|
+
price: uint104((i + 1) * 10),
|
|
1488
|
+
initialSupply: uint32(100),
|
|
1489
|
+
votingUnits: uint16(0),
|
|
1490
|
+
reserveFrequency: uint16(i),
|
|
1491
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1492
|
+
encodedIPFSUri: tokenUris[0],
|
|
1493
|
+
category: uint24(100),
|
|
1494
|
+
discountPercent: uint8(0),
|
|
1495
|
+
allowOwnerMint: false,
|
|
1496
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1497
|
+
transfersPausable: false,
|
|
1498
|
+
useVotingUnits: false,
|
|
1499
|
+
cannotBeRemoved: false,
|
|
1500
|
+
cannotIncreaseDiscountPercent: false
|
|
1501
|
+
});
|
|
1502
|
+
tiers[i] = JB721Tier({
|
|
1503
|
+
id: uint32(i + 1),
|
|
1504
|
+
price: tierConfigs[i].price,
|
|
1505
|
+
remainingSupply: tierConfigs[i].initialSupply,
|
|
1506
|
+
initialSupply: tierConfigs[i].initialSupply,
|
|
1507
|
+
votingUnits: tierConfigs[i].votingUnits,
|
|
1508
|
+
reserveFrequency: tierConfigs[i].reserveFrequency,
|
|
1509
|
+
reserveBeneficiary: i == 0 ? address(0) : tierConfigs[i].reserveBeneficiary,
|
|
1510
|
+
encodedIPFSUri: tierConfigs[i].encodedIPFSUri,
|
|
1511
|
+
category: tierConfigs[i].category,
|
|
1512
|
+
discountPercent: tierConfigs[i].discountPercent,
|
|
1513
|
+
allowOwnerMint: tierConfigs[i].allowOwnerMint,
|
|
1514
|
+
transfersPausable: tierConfigs[i].transfersPausable,
|
|
1515
|
+
cannotBeRemoved: tierConfigs[i].cannotBeRemoved,
|
|
1516
|
+
cannotIncreaseDiscountPercent: tierConfigs[i].cannotIncreaseDiscountPercent,
|
|
1517
|
+
resolvedUri: ""
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
1521
|
+
ForTest_JB721TiersHook hook = new ForTest_JB721TiersHook(
|
|
1522
|
+
projectId,
|
|
1523
|
+
IJBDirectory(mockJBDirectory),
|
|
1524
|
+
name,
|
|
1525
|
+
symbol,
|
|
1526
|
+
IJBRulesets(mockJBRulesets),
|
|
1527
|
+
baseUri,
|
|
1528
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1529
|
+
contractUri,
|
|
1530
|
+
tierConfigs,
|
|
1531
|
+
IJB721TiersHookStore(address(store)),
|
|
1532
|
+
JB721TiersHookFlags({
|
|
1533
|
+
preventOverspending: false,
|
|
1534
|
+
noNewTiersWithReserves: false,
|
|
1535
|
+
noNewTiersWithVotes: false,
|
|
1536
|
+
noNewTiersWithOwnerMinting: true
|
|
1537
|
+
})
|
|
1538
|
+
);
|
|
1539
|
+
hook.transferOwnership(owner);
|
|
1540
|
+
// Will be resized later
|
|
1541
|
+
JB721TierConfig[] memory tierConfigsRemaining = new JB721TierConfig[](initialNumberOfTiers);
|
|
1542
|
+
JB721Tier[] memory tiersRemaining = new JB721Tier[](initialNumberOfTiers);
|
|
1543
|
+
for (uint256 i; i < tiers.length; i++) {
|
|
1544
|
+
tierConfigsRemaining[i] = tierConfigs[i];
|
|
1545
|
+
tiersRemaining[i] = tiers[i];
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Iterate through the remaining tiers and remove the ones in `tiersToRemove`.
|
|
1549
|
+
// Do this by "swapping" the tier to remove with the last element in the array, and then "popping" that last
|
|
1550
|
+
// element.
|
|
1551
|
+
for (uint256 i; i < tiersRemaining.length;) {
|
|
1552
|
+
bool swappedAndPopped;
|
|
1553
|
+
for (uint256 j; j < tiersToRemove.length; j++) {
|
|
1554
|
+
if (tiersRemaining[i].id == tiersToRemove[j]) {
|
|
1555
|
+
// Swap and pop tiers removed
|
|
1556
|
+
tiersRemaining[i] = tiersRemaining[tiersRemaining.length - 1];
|
|
1557
|
+
tierConfigsRemaining[i] = tierConfigsRemaining[tierConfigsRemaining.length - 1];
|
|
1558
|
+
// Remove the last elelment / reduce array length by 1
|
|
1559
|
+
assembly ("memory-safe") {
|
|
1560
|
+
mstore(tiersRemaining, sub(mload(tiersRemaining), 1))
|
|
1561
|
+
mstore(tierConfigsRemaining, sub(mload(tierConfigsRemaining), 1))
|
|
1562
|
+
}
|
|
1563
|
+
swappedAndPopped = true;
|
|
1564
|
+
break;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (!swappedAndPopped) i++;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
vm.prank(owner);
|
|
1571
|
+
hook.adjustTiers(new JB721TierConfig[](0), tiersToRemove);
|
|
1572
|
+
JB721Tier[] memory tiersListDump = hook.test_store().ForTest_dumpTiersList(address(hook));
|
|
1573
|
+
// Check: are all of the tiers are still in the linked list (both active and inactive)?
|
|
1574
|
+
assertTrue(_isIn(tiers, tiersListDump));
|
|
1575
|
+
// Check: does the linked list include only the tiers (active and inactives)?
|
|
1576
|
+
assertTrue(_isIn(tiersListDump, tiers));
|
|
1577
|
+
// Check: was the correct event emitted?
|
|
1578
|
+
vm.expectEmit(true, false, false, true, address(hook.test_store()));
|
|
1579
|
+
emit CleanTiers(address(hook), beneficiary);
|
|
1580
|
+
vm.startPrank(beneficiary);
|
|
1581
|
+
hook.test_store().cleanTiers(address(hook));
|
|
1582
|
+
vm.stopPrank();
|
|
1583
|
+
tiersListDump = hook.test_store().ForTest_dumpTiersList(address(hook));
|
|
1584
|
+
// Check: is the correct number of tiers remaining?
|
|
1585
|
+
assertEq(tiersListDump.length, initialNumberOfTiers - numberOfTiersToRemove);
|
|
1586
|
+
// Check: are all of the remaining tiers in the dump?
|
|
1587
|
+
assertTrue(_isIn(tiersRemaining, tiersListDump));
|
|
1588
|
+
// Check: does the dump include any tiers which shouldn't be remaining?
|
|
1589
|
+
assertTrue(_isIn(tiersListDump, tiersRemaining));
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
function test_tiersOf_emptyArrayIfNoInitializedTiers(uint256 size) public {
|
|
1593
|
+
// Initialize a hook without default tiers.
|
|
1594
|
+
JB721TiersHook hook = _initHookDefaultTiers(0);
|
|
1595
|
+
|
|
1596
|
+
// Try to get `size` tiers
|
|
1597
|
+
JB721Tier[] memory intialTiers = hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, size);
|
|
1598
|
+
|
|
1599
|
+
// Check: does the array have a length of 0?
|
|
1600
|
+
assertEq(intialTiers.length, 0, "Length mismatch.");
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function test_setDiscountPercentOf_revertIfCannotIncreaseDiscount() public {
|
|
1604
|
+
// Initial tier config and data.
|
|
1605
|
+
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
|
|
1606
|
+
tierConfigs[0] = JB721TierConfig({
|
|
1607
|
+
price: 10,
|
|
1608
|
+
initialSupply: uint32(100),
|
|
1609
|
+
votingUnits: uint16(0),
|
|
1610
|
+
reserveFrequency: uint16(0),
|
|
1611
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1612
|
+
encodedIPFSUri: tokenUris[0],
|
|
1613
|
+
category: uint24(100),
|
|
1614
|
+
discountPercent: uint8(0),
|
|
1615
|
+
allowOwnerMint: false,
|
|
1616
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1617
|
+
transfersPausable: false,
|
|
1618
|
+
useVotingUnits: true,
|
|
1619
|
+
cannotBeRemoved: true,
|
|
1620
|
+
cannotIncreaseDiscountPercent: true
|
|
1621
|
+
});
|
|
1622
|
+
// Deploy the hook and its store with the initial tiers.
|
|
1623
|
+
vm.etch(hook_i, address(hook).code);
|
|
1624
|
+
JB721TiersHook hook = JB721TiersHook(hook_i);
|
|
1625
|
+
hook.initialize(
|
|
1626
|
+
projectId,
|
|
1627
|
+
name,
|
|
1628
|
+
symbol,
|
|
1629
|
+
baseUri,
|
|
1630
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1631
|
+
contractUri,
|
|
1632
|
+
JB721InitTiersConfig({
|
|
1633
|
+
tiers: tierConfigs,
|
|
1634
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1635
|
+
decimals: 18,
|
|
1636
|
+
prices: IJBPrices(address(0))
|
|
1637
|
+
}),
|
|
1638
|
+
JB721TiersHookFlags({
|
|
1639
|
+
preventOverspending: false,
|
|
1640
|
+
noNewTiersWithReserves: false,
|
|
1641
|
+
noNewTiersWithVotes: false,
|
|
1642
|
+
noNewTiersWithOwnerMinting: true
|
|
1643
|
+
})
|
|
1644
|
+
);
|
|
1645
|
+
hook.transferOwnership(owner);
|
|
1646
|
+
|
|
1647
|
+
// Expect the `setDiscountPercentOf` call to revert because of the flag.
|
|
1648
|
+
vm.expectRevert(
|
|
1649
|
+
abi.encodeWithSelector(
|
|
1650
|
+
JB721TiersHookStore.JB721TiersHookStore_DiscountPercentIncreaseNotAllowed.selector, 100, 0
|
|
1651
|
+
)
|
|
1652
|
+
);
|
|
1653
|
+
vm.prank(owner);
|
|
1654
|
+
// Attempt to increase the discount of the first tier to 100%
|
|
1655
|
+
hook.setDiscountPercentOf(1, 100);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
function test_setDiscountPercentsOf_revertIfCannotIncreaseDiscounts() public {
|
|
1659
|
+
// Initial tier config and data.
|
|
1660
|
+
JB721TierConfig[] memory initialConfig = new JB721TierConfig[](2);
|
|
1661
|
+
initialConfig[0] = JB721TierConfig({
|
|
1662
|
+
price: 10,
|
|
1663
|
+
initialSupply: uint32(100),
|
|
1664
|
+
votingUnits: uint16(0),
|
|
1665
|
+
reserveFrequency: uint16(0),
|
|
1666
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1667
|
+
encodedIPFSUri: tokenUris[0],
|
|
1668
|
+
category: uint24(100),
|
|
1669
|
+
discountPercent: uint8(0),
|
|
1670
|
+
allowOwnerMint: false,
|
|
1671
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1672
|
+
transfersPausable: false,
|
|
1673
|
+
useVotingUnits: true,
|
|
1674
|
+
cannotBeRemoved: true,
|
|
1675
|
+
cannotIncreaseDiscountPercent: true
|
|
1676
|
+
});
|
|
1677
|
+
initialConfig[1] = JB721TierConfig({
|
|
1678
|
+
price: 10,
|
|
1679
|
+
initialSupply: uint32(100),
|
|
1680
|
+
votingUnits: uint16(0),
|
|
1681
|
+
reserveFrequency: uint16(0),
|
|
1682
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
1683
|
+
encodedIPFSUri: tokenUris[0],
|
|
1684
|
+
category: uint24(100),
|
|
1685
|
+
discountPercent: uint8(0),
|
|
1686
|
+
allowOwnerMint: false,
|
|
1687
|
+
useReserveBeneficiaryAsDefault: false,
|
|
1688
|
+
transfersPausable: false,
|
|
1689
|
+
useVotingUnits: true,
|
|
1690
|
+
cannotBeRemoved: true,
|
|
1691
|
+
cannotIncreaseDiscountPercent: false
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
// Deploy the hook and its store with the initial tiers.
|
|
1695
|
+
vm.etch(hook_i, address(hook).code);
|
|
1696
|
+
JB721TiersHook hook = JB721TiersHook(hook_i);
|
|
1697
|
+
hook.initialize(
|
|
1698
|
+
projectId,
|
|
1699
|
+
name,
|
|
1700
|
+
symbol,
|
|
1701
|
+
baseUri,
|
|
1702
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
1703
|
+
contractUri,
|
|
1704
|
+
JB721InitTiersConfig({
|
|
1705
|
+
tiers: initialConfig,
|
|
1706
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1707
|
+
decimals: 18,
|
|
1708
|
+
prices: IJBPrices(address(0))
|
|
1709
|
+
}),
|
|
1710
|
+
JB721TiersHookFlags({
|
|
1711
|
+
preventOverspending: false,
|
|
1712
|
+
noNewTiersWithReserves: false,
|
|
1713
|
+
noNewTiersWithVotes: false,
|
|
1714
|
+
noNewTiersWithOwnerMinting: true
|
|
1715
|
+
})
|
|
1716
|
+
);
|
|
1717
|
+
hook.transferOwnership(owner);
|
|
1718
|
+
|
|
1719
|
+
// Build calldata for increasing multiple tier discounts at once.
|
|
1720
|
+
JB721TiersSetDiscountPercentConfig[] memory discountCalldata = new JB721TiersSetDiscountPercentConfig[](2);
|
|
1721
|
+
discountCalldata[0] = JB721TiersSetDiscountPercentConfig({
|
|
1722
|
+
tierId: 1,
|
|
1723
|
+
discountPercent: 100 // invalid
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
discountCalldata[1] = JB721TiersSetDiscountPercentConfig({
|
|
1727
|
+
tierId: 2,
|
|
1728
|
+
discountPercent: 100 // valid
|
|
1729
|
+
});
|
|
1730
|
+
|
|
1731
|
+
// Expect the `setDiscountPercentsOf` call to revert because of the flag.
|
|
1732
|
+
vm.expectRevert(
|
|
1733
|
+
abi.encodeWithSelector(
|
|
1734
|
+
JB721TiersHookStore.JB721TiersHookStore_DiscountPercentIncreaseNotAllowed.selector, 100, 0
|
|
1735
|
+
)
|
|
1736
|
+
);
|
|
1737
|
+
vm.prank(owner);
|
|
1738
|
+
hook.setDiscountPercentsOf(discountCalldata);
|
|
1739
|
+
}
|
|
1740
|
+
}
|