@bananapus/721-hook-v6 0.0.28 → 0.0.30
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/ADMINISTRATION.md +38 -11
- package/ARCHITECTURE.md +53 -99
- package/AUDIT_INSTRUCTIONS.md +84 -383
- package/CHANGELOG.md +71 -0
- package/README.md +79 -225
- package/RISKS.md +28 -11
- package/SKILLS.md +29 -296
- package/STYLE_GUIDE.md +57 -18
- package/USER_JOURNEYS.md +57 -501
- package/package.json +1 -1
- package/references/operations.md +28 -0
- package/references/runtime.md +32 -0
- package/script/Deploy.s.sol +5 -4
- package/src/JB721TiersHook.sol +1 -1
- package/src/JB721TiersHookDeployer.sol +1 -1
- package/src/JB721TiersHookProjectDeployer.sol +1 -1
- package/src/JB721TiersHookStore.sol +23 -17
- package/src/libraries/JB721Constants.sol +1 -1
- package/src/libraries/JB721TiersRulesetMetadataResolver.sol +1 -1
- package/src/libraries/JBBitmap.sol +1 -1
- package/src/libraries/JBIpfsDecoder.sol +1 -1
- package/src/structs/JB721Tier.sol +5 -11
- package/src/structs/JB721TierConfig.sol +5 -20
- package/src/structs/JB721TierConfigFlags.sol +26 -0
- package/src/structs/JB721TierFlags.sol +17 -0
- package/test/721HookAttacks.t.sol +22 -17
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +19 -14
- package/test/Fork.t.sol +69 -54
- package/test/TestAuditGaps.sol +73 -56
- package/test/TestSafeTransferReentrancy.t.sol +4 -4
- package/test/TestVotingUnitsLifecycle.t.sol +11 -11
- package/test/audit/CodexPayCreditsBypassTierSplits.t.sol +10 -7
- package/test/audit/CodexSplitCreditsMismatch.t.sol +10 -7
- package/test/fork/ERC20CashOutFork.t.sol +37 -28
- package/test/fork/ERC20TierSplitFork.t.sol +28 -21
- package/test/fork/IssueTokensForSplitsFork.t.sol +10 -7
- package/test/invariants/handlers/TierLifecycleHandler.sol +10 -7
- package/test/invariants/handlers/TierStoreHandler.sol +10 -7
- package/test/regression/ProjectDeployerRulesets.t.sol +10 -7
- package/test/regression/ReserveBeneficiaryOverwrite.t.sol +6 -6
- package/test/unit/AuditFixes_Unit.t.sol +37 -28
- package/test/unit/adjustTier_Unit.t.sol +268 -202
- package/test/unit/getters_constructor_Unit.t.sol +20 -14
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +2 -2
- package/test/unit/pay_Unit.t.sol +1 -1
- package/CHANGE_LOG.md +0 -359
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 721 Hook Runtime
|
|
2
|
+
|
|
3
|
+
## Contract Roles
|
|
4
|
+
|
|
5
|
+
- [`src/abstract/JB721Hook.sol`](../src/abstract/JB721Hook.sol) is the shared pay and cash-out hook surface. It validates the calling terminal, decodes metadata, and delegates runtime behavior to the concrete hook.
|
|
6
|
+
- [`src/JB721TiersHook.sol`](../src/JB721TiersHook.sol) is the main project-facing contract. It handles tier-aware minting, split forwarding, discount updates, metadata changes, reserve minting, and cash-out weight calculations.
|
|
7
|
+
- [`src/JB721TiersHookStore.sol`](../src/JB721TiersHookStore.sol) is the shared storage and accounting backend. It owns tier definitions, supply counters, burn counts, reserve availability, and voting-unit state.
|
|
8
|
+
- [`src/libraries/JB721TiersHookLib.sol`](../src/libraries/JB721TiersHookLib.sol) holds size-sensitive helper logic such as tier adjustment, split calculation/distribution, pricing normalization, and token-URI resolution.
|
|
9
|
+
|
|
10
|
+
## Runtime Path
|
|
11
|
+
|
|
12
|
+
1. Terminal calls [`src/abstract/JB721Hook.sol`](../src/abstract/JB721Hook.sol) through the pay or cash-out hook interface.
|
|
13
|
+
2. The abstract hook validates the terminal and decodes metadata.
|
|
14
|
+
3. [`src/JB721TiersHook.sol`](../src/JB721TiersHook.sol) computes pricing, splits, credits, or burn-side cash-out weights.
|
|
15
|
+
4. [`src/JB721TiersHookStore.sol`](../src/JB721TiersHookStore.sol) records the mint, reserve, burn, and tier-state effects.
|
|
16
|
+
5. If the flow forwards split funds or resolves token metadata, the hook delegates into [`src/libraries/JB721TiersHookLib.sol`](../src/libraries/JB721TiersHookLib.sol).
|
|
17
|
+
|
|
18
|
+
## High-Risk Areas
|
|
19
|
+
|
|
20
|
+
- Reserve accounting: edits around `reserveFrequency`, pending reserves, or owner minting must preserve the store's supply protections.
|
|
21
|
+
- Tier splits: split forwarding changes affect both payer economics and project treasury accounting. Check both `beforePayRecordedWith` and the distribution path.
|
|
22
|
+
- Discount behavior: price discounts affect mint eligibility but cash-out weight still tracks the original tier price. Do not conflate the two.
|
|
23
|
+
- Voting units: verify whether a tier uses explicit voting units or falls back to price-based voting power before changing governance-facing math.
|
|
24
|
+
- Tier removal and cleanup: removing tiers is not the same as cleaning the sorted tier list. Storage cleanup behavior matters.
|
|
25
|
+
|
|
26
|
+
## Tests To Trust First
|
|
27
|
+
|
|
28
|
+
- [`test/invariants/`](../test/invariants/) for broad accounting invariants.
|
|
29
|
+
- [`test/E2E/`](../test/E2E/) for launch and end-to-end payment flows.
|
|
30
|
+
- [`test/regression/`](../test/regression/) for previously broken edge cases.
|
|
31
|
+
- [`test/TestVotingUnitsLifecycle.t.sol`](../test/TestVotingUnitsLifecycle.t.sol) for voting-unit lifecycle behavior.
|
|
32
|
+
- [`test/TestSafeTransferReentrancy.t.sol`](../test/TestSafeTransferReentrancy.t.sol) and [`test/721HookAttacks.t.sol`](../test/721HookAttacks.t.sol) for reentrancy and attack-surface checks.
|
package/script/Deploy.s.sol
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {CoreDeployment, CoreDeploymentLib} from "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
|
|
5
|
+
import {
|
|
6
|
+
AddressRegistryDeployment,
|
|
7
|
+
AddressRegistryDeploymentLib
|
|
8
|
+
} from "@bananapus/address-registry-v6/script/helpers/AddressRegistryDeploymentLib.sol";
|
|
8
9
|
|
|
9
10
|
import {Sphinx} from "@sphinx-labs/contracts/contracts/foundry/SphinxPlugin.sol";
|
|
10
11
|
import {Script} from "forge-std/Script.sol";
|
package/src/JB721TiersHook.sol
CHANGED
|
@@ -776,7 +776,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
776
776
|
// Transfers must not be paused (when not minting or burning).
|
|
777
777
|
if (from != address(0)) {
|
|
778
778
|
// If transfers are pausable, check if they're paused.
|
|
779
|
-
if (tier.transfersPausable) {
|
|
779
|
+
if (tier.flags.transfersPausable) {
|
|
780
780
|
// Get a reference to the project's current ruleset.
|
|
781
781
|
JBRuleset memory ruleset = _currentRulesetOf(PROJECT_ID);
|
|
782
782
|
|
|
@@ -29,7 +29,7 @@ contract JB721TiersHookDeployer is ERC2771Context, IJB721TiersHookDeployer {
|
|
|
29
29
|
IJB721TiersHookStore public immutable STORE;
|
|
30
30
|
|
|
31
31
|
//*********************************************************************//
|
|
32
|
-
//
|
|
32
|
+
// -------------------- internal stored properties ------------------- //
|
|
33
33
|
//*********************************************************************//
|
|
34
34
|
|
|
35
35
|
/// @notice This contract's current nonce, used for the Juicebox address registry.
|
|
@@ -218,7 +218,7 @@ contract JB721TiersHookProjectDeployer is ERC2771Context, JBPermissioned, IJB721
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
//*********************************************************************//
|
|
221
|
-
//
|
|
221
|
+
// ----------------------- internal helpers -------------------------- //
|
|
222
222
|
//*********************************************************************//
|
|
223
223
|
|
|
224
224
|
/// @notice Launches a project.
|
|
@@ -10,6 +10,7 @@ import {JB721Constants} from "./libraries/JB721Constants.sol";
|
|
|
10
10
|
import {JBBitmap} from "./libraries/JBBitmap.sol";
|
|
11
11
|
import {JB721Tier} from "./structs/JB721Tier.sol";
|
|
12
12
|
import {JB721TierConfig} from "./structs/JB721TierConfig.sol";
|
|
13
|
+
import {JB721TierFlags} from "./structs/JB721TierFlags.sol";
|
|
13
14
|
import {JB721TiersHookFlags} from "./structs/JB721TiersHookFlags.sol";
|
|
14
15
|
import {JBBitmapWord} from "./structs/JBBitmapWord.sol";
|
|
15
16
|
import {JBStored721Tier} from "./structs/JBStored721Tier.sol";
|
|
@@ -549,11 +550,13 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
549
550
|
encodedIPFSUri: encodedIPFSUriOf[hook][tierId],
|
|
550
551
|
category: storedTier.category,
|
|
551
552
|
discountPercent: storedTier.discountPercent,
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
553
|
+
flags: JB721TierFlags({
|
|
554
|
+
allowOwnerMint: (packed & 0x1 != 0),
|
|
555
|
+
transfersPausable: (packed & 0x2 != 0),
|
|
556
|
+
cantBeRemoved: (packed & 0x8 != 0),
|
|
557
|
+
cantIncreaseDiscountPercent: (packed & 0x10 != 0),
|
|
558
|
+
cantBuyWithCredits: (packed & 0x20 != 0)
|
|
559
|
+
}),
|
|
557
560
|
splitPercent: storedTier.splitPercent,
|
|
558
561
|
resolvedUri: !includeResolvedUri || tokenUriResolverOf[hook] == IJB721TokenUriResolver(address(0))
|
|
559
562
|
? ""
|
|
@@ -744,9 +747,11 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
744
747
|
uint256 currentSortedTierId = _firstSortedTierIdOf({hook: hook, category: 0});
|
|
745
748
|
|
|
746
749
|
// Keep track of the previous non-removed tier ID.
|
|
750
|
+
// slither-disable-next-line uninitialized-local
|
|
747
751
|
uint256 previousSortedTierId;
|
|
748
752
|
|
|
749
753
|
// Initialize a `JBBitmapWord` for tracking removed tiers.
|
|
754
|
+
// slither-disable-next-line uninitialized-local
|
|
750
755
|
JBBitmapWord memory bitmapWord;
|
|
751
756
|
|
|
752
757
|
// Make the sorted array.
|
|
@@ -820,6 +825,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
820
825
|
uint256 startSortedTierId = currentMaxTierIdOf == 0 ? 0 : _firstSortedTierIdOf({hook: msg.sender, category: 0});
|
|
821
826
|
|
|
822
827
|
// Keep track of the previous tier's ID while iterating.
|
|
828
|
+
// slither-disable-next-line uninitialized-local
|
|
823
829
|
uint256 previousTierId;
|
|
824
830
|
|
|
825
831
|
// Keep a reference to the 721 contract's flags.
|
|
@@ -855,20 +861,20 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
855
861
|
// Make sure the new tier doesn't have voting units if the 721 contract's flags don't allow it to.
|
|
856
862
|
if (
|
|
857
863
|
flags.noNewTiersWithVotes
|
|
858
|
-
&& ((tierToAdd.useVotingUnits && tierToAdd.votingUnits != 0)
|
|
859
|
-
|| (!tierToAdd.useVotingUnits && tierToAdd.price != 0))
|
|
864
|
+
&& ((tierToAdd.flags.useVotingUnits && tierToAdd.votingUnits != 0)
|
|
865
|
+
|| (!tierToAdd.flags.useVotingUnits && tierToAdd.price != 0))
|
|
860
866
|
) {
|
|
861
867
|
revert JB721TiersHookStore_VotingUnitsNotAllowed(tierId);
|
|
862
868
|
}
|
|
863
869
|
|
|
864
870
|
// Make sure the new tier doesn't have a reserve frequency if the 721 contract's flags don't allow it to,
|
|
865
871
|
// OR if manual minting is allowed.
|
|
866
|
-
if ((flags.noNewTiersWithReserves || tierToAdd.allowOwnerMint) && tierToAdd.reserveFrequency != 0) {
|
|
872
|
+
if ((flags.noNewTiersWithReserves || tierToAdd.flags.allowOwnerMint) && tierToAdd.reserveFrequency != 0) {
|
|
867
873
|
revert JB721TiersHookStore_ReserveFrequencyNotAllowed(tierId);
|
|
868
874
|
}
|
|
869
875
|
|
|
870
876
|
// Make sure the new tier doesn't have owner minting enabled if the 721 contract's flags don't allow it to.
|
|
871
|
-
if (flags.noNewTiersWithOwnerMinting && tierToAdd.allowOwnerMint) {
|
|
877
|
+
if (flags.noNewTiersWithOwnerMinting && tierToAdd.flags.allowOwnerMint) {
|
|
872
878
|
revert JB721TiersHookStore_ManualMintingNotAllowed(tierId);
|
|
873
879
|
}
|
|
874
880
|
|
|
@@ -905,17 +911,17 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
905
911
|
category: uint24(tierToAdd.category),
|
|
906
912
|
discountPercent: uint8(tierToAdd.discountPercent),
|
|
907
913
|
packedBools: _packBools({
|
|
908
|
-
allowOwnerMint: tierToAdd.allowOwnerMint,
|
|
909
|
-
transfersPausable: tierToAdd.transfersPausable,
|
|
910
|
-
useVotingUnits: tierToAdd.useVotingUnits,
|
|
911
|
-
cantBeRemoved: tierToAdd.cantBeRemoved,
|
|
912
|
-
cantIncreaseDiscountPercent: tierToAdd.cantIncreaseDiscountPercent,
|
|
913
|
-
cantBuyWithCredits: tierToAdd.cantBuyWithCredits
|
|
914
|
+
allowOwnerMint: tierToAdd.flags.allowOwnerMint,
|
|
915
|
+
transfersPausable: tierToAdd.flags.transfersPausable,
|
|
916
|
+
useVotingUnits: tierToAdd.flags.useVotingUnits,
|
|
917
|
+
cantBeRemoved: tierToAdd.flags.cantBeRemoved,
|
|
918
|
+
cantIncreaseDiscountPercent: tierToAdd.flags.cantIncreaseDiscountPercent,
|
|
919
|
+
cantBuyWithCredits: tierToAdd.flags.cantBuyWithCredits
|
|
914
920
|
})
|
|
915
921
|
});
|
|
916
922
|
|
|
917
923
|
// Store voting units in a separate mapping if custom voting units are used.
|
|
918
|
-
if (tierToAdd.useVotingUnits && tierToAdd.votingUnits != 0) {
|
|
924
|
+
if (tierToAdd.flags.useVotingUnits && tierToAdd.votingUnits != 0) {
|
|
919
925
|
_tierVotingUnitsOf[msg.sender][tierId] = uint32(tierToAdd.votingUnits);
|
|
920
926
|
}
|
|
921
927
|
|
|
@@ -928,7 +934,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
928
934
|
|
|
929
935
|
// Set the reserve beneficiary if needed.
|
|
930
936
|
if (tierToAdd.reserveBeneficiary != address(0) && tierToAdd.reserveFrequency != 0) {
|
|
931
|
-
if (tierToAdd.useReserveBeneficiaryAsDefault) {
|
|
937
|
+
if (tierToAdd.flags.useReserveBeneficiaryAsDefault) {
|
|
932
938
|
// WARNING: This overwrites the global default for ALL tiers without a tier-specific beneficiary.
|
|
933
939
|
if (defaultReserveBeneficiaryOf[msg.sender] != tierToAdd.reserveBeneficiary) {
|
|
934
940
|
defaultReserveBeneficiaryOf[msg.sender] = tierToAdd.reserveBeneficiary;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
+
import {JB721TierFlags} from "./JB721TierFlags.sol";
|
|
5
|
+
|
|
4
6
|
/// @custom:member id The tier's ID.
|
|
5
7
|
/// @custom:member price The price to buy an NFT in this tier, in terms of the currency in its `JBInitTiersConfig`.
|
|
6
8
|
/// @custom:member remainingSupply The remaining number of NFTs which can be minted from this tier.
|
|
@@ -13,12 +15,8 @@ pragma solidity ^0.8.0;
|
|
|
13
15
|
/// @custom:member encodedIPFSUri The IPFS URI to use for each NFT in this tier.
|
|
14
16
|
/// @custom:member category The category that NFTs in this tier belongs to. Used to group NFT tiers.
|
|
15
17
|
/// @custom:member discountPercent The discount that should be applied to the tier.
|
|
16
|
-
/// @custom:member
|
|
17
|
-
///
|
|
18
|
-
/// @custom:member cantBeRemoved A boolean indicating whether attempts to remove this tier will revert.
|
|
19
|
-
/// @custom:member cantIncreaseDiscountPercent If the tier cannot have its discount increased.
|
|
20
|
-
/// @custom:member cantBuyWithCredits If true, this tier cannot be purchased using accumulated pay credits.
|
|
21
|
-
/// @custom:member transfersPausable A boolean indicating whether transfers for NFTs in tier can be paused.
|
|
18
|
+
/// @custom:member flags Boolean flags for this tier (allowOwnerMint, transfersPausable, cantBeRemoved,
|
|
19
|
+
/// cantIncreaseDiscountPercent, cantBuyWithCredits).
|
|
22
20
|
/// @custom:member splitPercent The percentage of the tier's price that gets routed to the project's split group when
|
|
23
21
|
/// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
|
|
24
22
|
/// @custom:member resolvedUri A resolved token URI for NFTs in this tier. Only available if the NFT this tier belongs
|
|
@@ -36,11 +34,7 @@ struct JB721Tier {
|
|
|
36
34
|
bytes32 encodedIPFSUri;
|
|
37
35
|
uint24 category;
|
|
38
36
|
uint8 discountPercent;
|
|
39
|
-
|
|
40
|
-
bool transfersPausable;
|
|
41
|
-
bool cantBeRemoved;
|
|
42
|
-
bool cantIncreaseDiscountPercent;
|
|
43
|
-
bool cantBuyWithCredits;
|
|
37
|
+
JB721TierFlags flags;
|
|
44
38
|
uint32 splitPercent;
|
|
45
39
|
string resolvedUri;
|
|
46
40
|
}
|
|
@@ -3,6 +3,8 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
5
5
|
|
|
6
|
+
import {JB721TierConfigFlags} from "./JB721TierConfigFlags.sol";
|
|
7
|
+
|
|
6
8
|
/// @notice Config for a single NFT tier within a `JB721TiersHook`.
|
|
7
9
|
/// @custom:member price The price to buy an NFT in this tier, in terms of the currency in its `JBInitTiersConfig`.
|
|
8
10
|
/// @custom:member initialSupply The total number of NFTs which can be minted from this tier.
|
|
@@ -15,19 +17,8 @@ import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
|
15
17
|
/// @custom:member encodedIPFSUri The IPFS URI to use for each NFT in this tier.
|
|
16
18
|
/// @custom:member category The category that NFTs in this tier belongs to. Used to group NFT tiers.
|
|
17
19
|
/// @custom:member discountPercent The discount that should be applied to the tier.
|
|
18
|
-
/// @custom:member
|
|
19
|
-
///
|
|
20
|
-
/// @custom:member useReserveBeneficiaryAsDefault A boolean indicating whether this tier's `reserveBeneficiary` should
|
|
21
|
-
/// be stored as the default beneficiary for all tiers. WARNING: Setting this to `true` overwrites the global
|
|
22
|
-
/// `defaultReserveBeneficiaryOf` for the hook, which affects ALL existing tiers that do not have a tier-specific
|
|
23
|
-
/// reserve beneficiary. Use with caution when calling `adjustTiers` on hooks with existing tiers.
|
|
24
|
-
/// @custom:member transfersPausable A boolean indicating whether transfers for NFTs in tier can be paused.
|
|
25
|
-
/// @custom:member useVotingUnits A boolean indicating whether the `votingUnits` should be used to calculate voting
|
|
26
|
-
/// power. If `useVotingUnits` is false, voting power is based on the tier's price.
|
|
27
|
-
/// @custom:member cantBeRemoved If the tier cannot be removed once added.
|
|
28
|
-
/// @custom:member cannotIncreaseDiscount If the tier cannot have its discount increased.
|
|
29
|
-
/// @custom:member cantBuyWithCredits If true, this tier cannot be purchased using accumulated pay credits. Only fresh
|
|
30
|
-
/// payment value counts toward this tier's price.
|
|
20
|
+
/// @custom:member flags Boolean flags for this tier config (allowOwnerMint, useReserveBeneficiaryAsDefault,
|
|
21
|
+
/// transfersPausable, useVotingUnits, cantBeRemoved, cantIncreaseDiscountPercent, cantBuyWithCredits).
|
|
31
22
|
/// @custom:member splitPercent The percentage of the tier's price that gets routed to the tier's split group when
|
|
32
23
|
/// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
|
|
33
24
|
/// @custom:member splits The splits to use for this tier's split group. These define where the split portion of the
|
|
@@ -43,13 +34,7 @@ struct JB721TierConfig {
|
|
|
43
34
|
bytes32 encodedIPFSUri;
|
|
44
35
|
uint24 category;
|
|
45
36
|
uint8 discountPercent;
|
|
46
|
-
|
|
47
|
-
bool useReserveBeneficiaryAsDefault;
|
|
48
|
-
bool transfersPausable;
|
|
49
|
-
bool useVotingUnits;
|
|
50
|
-
bool cantBeRemoved;
|
|
51
|
-
bool cantIncreaseDiscountPercent;
|
|
52
|
-
bool cantBuyWithCredits;
|
|
37
|
+
JB721TierConfigFlags flags;
|
|
53
38
|
uint32 splitPercent;
|
|
54
39
|
JBSplit[] splits;
|
|
55
40
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
/// @custom:member allowOwnerMint A boolean indicating whether the contract's owner can mint NFTs from this tier
|
|
5
|
+
/// on-demand.
|
|
6
|
+
/// @custom:member useReserveBeneficiaryAsDefault A boolean indicating whether this tier's `reserveBeneficiary` should
|
|
7
|
+
/// be stored as the default beneficiary for all tiers. WARNING: Setting this to `true` overwrites the global
|
|
8
|
+
/// `defaultReserveBeneficiaryOf` for the hook, which affects ALL existing tiers that do not have a tier-specific
|
|
9
|
+
/// reserve beneficiary. Use with caution when calling `adjustTiers` on hooks with existing tiers.
|
|
10
|
+
/// @custom:member transfersPausable A boolean indicating whether transfers for NFTs in this tier can be paused.
|
|
11
|
+
/// @custom:member useVotingUnits A boolean indicating whether the `votingUnits` should be used to calculate voting
|
|
12
|
+
/// power. If `useVotingUnits` is false, voting power is based on the tier's price.
|
|
13
|
+
/// @custom:member cantBeRemoved If the tier cannot be removed once added.
|
|
14
|
+
/// @custom:member cantIncreaseDiscountPercent If the tier cannot have its discount increased.
|
|
15
|
+
/// @custom:member cantBuyWithCredits If true, this tier cannot be purchased using accumulated pay credits. Only fresh
|
|
16
|
+
/// payment value counts toward this tier's price.
|
|
17
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
18
|
+
struct JB721TierConfigFlags {
|
|
19
|
+
bool allowOwnerMint;
|
|
20
|
+
bool useReserveBeneficiaryAsDefault;
|
|
21
|
+
bool transfersPausable;
|
|
22
|
+
bool useVotingUnits;
|
|
23
|
+
bool cantBeRemoved;
|
|
24
|
+
bool cantIncreaseDiscountPercent;
|
|
25
|
+
bool cantBuyWithCredits;
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
/// @custom:member allowOwnerMint A boolean indicating whether the contract's owner can mint NFTs from this tier
|
|
5
|
+
/// on-demand.
|
|
6
|
+
/// @custom:member transfersPausable A boolean indicating whether transfers for NFTs in this tier can be paused.
|
|
7
|
+
/// @custom:member cantBeRemoved A boolean indicating whether attempts to remove this tier will revert.
|
|
8
|
+
/// @custom:member cantIncreaseDiscountPercent If the tier cannot have its discount increased.
|
|
9
|
+
/// @custom:member cantBuyWithCredits If true, this tier cannot be purchased using accumulated pay credits.
|
|
10
|
+
// forge-lint: disable-next-line(pascal-case-struct)
|
|
11
|
+
struct JB721TierFlags {
|
|
12
|
+
bool allowOwnerMint;
|
|
13
|
+
bool transfersPausable;
|
|
14
|
+
bool cantBeRemoved;
|
|
15
|
+
bool cantIncreaseDiscountPercent;
|
|
16
|
+
bool cantBuyWithCredits;
|
|
17
|
+
}
|
|
@@ -3,6 +3,7 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
5
|
import "./utils/UnitTestSetup.sol";
|
|
6
|
+
import {JB721TierConfigFlags} from "../src/structs/JB721TierConfigFlags.sol";
|
|
6
7
|
|
|
7
8
|
/// @title 721HookAttacks
|
|
8
9
|
/// @notice Adversarial security tests for JB721TiersHook and JB721TiersHookStore.
|
|
@@ -81,13 +82,15 @@ contract NFTHookAttacks is UnitTestSetup {
|
|
|
81
82
|
encodedIPFSUri: tokenUris[0],
|
|
82
83
|
category: 2,
|
|
83
84
|
discountPercent: 0,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
flags: JB721TierConfigFlags({
|
|
86
|
+
allowOwnerMint: false,
|
|
87
|
+
useReserveBeneficiaryAsDefault: false,
|
|
88
|
+
transfersPausable: false,
|
|
89
|
+
useVotingUnits: false,
|
|
90
|
+
cantBeRemoved: false,
|
|
91
|
+
cantIncreaseDiscountPercent: false,
|
|
92
|
+
cantBuyWithCredits: false
|
|
93
|
+
}),
|
|
91
94
|
splitPercent: 0,
|
|
92
95
|
splits: new JBSplit[](0)
|
|
93
96
|
});
|
|
@@ -116,7 +119,7 @@ contract NFTHookAttacks is UnitTestSetup {
|
|
|
116
119
|
/// @notice Set discount to 100%, verify the effective price for the tier.
|
|
117
120
|
function test_maxDiscountPercent_effectivePrice() public {
|
|
118
121
|
defaultTierConfig.discountPercent = 0;
|
|
119
|
-
defaultTierConfig.cantIncreaseDiscountPercent = false;
|
|
122
|
+
defaultTierConfig.flags.cantIncreaseDiscountPercent = false;
|
|
120
123
|
|
|
121
124
|
JB721TiersHook targetHook = _initHookDefaultTiers(1);
|
|
122
125
|
|
|
@@ -137,7 +140,7 @@ contract NFTHookAttacks is UnitTestSetup {
|
|
|
137
140
|
/// @notice Try to increase discount when the flag forbids it.
|
|
138
141
|
function test_cantIncreaseDiscountPercent_enforcement() public {
|
|
139
142
|
defaultTierConfig.discountPercent = 10;
|
|
140
|
-
defaultTierConfig.cantIncreaseDiscountPercent = true;
|
|
143
|
+
defaultTierConfig.flags.cantIncreaseDiscountPercent = true;
|
|
141
144
|
|
|
142
145
|
JB721TiersHook targetHook = _initHookDefaultTiers(1);
|
|
143
146
|
|
|
@@ -213,7 +216,7 @@ contract NFTHookAttacks is UnitTestSetup {
|
|
|
213
216
|
function test_cashOutWeight_afterTierRemoval() public {
|
|
214
217
|
defaultTierConfig.initialSupply = 100;
|
|
215
218
|
defaultTierConfig.votingUnits = 10;
|
|
216
|
-
defaultTierConfig.useVotingUnits = true;
|
|
219
|
+
defaultTierConfig.flags.useVotingUnits = true;
|
|
217
220
|
|
|
218
221
|
ForTest_JB721TiersHook targetHook = _initializeForTestHook(1);
|
|
219
222
|
IJB721TiersHookStore hookStore = targetHook.STORE();
|
|
@@ -369,13 +372,15 @@ contract NFTHookAttacks is UnitTestSetup {
|
|
|
369
372
|
encodedIPFSUri: tokenUris[0],
|
|
370
373
|
category: 1,
|
|
371
374
|
discountPercent: 0,
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
375
|
+
flags: JB721TierConfigFlags({
|
|
376
|
+
allowOwnerMint: true,
|
|
377
|
+
useReserveBeneficiaryAsDefault: false,
|
|
378
|
+
transfersPausable: false,
|
|
379
|
+
useVotingUnits: false,
|
|
380
|
+
cantBeRemoved: false,
|
|
381
|
+
cantIncreaseDiscountPercent: false,
|
|
382
|
+
cantBuyWithCredits: false
|
|
383
|
+
}),
|
|
379
384
|
splitPercent: 0,
|
|
380
385
|
splits: new JBSplit[](0)
|
|
381
386
|
});
|
|
@@ -20,6 +20,7 @@ import "../utils/TestBaseWorkflow.sol";
|
|
|
20
20
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
21
21
|
import "../../src/interfaces/IJB721TiersHook.sol";
|
|
22
22
|
import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
|
|
23
|
+
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
23
24
|
|
|
24
25
|
contract Test_TiersHook_E2E is TestBaseWorkflow {
|
|
25
26
|
using JBRulesetMetadataResolver for JBRuleset;
|
|
@@ -796,13 +797,15 @@ contract Test_TiersHook_E2E is TestBaseWorkflow {
|
|
|
796
797
|
encodedIPFSUri: tokenUris[i],
|
|
797
798
|
category: uint24(100),
|
|
798
799
|
discountPercent: uint8(0),
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
800
|
+
flags: JB721TierConfigFlags({
|
|
801
|
+
allowOwnerMint: false,
|
|
802
|
+
useReserveBeneficiaryAsDefault: false,
|
|
803
|
+
transfersPausable: false,
|
|
804
|
+
useVotingUnits: false,
|
|
805
|
+
cantBeRemoved: false,
|
|
806
|
+
cantIncreaseDiscountPercent: false,
|
|
807
|
+
cantBuyWithCredits: false
|
|
808
|
+
}),
|
|
806
809
|
splitPercent: 0,
|
|
807
810
|
splits: new JBSplit[](0)
|
|
808
811
|
});
|
|
@@ -889,13 +892,15 @@ contract Test_TiersHook_E2E is TestBaseWorkflow {
|
|
|
889
892
|
encodedIPFSUri: tokenUris[0],
|
|
890
893
|
category: uint24(100),
|
|
891
894
|
discountPercent: _discountPercent,
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
895
|
+
flags: JB721TierConfigFlags({
|
|
896
|
+
allowOwnerMint: false,
|
|
897
|
+
useReserveBeneficiaryAsDefault: false,
|
|
898
|
+
transfersPausable: false,
|
|
899
|
+
useVotingUnits: false,
|
|
900
|
+
cantBeRemoved: false,
|
|
901
|
+
cantIncreaseDiscountPercent: false,
|
|
902
|
+
cantBuyWithCredits: false
|
|
903
|
+
}),
|
|
899
904
|
splitPercent: 0,
|
|
900
905
|
splits: new JBSplit[](0)
|
|
901
906
|
});
|