@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,103 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
5
|
+
import "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
6
|
+
import "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
7
|
+
import "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
8
|
+
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
9
|
+
|
|
10
|
+
import "../../src/JB721TiersHookProjectDeployer.sol";
|
|
11
|
+
import "../../src/JB721TiersHookStore.sol";
|
|
12
|
+
import "../../src/interfaces/IJB721TiersHookProjectDeployer.sol";
|
|
13
|
+
import "../../src/structs/JBLaunchProjectConfig.sol";
|
|
14
|
+
import "../../src/structs/JB721InitTiersConfig.sol";
|
|
15
|
+
|
|
16
|
+
import "../utils/UnitTestSetup.sol";
|
|
17
|
+
|
|
18
|
+
/// @dev A minimal mock for IJBProjects whose `count()` can be bumped by the mock controller.
|
|
19
|
+
contract MockJBProjectsCount {
|
|
20
|
+
uint256 private _count;
|
|
21
|
+
address private _owner;
|
|
22
|
+
|
|
23
|
+
function setup(uint256 initialCount, address projectOwner) external {
|
|
24
|
+
_count = initialCount;
|
|
25
|
+
_owner = projectOwner;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function count() external view returns (uint256) {
|
|
29
|
+
return _count;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setCount(uint256 newCount) external {
|
|
33
|
+
_count = newCount;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ownerOf(uint256) external view returns (address) {
|
|
37
|
+
return _owner;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// @dev A mock controller whose fallback bumps the projects mock count by 1 (simulating real behaviour)
|
|
42
|
+
/// and returns a truthy value for any function call.
|
|
43
|
+
contract MockLaunchController {
|
|
44
|
+
MockJBProjectsCount private _projects;
|
|
45
|
+
|
|
46
|
+
constructor(MockJBProjectsCount projects) {
|
|
47
|
+
_projects = projects;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fallback() external payable {
|
|
51
|
+
// Bump projects count by 1 -- simulates a new project being created.
|
|
52
|
+
_projects.setCount(_projects.count() + 1);
|
|
53
|
+
// Return `uint256(1)` which is truthy for the deployer's expected return.
|
|
54
|
+
bytes memory result = abi.encode(uint256(1));
|
|
55
|
+
assembly {
|
|
56
|
+
return(add(result, 32), mload(result))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
contract Test_ProjectDeployer_Unit is UnitTestSetup {
|
|
62
|
+
using stdStorage for StdStorage;
|
|
63
|
+
|
|
64
|
+
IJB721TiersHookProjectDeployer deployer;
|
|
65
|
+
|
|
66
|
+
function setUp() public override {
|
|
67
|
+
super.setUp();
|
|
68
|
+
|
|
69
|
+
deployer = new JB721TiersHookProjectDeployer(
|
|
70
|
+
IJBDirectory(mockJBDirectory), IJBPermissions(mockJBPermissions), jbHookDeployer, address(0)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function test_launchProjectFor_shouldLaunchProject(uint256 previousProjectId, bytes32 salt) external {
|
|
75
|
+
// Include launching the protocol project (project ID 1).
|
|
76
|
+
previousProjectId = bound(previousProjectId, 0, type(uint88).max - 1);
|
|
77
|
+
|
|
78
|
+
(JBDeploy721TiersHookConfig memory deploy721TiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
|
|
79
|
+
createData();
|
|
80
|
+
|
|
81
|
+
// Deploy a real MockJBProjectsCount implementation and etch its code onto the existing mockJBProjects address.
|
|
82
|
+
// This is necessary because the hook's immutable PROJECTS is set to mockJBProjects during the hook
|
|
83
|
+
// implementation's constructor (in UnitTestSetup.setUp). By etching real contract code there, both the
|
|
84
|
+
// deployer's DIRECTORY.PROJECTS().count() path and the hook's PROJECTS.count() path hit the same contract.
|
|
85
|
+
MockJBProjectsCount projectsImpl = new MockJBProjectsCount();
|
|
86
|
+
vm.etch(mockJBProjects, address(projectsImpl).code);
|
|
87
|
+
MockJBProjectsCount(mockJBProjects).setup(previousProjectId, owner);
|
|
88
|
+
|
|
89
|
+
// Mock DIRECTORY.PROJECTS() to return mockJBProjects (which now has real code).
|
|
90
|
+
vm.mockCall(mockJBDirectory, abi.encodeWithSelector(IJBDirectory.PROJECTS.selector), abi.encode(mockJBProjects));
|
|
91
|
+
|
|
92
|
+
// Deploy a mock controller that bumps count when launchProjectFor is called.
|
|
93
|
+
MockLaunchController mockController = new MockLaunchController(MockJBProjectsCount(mockJBProjects));
|
|
94
|
+
|
|
95
|
+
// Launch the project using our mock controller that bumps count.
|
|
96
|
+
(uint256 projectId,) = deployer.launchProjectFor(
|
|
97
|
+
owner, deploy721TiersHookConfig, launchProjectConfig, IJBController(address(mockController)), salt
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Check: does the project have the correct project ID (the previous ID incremented by 1)?
|
|
101
|
+
assertEq(previousProjectId, projectId - 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import "../utils/UnitTestSetup.sol";
|
|
5
|
+
|
|
6
|
+
contract Test_Getters_Constructor_Unit is UnitTestSetup {
|
|
7
|
+
using stdStorage for StdStorage;
|
|
8
|
+
|
|
9
|
+
function test_tiersOf_returnsAllTiers(uint256 numberOfTiers) public {
|
|
10
|
+
numberOfTiers = bound(numberOfTiers, 0, 30);
|
|
11
|
+
|
|
12
|
+
(, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
|
|
13
|
+
|
|
14
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
15
|
+
|
|
16
|
+
// Check: is everything from `tiersOf` in `tiers`, and vice versa (do they match)?
|
|
17
|
+
assertTrue(_isIn(hook.test_store().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers), tiers));
|
|
18
|
+
assertTrue(_isIn(tiers, hook.test_store().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function test_pricingContext_packingFunctionsAsExpected(
|
|
22
|
+
uint32 currency,
|
|
23
|
+
uint8 decimals,
|
|
24
|
+
address prices,
|
|
25
|
+
bytes32 salt
|
|
26
|
+
)
|
|
27
|
+
public
|
|
28
|
+
{
|
|
29
|
+
// Decimals must be <= 18 per validation in initialize.
|
|
30
|
+
vm.assume(decimals <= 18);
|
|
31
|
+
JBDeploy721TiersHookConfig memory hookConfig = JBDeploy721TiersHookConfig(
|
|
32
|
+
name,
|
|
33
|
+
symbol,
|
|
34
|
+
baseUri,
|
|
35
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
36
|
+
contractUri,
|
|
37
|
+
JB721InitTiersConfig({tiers: tiers, currency: currency, decimals: decimals, prices: IJBPrices(prices)}),
|
|
38
|
+
address(0),
|
|
39
|
+
JB721TiersHookFlags({
|
|
40
|
+
preventOverspending: false,
|
|
41
|
+
noNewTiersWithReserves: true,
|
|
42
|
+
noNewTiersWithVotes: true,
|
|
43
|
+
noNewTiersWithOwnerMinting: true
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
JB721TiersHook hook = JB721TiersHook(address(jbHookDeployer.deployHookFor(projectId, hookConfig, salt)));
|
|
48
|
+
|
|
49
|
+
(uint256 currency2, uint256 decimals2, IJBPrices prices2) = hook.pricingContext();
|
|
50
|
+
// Check: do the unpacked values from `pricingContext` match the values we used in the config?
|
|
51
|
+
assertEq(currency2, uint256(currency));
|
|
52
|
+
assertEq(decimals2, uint256(decimals));
|
|
53
|
+
assertEq(address(prices2), prices);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function test_bools_doesPackingAndUnpackingWork(bool a, bool b, bool c, bool d, bool e) public {
|
|
57
|
+
ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
|
|
58
|
+
uint8 packed = store.ForTest_packBools(a, b, c, d, e);
|
|
59
|
+
(bool a2, bool b2, bool c2, bool d2, bool e2) = store.ForTest_unpackBools(packed);
|
|
60
|
+
// Check: do the packed values match the unpacked values?
|
|
61
|
+
assertEq(a, a2);
|
|
62
|
+
assertEq(b, b2);
|
|
63
|
+
assertEq(c, c2);
|
|
64
|
+
assertEq(d, d2);
|
|
65
|
+
assertEq(e, e2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function test_tiersOf_returnsAllTiersWithResolver(uint256 numberOfTiers) public {
|
|
69
|
+
numberOfTiers = bound(numberOfTiers, 0, 30);
|
|
70
|
+
|
|
71
|
+
// Use a non-null resolved URI.
|
|
72
|
+
defaultTierConfig.encodedIPFSUri = bytes32(hex"69");
|
|
73
|
+
|
|
74
|
+
(, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
|
|
75
|
+
|
|
76
|
+
mockTokenUriResolver = makeAddr("mockTokenUriResolver");
|
|
77
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
78
|
+
|
|
79
|
+
for (uint256 i; i < numberOfTiers; i++) {
|
|
80
|
+
// Mock the URI resolver call
|
|
81
|
+
mockAndExpect(
|
|
82
|
+
mockTokenUriResolver,
|
|
83
|
+
abi.encodeWithSelector(
|
|
84
|
+
IJB721TokenUriResolver.tokenUriOf.selector, address(hook), _generateTokenId(i + 1, 0)
|
|
85
|
+
),
|
|
86
|
+
abi.encode(string(abi.encodePacked("resolverURI", _generateTokenId(i + 1, 0))))
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check: is everything from `tiersOf` in `tiers`, and vice versa (do they match)? Do the resolved URIs match?
|
|
91
|
+
assertTrue(_isIn(hook.test_store().tiersOf(address(hook), new uint256[](0), true, 0, 100), tiers));
|
|
92
|
+
assertTrue(_isIn(tiers, hook.test_store().tiersOf(address(hook), new uint256[](0), true, 0, 100)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function test_tiersOf_returnsAllTiersExcludingRemovedOnes(
|
|
96
|
+
uint256 numberOfTiers,
|
|
97
|
+
uint256 firstRemovedTier,
|
|
98
|
+
uint256 secondRemovedTier
|
|
99
|
+
)
|
|
100
|
+
public
|
|
101
|
+
{
|
|
102
|
+
numberOfTiers = bound(numberOfTiers, 1, 30);
|
|
103
|
+
firstRemovedTier = bound(firstRemovedTier, 1, numberOfTiers);
|
|
104
|
+
secondRemovedTier = bound(secondRemovedTier, 1, numberOfTiers);
|
|
105
|
+
vm.assume(firstRemovedTier != secondRemovedTier);
|
|
106
|
+
|
|
107
|
+
(, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
|
|
108
|
+
|
|
109
|
+
// Only copy the tiers we keep.
|
|
110
|
+
JB721Tier[] memory nonRemovedTiers = new JB721Tier[](numberOfTiers - 2);
|
|
111
|
+
uint256 j;
|
|
112
|
+
for (uint256 i; i < numberOfTiers; i++) {
|
|
113
|
+
if (i != firstRemovedTier - 1 && i != secondRemovedTier - 1) {
|
|
114
|
+
nonRemovedTiers[j] = tiers[i];
|
|
115
|
+
j++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
120
|
+
|
|
121
|
+
// Set the removed tiers.
|
|
122
|
+
hook.test_store().ForTest_setIsTierRemoved(address(hook), firstRemovedTier);
|
|
123
|
+
hook.test_store().ForTest_setIsTierRemoved(address(hook), secondRemovedTier);
|
|
124
|
+
|
|
125
|
+
JB721Tier[] memory storedTiers =
|
|
126
|
+
hook.test_store().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers);
|
|
127
|
+
|
|
128
|
+
// Check: was the returned tier array resized correctly?
|
|
129
|
+
assertEq(storedTiers.length, numberOfTiers - 2);
|
|
130
|
+
|
|
131
|
+
// Check: is everything from `storedTiers` a `nonRemovedTier`, and vice versa (do they match)?
|
|
132
|
+
assertTrue(_isIn(storedTiers, nonRemovedTiers));
|
|
133
|
+
assertTrue(_isIn(nonRemovedTiers, storedTiers));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function test_tierOf_returnsAGivenTier(uint256 numberOfTiers, uint16 givenTier) public {
|
|
137
|
+
numberOfTiers = bound(numberOfTiers, 0, 30);
|
|
138
|
+
|
|
139
|
+
(, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
|
|
140
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
141
|
+
|
|
142
|
+
// Check: if the tier exists, it is returned correctly?
|
|
143
|
+
if (givenTier <= numberOfTiers && givenTier != 0) {
|
|
144
|
+
assertEq(hook.test_store().tierOf(address(hook), givenTier, false), tiers[givenTier - 1]);
|
|
145
|
+
} else {
|
|
146
|
+
assertEq( // Check: if the tier doesn't exist, is an empty tier returned?
|
|
147
|
+
hook.test_store().tierOf(address(hook), givenTier, false),
|
|
148
|
+
JB721Tier({
|
|
149
|
+
id: givenTier,
|
|
150
|
+
price: 0,
|
|
151
|
+
remainingSupply: 0,
|
|
152
|
+
initialSupply: 0,
|
|
153
|
+
votingUnits: 0,
|
|
154
|
+
reserveFrequency: 0,
|
|
155
|
+
reserveBeneficiary: address(0),
|
|
156
|
+
encodedIPFSUri: bytes32(0),
|
|
157
|
+
category: uint24(100),
|
|
158
|
+
discountPercent: uint8(0),
|
|
159
|
+
allowOwnerMint: false,
|
|
160
|
+
transfersPausable: false,
|
|
161
|
+
cannotBeRemoved: false,
|
|
162
|
+
cannotIncreaseDiscountPercent: false,
|
|
163
|
+
resolvedUri: ""
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function test_totalSupplyOf_returnsTotalSupply(uint256 numberOfTiers) public {
|
|
170
|
+
numberOfTiers = bound(numberOfTiers, 0, 30);
|
|
171
|
+
|
|
172
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
173
|
+
|
|
174
|
+
// Initialize `numberOfTiers` tiers with an initial supply of 100, and (i + 1) mints.
|
|
175
|
+
// This should yield a total supply of (`numberOfTiers` * (`numberOfTiers` + 1)) / 2,
|
|
176
|
+
// which is the sum of natural numbers from 1 to `numberOfTiers`.
|
|
177
|
+
for (uint256 i; i < numberOfTiers; i++) {
|
|
178
|
+
hook.test_store()
|
|
179
|
+
.ForTest_setTier(
|
|
180
|
+
address(hook),
|
|
181
|
+
i + 1,
|
|
182
|
+
JBStored721Tier({
|
|
183
|
+
price: uint104((i + 1) * 10),
|
|
184
|
+
remainingSupply: uint32(100 - (i + 1)),
|
|
185
|
+
initialSupply: uint32(100),
|
|
186
|
+
votingUnits: uint16(0),
|
|
187
|
+
reserveFrequency: uint16(0),
|
|
188
|
+
category: uint24(100),
|
|
189
|
+
discountPercent: uint8(0),
|
|
190
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false)
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check: does the total supply match the expected value?
|
|
196
|
+
assertEq(hook.test_store().totalSupplyOf(address(hook)), ((numberOfTiers * (numberOfTiers + 1)) / 2));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function test_balanceOf_returnsCompleteBalance(uint256 numberOfTiers, address holder) public {
|
|
200
|
+
numberOfTiers = bound(numberOfTiers, 0, 30);
|
|
201
|
+
|
|
202
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
203
|
+
|
|
204
|
+
// Give the holder (i + 1) * 10 NFTs from each tier up to `numberOfTiers`.
|
|
205
|
+
for (uint256 i; i < numberOfTiers; i++) {
|
|
206
|
+
hook.test_store().ForTest_setBalanceOf(address(hook), holder, i + 1, (i + 1) * 10);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check: does the holder have the correct NFT balance?
|
|
210
|
+
// Calculated using 10 * sum of natural numbers from 1 to `numberOfTiers`.
|
|
211
|
+
assertEq(hook.balanceOf(holder), 10 * ((numberOfTiers * (numberOfTiers + 1)) / 2));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function test_numberOfPendingReservesFor_returnsPendingReserves() public {
|
|
215
|
+
uint256 initialSupply = 200; // the starting supply
|
|
216
|
+
uint256 totalMinted = 120; // the number to mint from the supply
|
|
217
|
+
uint256 reservedMinted = 10; // the number of reserve mints (out of `totalMinted`)
|
|
218
|
+
uint256 reserveFrequency = 9; // the reserve frequency
|
|
219
|
+
|
|
220
|
+
// For each tier, 120 NFTs are minted, and 10 of these are reserve mints.
|
|
221
|
+
// This means 110 non-reserved NFTs are minted.
|
|
222
|
+
// Since the `reserveFrequency` is 9, for every 9 non-reserved tokens minted, 1 reserved token is minted.
|
|
223
|
+
// The total number of reserve mints should be `ceil(non-reserve mints / reserveFrequency)`.
|
|
224
|
+
// In our case, `ceil(110/9)` comes out to 13, and 10 reserve mints have already been minted.
|
|
225
|
+
// Therefore, there should be 3 reserve mints remaining for each tier.
|
|
226
|
+
|
|
227
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
228
|
+
|
|
229
|
+
// Set up 10 tiers, each with the parameters above.
|
|
230
|
+
for (uint256 i; i < 10; i++) {
|
|
231
|
+
hook.test_store()
|
|
232
|
+
.ForTest_setTier(
|
|
233
|
+
address(hook),
|
|
234
|
+
i + 1,
|
|
235
|
+
JBStored721Tier({
|
|
236
|
+
price: uint104((i + 1) * 10),
|
|
237
|
+
remainingSupply: uint32(initialSupply - totalMinted),
|
|
238
|
+
initialSupply: uint32(initialSupply),
|
|
239
|
+
votingUnits: uint16(0),
|
|
240
|
+
reserveFrequency: uint16(reserveFrequency),
|
|
241
|
+
category: uint24(100),
|
|
242
|
+
discountPercent: uint8(0),
|
|
243
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false)
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
// Manually set the number of reserve mints for each tier.
|
|
247
|
+
hook.test_store().ForTest_setReservesMintedFor(address(hook), i + 1, reservedMinted);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check: does each tier have the correct number of pending reserves?
|
|
251
|
+
for (uint256 i; i < 10; i++) {
|
|
252
|
+
assertEq(hook.test_store().numberOfPendingReservesFor(address(hook), i + 1), 3);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function test_votingUnitsOf_returnsVotingUnitsCorrectly(
|
|
257
|
+
uint256 numberOfTiers,
|
|
258
|
+
uint256 votingUnits,
|
|
259
|
+
uint256 balances
|
|
260
|
+
)
|
|
261
|
+
public
|
|
262
|
+
{
|
|
263
|
+
numberOfTiers = bound(numberOfTiers, 1, 30);
|
|
264
|
+
votingUnits = bound(votingUnits, 1, type(uint32).max);
|
|
265
|
+
balances = bound(balances, 1, type(uint32).max);
|
|
266
|
+
|
|
267
|
+
defaultTierConfig.useVotingUnits = true;
|
|
268
|
+
defaultTierConfig.votingUnits = uint32(votingUnits);
|
|
269
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
270
|
+
|
|
271
|
+
// Set up tier 1 with 0 voting units.
|
|
272
|
+
hook.test_store()
|
|
273
|
+
.ForTest_setTier(
|
|
274
|
+
address(hook),
|
|
275
|
+
1,
|
|
276
|
+
JBStored721Tier({
|
|
277
|
+
price: uint104(10),
|
|
278
|
+
remainingSupply: uint32(10),
|
|
279
|
+
initialSupply: uint32(20),
|
|
280
|
+
votingUnits: uint16(0),
|
|
281
|
+
reserveFrequency: uint16(100),
|
|
282
|
+
category: uint24(100),
|
|
283
|
+
discountPercent: uint8(0),
|
|
284
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, true, false, false)
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Give the beneficiary `balances` NFTs from each tier up to `numberOfTiers`.
|
|
289
|
+
for (uint256 i; i < numberOfTiers; i++) {
|
|
290
|
+
hook.test_store().ForTest_setBalanceOf(address(hook), beneficiary, i + 1, balances);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check: does the beneficiary have the correct number voting units?
|
|
294
|
+
assertEq(
|
|
295
|
+
hook.test_store().votingUnitsOf(address(hook), beneficiary),
|
|
296
|
+
numberOfTiers * votingUnits * balances - (votingUnits * balances) // One tier has no voting units.
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function test_tierOfTokenId_returnsCorrectTierNumber(uint16 tierId, uint16 tokenNumber) public {
|
|
301
|
+
vm.assume(tierId > 0 && tokenNumber > 0);
|
|
302
|
+
uint256 tokenId = _generateTokenId(tierId, tokenNumber);
|
|
303
|
+
// Check: does the generated token ID match the provided `tierId`.
|
|
304
|
+
assertEq(hook.STORE().tierOfTokenId(address(hook), tokenId, false).id, tierId);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function test_tokenURI_returnsCorrectUriWithResolver(uint256 tokenId) public {
|
|
308
|
+
mockTokenUriResolver = makeAddr("mockTokenUriResolver");
|
|
309
|
+
|
|
310
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
311
|
+
|
|
312
|
+
// Mock the URI resolver call.
|
|
313
|
+
mockAndExpect(
|
|
314
|
+
mockTokenUriResolver,
|
|
315
|
+
abi.encodeWithSelector(IJB721TokenUriResolver.tokenUriOf.selector, address(hook), tokenId),
|
|
316
|
+
abi.encode("resolverURI")
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
hook.ForTest_setOwnerOf(tokenId, beneficiary);
|
|
320
|
+
|
|
321
|
+
// Check: does the token URI resolver return the correct URI from the resolver?
|
|
322
|
+
assertEq(hook.tokenURI(tokenId), "resolverURI");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function test_tokenURI_returnsCorrectUriWithoutResolver() public {
|
|
326
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
327
|
+
|
|
328
|
+
// Check: for each tier, does the tier's token URI match the theoretic hash?
|
|
329
|
+
for (uint256 i = 1; i <= 10; i++) {
|
|
330
|
+
uint256 tokenId = _generateTokenId(i, 1);
|
|
331
|
+
assertEq(hook.tokenURI(tokenId), string(abi.encodePacked(baseUri, theoreticHashes[i - 1])));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function test_setEncodedIPFSUriOf_returnsCorrectEncodedURI() public {
|
|
336
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
337
|
+
|
|
338
|
+
uint256 tokenId = _generateTokenId(1, 1);
|
|
339
|
+
hook.ForTest_setOwnerOf(tokenId, address(123));
|
|
340
|
+
|
|
341
|
+
vm.prank(owner);
|
|
342
|
+
hook.setMetadata("", "", IJB721TokenUriResolver(address(0)), 1, tokenUris[1]);
|
|
343
|
+
|
|
344
|
+
// Check: does the token URI match the theoretic hash?
|
|
345
|
+
assertEq(hook.tokenURI(tokenId), string(abi.encodePacked(baseUri, theoreticHashes[1])));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function test_cashOutWeightOf_returnsCorrectWeightAsCumSumOfPrices(
|
|
349
|
+
uint256 numberOfTiers,
|
|
350
|
+
uint256 firstTier,
|
|
351
|
+
uint256 lastTier
|
|
352
|
+
)
|
|
353
|
+
public
|
|
354
|
+
{
|
|
355
|
+
numberOfTiers = bound(numberOfTiers, 0, 30);
|
|
356
|
+
lastTier = bound(lastTier, 0, numberOfTiers);
|
|
357
|
+
firstTier = bound(firstTier, 0, lastTier);
|
|
358
|
+
|
|
359
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
360
|
+
|
|
361
|
+
// Each tier has `tierId` mintable NFTs, so the maximum number of mints
|
|
362
|
+
// is the sum of natural numbers from 1 to `numberOfTiers`.
|
|
363
|
+
uint256 maxNumberOfTiers = (numberOfTiers * (numberOfTiers + 1)) / 2;
|
|
364
|
+
|
|
365
|
+
// Initialize an array `tierToGetWeightOf` to store the token IDs for each tier,
|
|
366
|
+
// which will later be used to calculate the cash out weight.
|
|
367
|
+
uint256[] memory tierToGetWeightOf = new uint256[](maxNumberOfTiers);
|
|
368
|
+
uint256 iterator;
|
|
369
|
+
uint256 theoreticalWeight;
|
|
370
|
+
|
|
371
|
+
// Mint `tierId` NFTs for each tier. In the inner loop, `i + 1` is the tier ID, and `j + 1` is the token ID.
|
|
372
|
+
for (uint256 i; i < numberOfTiers; i++) {
|
|
373
|
+
if (i >= firstTier && i < lastTier) {
|
|
374
|
+
for (uint256 j; j <= i; j++) {
|
|
375
|
+
tierToGetWeightOf[iterator] = _generateTokenId(i + 1, j + 1);
|
|
376
|
+
iterator++;
|
|
377
|
+
}
|
|
378
|
+
theoreticalWeight += (i + 1) * (i + 1) * 10; // Add the price of the NFTs to the weight.
|
|
379
|
+
// (10 is the price multiplier).
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Check: does the cash out weight match the expected value?
|
|
384
|
+
assertEq(hook.test_store().cashOutWeightOf(address(hook), tierToGetWeightOf), theoreticalWeight);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function test_totalCashOutWeight_returnsCorrectTotalWeightAsCumSumOfPrices(uint256 numberOfTiers) public {
|
|
388
|
+
numberOfTiers = bound(numberOfTiers, 0, 30);
|
|
389
|
+
|
|
390
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
391
|
+
|
|
392
|
+
uint256 theoreticalWeight;
|
|
393
|
+
|
|
394
|
+
// Set up `numberOfTiers` tiers and calculate the theoretical weight for each.
|
|
395
|
+
for (uint256 i = 1; i <= numberOfTiers; i++) {
|
|
396
|
+
hook.test_store()
|
|
397
|
+
.ForTest_setTier(
|
|
398
|
+
address(hook),
|
|
399
|
+
i,
|
|
400
|
+
JBStored721Tier({
|
|
401
|
+
price: uint104(i * 10),
|
|
402
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
403
|
+
initialSupply: uint32(10 * i),
|
|
404
|
+
votingUnits: uint16(0),
|
|
405
|
+
reserveFrequency: uint16(0),
|
|
406
|
+
category: uint24(100),
|
|
407
|
+
discountPercent: uint8(0),
|
|
408
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false)
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
// Calculate the theoretical weight for the current tier. 10 the price multiplier.
|
|
412
|
+
theoreticalWeight += (10 * i - 5 * i) * i * 10;
|
|
413
|
+
}
|
|
414
|
+
// Check: does the total cash out weight match the theoretical weight calculated?
|
|
415
|
+
assertEq(hook.test_store().totalCashOutWeight(address(hook)), theoreticalWeight);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function test_firstOwnerOf_shouldReturnCurrentOwnerIfFirstOwner(uint256 tokenId, address owner) public {
|
|
419
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
420
|
+
|
|
421
|
+
hook.ForTest_setOwnerOf(tokenId, owner);
|
|
422
|
+
|
|
423
|
+
// Check: is the first owner of the NFT is the current owner?
|
|
424
|
+
assertEq(hook.firstOwnerOf(tokenId), owner);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function test_firstOwnerOf_shouldReturnFirstOwnerIfOwnerChanged(address newOwner, address previousOwner) public {
|
|
428
|
+
// Assume that the new owner and previous owner are different and not the zero address.
|
|
429
|
+
vm.assume(newOwner != previousOwner);
|
|
430
|
+
vm.assume(newOwner != address(0));
|
|
431
|
+
vm.assume(previousOwner != address(0));
|
|
432
|
+
|
|
433
|
+
// Trusted forwarder is a special case, it can only be the sender if the transaction is a meta transaction.
|
|
434
|
+
// which we aren't doing here.
|
|
435
|
+
vm.assume(newOwner != trustedForwarder);
|
|
436
|
+
vm.assume(previousOwner != trustedForwarder);
|
|
437
|
+
|
|
438
|
+
defaultTierConfig.allowOwnerMint = true;
|
|
439
|
+
defaultTierConfig.reserveFrequency = 0;
|
|
440
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
441
|
+
|
|
442
|
+
uint16[] memory tiersToMint = new uint16[](1);
|
|
443
|
+
tiersToMint[0] = 1;
|
|
444
|
+
|
|
445
|
+
uint256 tokenId = _generateTokenId(tiersToMint[0], 1);
|
|
446
|
+
|
|
447
|
+
vm.prank(owner);
|
|
448
|
+
hook.mintFor(tiersToMint, previousOwner);
|
|
449
|
+
|
|
450
|
+
// Check: is the first owner of the NFT the previous owner?
|
|
451
|
+
assertEq(hook.firstOwnerOf(tokenId), previousOwner);
|
|
452
|
+
|
|
453
|
+
// Prank the previous owner and transfer the NFT to the new owner.
|
|
454
|
+
vm.startPrank(previousOwner);
|
|
455
|
+
IERC721(hook).transferFrom(previousOwner, newOwner, tokenId);
|
|
456
|
+
vm.stopPrank();
|
|
457
|
+
|
|
458
|
+
// Check: is the first owner of the NFT still the previous owner?
|
|
459
|
+
assertEq(hook.firstOwnerOf(tokenId), previousOwner);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function test_firstOwnerOf_shouldReturnZeroAddressIfNotMinted(uint256 tokenId) public {
|
|
463
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
464
|
+
// Check: is the "first owner" of the NFT the zero address?
|
|
465
|
+
assertEq(hook.firstOwnerOf(tokenId), address(0));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function test_constructor_deployIfInitialSuppliesNotEmpty(uint256 numberOfTiers) public {
|
|
469
|
+
numberOfTiers = bound(numberOfTiers, 0, 10);
|
|
470
|
+
// Create new tiers array.
|
|
471
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
|
|
472
|
+
(, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
|
|
473
|
+
|
|
474
|
+
// Check: do the hook's parameters match the expected values?
|
|
475
|
+
assertEq(hook.PROJECT_ID(), projectId);
|
|
476
|
+
assertEq(address(hook.DIRECTORY()), mockJBDirectory);
|
|
477
|
+
assertEq(hook.name(), name);
|
|
478
|
+
assertEq(hook.symbol(), symbol);
|
|
479
|
+
assertEq(address(hook.STORE().tokenUriResolverOf(address(hook))), mockTokenUriResolver);
|
|
480
|
+
assertEq(hook.contractURI(), contractUri);
|
|
481
|
+
assertEq(hook.owner(), owner);
|
|
482
|
+
// Check: are all of the `tiers` in `hook.STORE().tiersOf`, and vice versa (do they match)?
|
|
483
|
+
// Order is not guaranteed, so we use `_isIn` and check both ways.
|
|
484
|
+
assertTrue(_isIn(hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers), tiers));
|
|
485
|
+
assertTrue(_isIn(tiers, hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers)));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function test_constructor_revertDeploymentIfOneEmptyInitialSupply(
|
|
489
|
+
uint256 numberOfTiers,
|
|
490
|
+
uint256 errorIndex
|
|
491
|
+
)
|
|
492
|
+
public
|
|
493
|
+
{
|
|
494
|
+
numberOfTiers = bound(numberOfTiers, 1, 20);
|
|
495
|
+
errorIndex = bound(errorIndex, 0, numberOfTiers - 1);
|
|
496
|
+
JB721TierConfig[] memory tiers = new JB721TierConfig[](numberOfTiers);
|
|
497
|
+
|
|
498
|
+
// Populate the tiers array with the default tier config.
|
|
499
|
+
for (uint256 i; i < numberOfTiers; i++) {
|
|
500
|
+
tiers[i] = JB721TierConfig({
|
|
501
|
+
price: uint104(i * 10),
|
|
502
|
+
initialSupply: uint32(100),
|
|
503
|
+
votingUnits: uint16(0),
|
|
504
|
+
reserveFrequency: uint16(0),
|
|
505
|
+
reserveBeneficiary: reserveBeneficiary,
|
|
506
|
+
encodedIPFSUri: tokenUris[0],
|
|
507
|
+
category: uint24(100),
|
|
508
|
+
discountPercent: uint8(0),
|
|
509
|
+
allowOwnerMint: false,
|
|
510
|
+
useReserveBeneficiaryAsDefault: false,
|
|
511
|
+
transfersPausable: false,
|
|
512
|
+
useVotingUnits: true,
|
|
513
|
+
cannotBeRemoved: false,
|
|
514
|
+
cannotIncreaseDiscountPercent: false
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Set the initial supply of the tier at `errorIndex` to 0. This should cause an error.
|
|
519
|
+
tiers[errorIndex].initialSupply = 0;
|
|
520
|
+
|
|
521
|
+
// Expect the error.
|
|
522
|
+
vm.expectRevert(
|
|
523
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_ZeroInitialSupply.selector, errorIndex + 1)
|
|
524
|
+
);
|
|
525
|
+
vm.etch(hook_i, address(hook).code);
|
|
526
|
+
JB721TiersHook hook = JB721TiersHook(hook_i);
|
|
527
|
+
hook.initialize(
|
|
528
|
+
projectId,
|
|
529
|
+
name,
|
|
530
|
+
symbol,
|
|
531
|
+
baseUri,
|
|
532
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
533
|
+
contractUri,
|
|
534
|
+
JB721InitTiersConfig({
|
|
535
|
+
tiers: tiers,
|
|
536
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
537
|
+
decimals: 18,
|
|
538
|
+
prices: IJBPrices(address(0))
|
|
539
|
+
}),
|
|
540
|
+
JB721TiersHookFlags({
|
|
541
|
+
preventOverspending: false,
|
|
542
|
+
noNewTiersWithReserves: true,
|
|
543
|
+
noNewTiersWithVotes: true,
|
|
544
|
+
noNewTiersWithOwnerMinting: true
|
|
545
|
+
})
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|