@bananapus/721-hook-v6 0.0.42 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/foundry.lock +1 -7
- package/foundry.toml +1 -1
- package/package.json +20 -9
- package/script/Deploy.s.sol +2 -2
- package/src/JB721Checkpoints.sol +61 -19
- package/src/JB721CheckpointsDeployer.sol +10 -5
- package/src/JB721TiersHook.sol +66 -53
- package/src/JB721TiersHookDeployer.sol +8 -5
- package/src/JB721TiersHookProjectDeployer.sol +87 -46
- package/src/JB721TiersHookStore.sol +137 -107
- package/src/abstract/JB721Hook.sol +8 -6
- package/src/interfaces/IJB721Checkpoints.sol +21 -14
- package/src/interfaces/IJB721CheckpointsDeployer.sol +7 -3
- package/src/interfaces/IJB721TiersHook.sol +3 -3
- package/src/interfaces/IJB721TiersHookProjectDeployer.sol +4 -2
- package/src/interfaces/IJB721TiersHookStore.sol +11 -11
- package/src/libraries/JB721TiersHookLib.sol +1 -1
- package/src/structs/JB721TiersHookFlags.sol +1 -1
- package/src/structs/JBPayDataHookRulesetMetadata.sol +1 -1
- package/test/utils/AccessJBLib.sol +49 -0
- package/test/utils/ForTest_JB721TiersHook.sol +246 -0
- package/test/utils/TestBaseWorkflow.sol +213 -0
- package/test/utils/UnitTestSetup.sol +805 -0
- package/.gas-snapshot +0 -152
- package/ADMINISTRATION.md +0 -87
- package/ARCHITECTURE.md +0 -98
- package/AUDIT_INSTRUCTIONS.md +0 -77
- package/RISKS.md +0 -118
- package/SKILLS.md +0 -43
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -121
- package/assets/findings/nana-721-hook-v6-pashov-ai-audit-report-20260330-091257.md +0 -83
- package/slither-ci.config.json +0 -10
- package/test/721HookAttacks.t.sol +0 -408
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +0 -985
- package/test/Fork.t.sol +0 -2346
- package/test/TestAuditGaps.sol +0 -1075
- package/test/TestCheckpoints.t.sol +0 -341
- package/test/TestSafeTransferReentrancy.t.sol +0 -305
- package/test/TestVotingUnitsLifecycle.t.sol +0 -313
- package/test/audit/AuditRegressions.t.sol +0 -83
- package/test/audit/CodexNemesisReserveSellout.t.sol +0 -66
- package/test/audit/CrossCurrencySplitNoPrices.t.sol +0 -123
- package/test/audit/FreshAudit.t.sol +0 -197
- package/test/audit/FutureTierPoC.t.sol +0 -39
- package/test/audit/FutureTierRemoval.t.sol +0 -47
- package/test/audit/Pass12L18.t.sol +0 -80
- package/test/audit/PayCreditsBypassTierSplits.t.sol +0 -200
- package/test/audit/ProjectDeployerAuth.t.sol +0 -266
- package/test/audit/RepoFindings.t.sol +0 -195
- package/test/audit/ReserveActivation.t.sol +0 -87
- package/test/audit/ReserveSlotProtection.t.sol +0 -273
- package/test/audit/RetroactiveReserveBeneficiaryDilution.t.sol +0 -149
- package/test/audit/SameCurrencyDecimalMismatch.t.sol +0 -249
- package/test/audit/SplitCreditsMismatch.t.sol +0 -219
- package/test/audit/SplitFailureRedistribution.t.sol +0 -143
- package/test/audit/USDTVoidReturnCompat.t.sol +0 -301
- package/test/fork/ERC20CashOutFork.t.sol +0 -633
- package/test/fork/ERC20TierSplitFork.t.sol +0 -596
- package/test/fork/IssueTokensForSplitsFork.t.sol +0 -516
- package/test/invariants/TierLifecycleInvariant.t.sol +0 -188
- package/test/invariants/TieredHookStoreInvariant.t.sol +0 -86
- package/test/invariants/handlers/TierLifecycleHandler.sol +0 -300
- package/test/invariants/handlers/TierStoreHandler.sol +0 -165
- package/test/regression/BrokenTerminalDoesNotDos.t.sol +0 -277
- package/test/regression/CacheTierLookup.t.sol +0 -190
- package/test/regression/ProjectDeployerRulesets.t.sol +0 -358
- package/test/regression/ReserveBeneficiaryOverwrite.t.sol +0 -155
- package/test/regression/SplitDistributionBugs.t.sol +0 -751
- package/test/regression/SplitNoBeneficiary.t.sol +0 -140
- package/test/unit/AuditFixes_Unit.t.sol +0 -624
- package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +0 -116
- package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +0 -144
- package/test/unit/JBBitmap.t.sol +0 -170
- package/test/unit/JBIpfsDecoder.t.sol +0 -136
- package/test/unit/TierSupplyReserveCheck.t.sol +0 -221
- package/test/unit/adjustTier_Unit.t.sol +0 -1942
- package/test/unit/deployer_Unit.t.sol +0 -114
- package/test/unit/getters_constructor_Unit.t.sol +0 -593
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +0 -452
- package/test/unit/pay_CrossCurrency_Unit.t.sol +0 -530
- package/test/unit/pay_Unit.t.sol +0 -1661
- package/test/unit/redeem_Unit.t.sol +0 -473
- package/test/unit/relayBeneficiary_Unit.t.sol +0 -182
- package/test/unit/splitHookDistribution_Unit.t.sol +0 -604
- package/test/unit/tierSplitRouting_Unit.t.sol +0 -757
|
@@ -203,19 +203,19 @@ interface IJB721TiersHookStore {
|
|
|
203
203
|
|
|
204
204
|
/// @notice Record newly added tiers.
|
|
205
205
|
/// @param tiersToAdd The tiers to add.
|
|
206
|
-
/// @return tierIds The IDs of the tiers
|
|
206
|
+
/// @return tierIds The IDs of the tiers added.
|
|
207
207
|
function recordAddTiers(JB721TierConfig[] calldata tiersToAdd) external returns (uint256[] memory tierIds);
|
|
208
208
|
|
|
209
|
-
/// @notice
|
|
209
|
+
/// @notice Records the burning of tiered 721 tokens, updating supply tracking for the affected tiers.
|
|
210
210
|
/// @param tokenIds The token IDs of the NFTs to burn.
|
|
211
211
|
function recordBurn(uint256[] calldata tokenIds) external;
|
|
212
212
|
|
|
213
|
-
/// @notice
|
|
213
|
+
/// @notice Stores updated ruleset metadata flags that control minting, transferring, and tier behavior.
|
|
214
214
|
/// @param flags The flags to set.
|
|
215
215
|
function recordFlags(JB721TiersHookFlags calldata flags) external;
|
|
216
216
|
|
|
217
217
|
/// @notice Record 721 mints from the provided tiers.
|
|
218
|
-
/// @param amount The amount
|
|
218
|
+
/// @param amount The amount to spend on NFTs.
|
|
219
219
|
/// @param tierIds The IDs of the tiers to mint from.
|
|
220
220
|
/// @param isOwnerMint Whether this is a direct owner mint.
|
|
221
221
|
/// @return tokenIds The token IDs of the NFTs which were minted.
|
|
@@ -235,13 +235,13 @@ interface IJB721TiersHookStore {
|
|
|
235
235
|
/// @return tokenIds The token IDs of the reserve NFTs which were minted.
|
|
236
236
|
function recordMintReservesFor(uint256 tierId, uint256 count) external returns (uint256[] memory tokenIds);
|
|
237
237
|
|
|
238
|
-
/// @notice Record tiers
|
|
239
|
-
/// @param tierIds The IDs of the tiers
|
|
238
|
+
/// @notice Record tiers to remove.
|
|
239
|
+
/// @param tierIds The IDs of the tiers to remove.
|
|
240
240
|
function recordRemoveTierIds(uint256[] calldata tierIds) external;
|
|
241
241
|
|
|
242
242
|
/// @notice Record the setting of a discount for a tier.
|
|
243
243
|
/// @param tierId The ID of the tier to set the discount of.
|
|
244
|
-
/// @param discountPercent The new discount percent
|
|
244
|
+
/// @param discountPercent The new discount percent to apply.
|
|
245
245
|
function recordSetDiscountPercentOf(uint256 tierId, uint256 discountPercent) external;
|
|
246
246
|
|
|
247
247
|
/// @notice Record a new encoded IPFS URI for a tier.
|
|
@@ -254,9 +254,9 @@ interface IJB721TiersHookStore {
|
|
|
254
254
|
/// @param resolver The resolver to set.
|
|
255
255
|
function recordSetTokenUriResolver(IJB721TokenUriResolver resolver) external;
|
|
256
256
|
|
|
257
|
-
/// @notice
|
|
258
|
-
/// @param tierId The ID of the tier that the 721
|
|
259
|
-
/// @param from The address
|
|
260
|
-
/// @param to The address
|
|
257
|
+
/// @notice Records a 721 token transfer, updating the first-owner tracking for the receiving address.
|
|
258
|
+
/// @param tierId The ID of the tier that the 721 to transfer belongs to.
|
|
259
|
+
/// @param from The address to transfer the 721 from.
|
|
260
|
+
/// @param to The address to transfer the 721 to.
|
|
261
261
|
function recordTransferForTier(uint256 tierId, address from, address to) external;
|
|
262
262
|
}
|
|
@@ -113,7 +113,7 @@ library JB721TiersHookLib {
|
|
|
113
113
|
/// @param splits The splits contract to read tier split groups from.
|
|
114
114
|
/// @param projectId The project ID of the hook.
|
|
115
115
|
/// @param hookAddress The hook address (for computing split group IDs).
|
|
116
|
-
/// @param token The token
|
|
116
|
+
/// @param token The token to distribute.
|
|
117
117
|
/// @param amount The total amount to distribute.
|
|
118
118
|
/// @param decimals The token decimals.
|
|
119
119
|
/// @param encodedSplitData The encoded per-tier breakdown from hookMetadata.
|
|
@@ -8,7 +8,7 @@ pragma solidity ^0.8.0;
|
|
|
8
8
|
/// @custom:member noNewTiersWithOwnerMinting A boolean indicating whether attempts to add new tiers with
|
|
9
9
|
/// `allowOwnerMint` set to true will revert.
|
|
10
10
|
/// @custom:member preventOverspending A boolean indicating whether payments attempting to spend more than the price of
|
|
11
|
-
/// the NFTs
|
|
11
|
+
/// the NFTs to mint will revert.
|
|
12
12
|
/// @custom:member issueTokensForSplits A boolean indicating whether payers receive token credit for the portion of
|
|
13
13
|
/// their payment that is routed to tier splits. When false (default), weight is reduced proportionally.
|
|
14
14
|
struct JB721TiersHookFlags {
|
|
@@ -23,7 +23,7 @@ pragma solidity ^0.8.0;
|
|
|
23
23
|
/// between its tokens.
|
|
24
24
|
/// @custom:member holdFees A flag indicating if fees should be held during this ruleset.
|
|
25
25
|
/// @custom:member useTotalSurplusForCashOut A flag indicating if cash outs should use the project's balance held
|
|
26
|
-
/// in all terminals instead of the project's local terminal balance from which the cash out is
|
|
26
|
+
/// in all terminals instead of the project's local terminal balance from which the cash out is fulfilled.
|
|
27
27
|
/// @custom:member useDataHookForCashOuts A flag indicating if the data hook should be used for cash out transactions
|
|
28
28
|
/// during
|
|
29
29
|
/// this ruleset.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
+
import "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
|
|
6
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
7
|
+
import "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
8
|
+
|
|
9
|
+
contract AccessJBLib {
|
|
10
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
11
|
+
function NATIVE() external pure returns (uint256) {
|
|
12
|
+
return uint32(uint160(JBConstants.NATIVE_TOKEN));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
16
|
+
function USD() external pure returns (uint256) {
|
|
17
|
+
return JBCurrencyIds.USD;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
21
|
+
function NATIVE_TOKEN() external pure returns (address) {
|
|
22
|
+
return JBConstants.NATIVE_TOKEN;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
26
|
+
function MAX_FEE() external pure returns (uint256) {
|
|
27
|
+
return JBConstants.MAX_FEE;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
31
|
+
function MAX_RESERVED_PERCENT() external pure returns (uint256) {
|
|
32
|
+
return JBConstants.MAX_RESERVED_PERCENT;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
36
|
+
function MAX_CASH_OUT_TAX_RATE() external pure returns (uint256) {
|
|
37
|
+
return JBConstants.MAX_CASH_OUT_TAX_RATE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
41
|
+
function MAX_WEIGHT_CUT_PERCENT() external pure returns (uint256) {
|
|
42
|
+
return JBConstants.MAX_WEIGHT_CUT_PERCENT;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
46
|
+
function SPLITS_TOTAL_PERCENT() external pure returns (uint256) {
|
|
47
|
+
return JBConstants.SPLITS_TOTAL_PERCENT;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
+
import "../../src/interfaces/IJB721TiersHook.sol";
|
|
6
|
+
import {JB721TierFlags} from "../../src/structs/JB721TierFlags.sol";
|
|
7
|
+
|
|
8
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
9
|
+
import "../../src/JB721TiersHook.sol";
|
|
10
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
11
|
+
import "../../src/JB721TiersHookStore.sol";
|
|
12
|
+
|
|
13
|
+
import {JB721CheckpointsDeployer} from "../../src/JB721CheckpointsDeployer.sol";
|
|
14
|
+
import {IJB721CheckpointsDeployer} from "../../src/interfaces/IJB721CheckpointsDeployer.sol";
|
|
15
|
+
|
|
16
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
17
|
+
import "../../src/structs/JBBitmapWord.sol";
|
|
18
|
+
|
|
19
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
20
|
+
import "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
21
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
22
|
+
import "@bananapus/core-v6/src/interfaces/IJBPermissioned.sol";
|
|
23
|
+
import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
|
|
24
|
+
|
|
25
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
26
|
+
import "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
27
|
+
|
|
28
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
29
|
+
import "./UnitTestSetup.sol"; // Only used to get the `PAY_HOOK_ID` and `CASH_OUT_HOOK_ID` constants.
|
|
30
|
+
|
|
31
|
+
interface IJB721TiersHookStore_ForTest is IJB721TiersHookStore {
|
|
32
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
33
|
+
function ForTest_dumpTiersList(address nft) external view returns (JB721Tier[] memory tiers);
|
|
34
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
35
|
+
function ForTest_setTier(address hook, uint256 index, JBStored721Tier calldata newTier) external;
|
|
36
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
37
|
+
function ForTest_setTierVotingUnits(address hook, uint256 tierId, uint32 votingUnits) external;
|
|
38
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
39
|
+
function ForTest_setBalanceOf(address hook, address holder, uint256 tier, uint256 balance) external;
|
|
40
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
41
|
+
function ForTest_setReservesMintedFor(address hook, uint256 tier, uint256 amount) external;
|
|
42
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
43
|
+
function ForTest_setIsTierRemoved(address hook, uint256 tokenId) external;
|
|
44
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
45
|
+
function ForTest_packBools(
|
|
46
|
+
bool allowOwnerMint,
|
|
47
|
+
bool transfersPausable,
|
|
48
|
+
bool useVotingUnits,
|
|
49
|
+
bool cantBeRemoved,
|
|
50
|
+
bool cantIncreaseDiscountPercent,
|
|
51
|
+
bool cantBuyWithCredits
|
|
52
|
+
)
|
|
53
|
+
external
|
|
54
|
+
returns (uint8);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// A customized 721 tiers hook for testing purposes.
|
|
58
|
+
contract ForTest_JB721TiersHook is JB721TiersHook {
|
|
59
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
60
|
+
IJB721TiersHookStore_ForTest public test_store;
|
|
61
|
+
MetadataResolverHelper metadataHelper;
|
|
62
|
+
|
|
63
|
+
uint256 constant SURPLUS = 10e18;
|
|
64
|
+
uint256 constant CASH_OUT_TAX_RATE = JBConstants.MAX_CASH_OUT_TAX_RATE; // 40%
|
|
65
|
+
address _trustedForwarder = address(123_456);
|
|
66
|
+
|
|
67
|
+
/// @dev Bundles ForTest_JB721TiersHook constructor args to avoid stack-too-deep.
|
|
68
|
+
struct ForTestInitConfig {
|
|
69
|
+
uint256 projectId;
|
|
70
|
+
string name;
|
|
71
|
+
string symbol;
|
|
72
|
+
string baseUri;
|
|
73
|
+
IJB721TokenUriResolver tokenUriResolver;
|
|
74
|
+
string contractUri;
|
|
75
|
+
JB721TierConfig[] tiers;
|
|
76
|
+
JB721TiersHookFlags flags;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
constructor(
|
|
80
|
+
ForTestInitConfig memory config,
|
|
81
|
+
IJBDirectory directory,
|
|
82
|
+
IJBPrices prices,
|
|
83
|
+
IJBRulesets rulesets,
|
|
84
|
+
IJB721TiersHookStore store,
|
|
85
|
+
IJBSplits splits
|
|
86
|
+
)
|
|
87
|
+
// The directory is also `IJBPermissioned`.
|
|
88
|
+
JB721TiersHook(
|
|
89
|
+
directory,
|
|
90
|
+
IJBPermissioned(address(directory)).PERMISSIONS(),
|
|
91
|
+
prices,
|
|
92
|
+
rulesets,
|
|
93
|
+
store,
|
|
94
|
+
splits,
|
|
95
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer(store))),
|
|
96
|
+
_trustedForwarder
|
|
97
|
+
)
|
|
98
|
+
{
|
|
99
|
+
// Reset the _initialized flag set by the parent constructor so this test contract can initialize itself.
|
|
100
|
+
_initialized = false;
|
|
101
|
+
JB721TiersHook.initialize(
|
|
102
|
+
config.projectId,
|
|
103
|
+
config.name,
|
|
104
|
+
config.symbol,
|
|
105
|
+
config.baseUri,
|
|
106
|
+
config.tokenUriResolver,
|
|
107
|
+
config.contractUri,
|
|
108
|
+
JB721InitTiersConfig({
|
|
109
|
+
tiers: config.tiers, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
|
|
110
|
+
}),
|
|
111
|
+
config.flags
|
|
112
|
+
);
|
|
113
|
+
test_store = IJB721TiersHookStore_ForTest(address(store));
|
|
114
|
+
|
|
115
|
+
metadataHelper = new MetadataResolverHelper();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
119
|
+
function ForTest_setOwnerOf(uint256 tokenId, address owner) public {
|
|
120
|
+
_owners[tokenId] = owner;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function burn(uint256[] memory tokenIds) public {
|
|
124
|
+
for (uint256 i; i < tokenIds.length; i++) {
|
|
125
|
+
_burn(tokenIds[i]);
|
|
126
|
+
}
|
|
127
|
+
STORE.recordBurn(tokenIds);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// A customized 721 tiers hook store for testing purposes.
|
|
132
|
+
contract ForTest_JB721TiersHookStore is JB721TiersHookStore, IJB721TiersHookStore_ForTest {
|
|
133
|
+
using JBBitmap for mapping(uint256 => uint256);
|
|
134
|
+
using JBBitmap for JBBitmapWord;
|
|
135
|
+
|
|
136
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
137
|
+
function ForTest_dumpTiersList(address nft) public view override returns (JB721Tier[] memory tiers) {
|
|
138
|
+
// Keep a reference to the max tier ID.
|
|
139
|
+
uint256 maxTierId = maxTierIdOf[nft];
|
|
140
|
+
// Initialize an array with the appropriate length.
|
|
141
|
+
tiers = new JB721Tier[](maxTierId);
|
|
142
|
+
// Count the number of included tiers.
|
|
143
|
+
uint256 numberOfIncludedTiers;
|
|
144
|
+
// Get a reference to the sorted index being iterated on, starting with the first one.
|
|
145
|
+
uint256 currentSortIndex = _firstSortedTierIdOf(nft, 0);
|
|
146
|
+
// Keep a reference to the tier being iterated on.
|
|
147
|
+
JBStored721Tier memory storedTier;
|
|
148
|
+
// Make the sorted array.
|
|
149
|
+
while (currentSortIndex != 0 && numberOfIncludedTiers < maxTierId) {
|
|
150
|
+
storedTier = _storedTierOf[nft][currentSortIndex];
|
|
151
|
+
|
|
152
|
+
// Unpack stored tier.
|
|
153
|
+
(bool allowOwnerMint, bool transfersPausable,,,,) = _unpackBools(storedTier.packedBools);
|
|
154
|
+
|
|
155
|
+
// Add the tier to the array being returned.
|
|
156
|
+
tiers[numberOfIncludedTiers++] = JB721Tier({
|
|
157
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
158
|
+
id: uint32(currentSortIndex),
|
|
159
|
+
price: storedTier.price,
|
|
160
|
+
remainingSupply: storedTier.remainingSupply,
|
|
161
|
+
initialSupply: storedTier.initialSupply,
|
|
162
|
+
votingUnits: storedTier.price,
|
|
163
|
+
reserveFrequency: storedTier.reserveFrequency,
|
|
164
|
+
reserveBeneficiary: reserveBeneficiaryOf(nft, currentSortIndex),
|
|
165
|
+
encodedIPFSUri: encodedIPFSUriOf[nft][currentSortIndex],
|
|
166
|
+
category: storedTier.category,
|
|
167
|
+
discountPercent: storedTier.discountPercent,
|
|
168
|
+
flags: JB721TierFlags({
|
|
169
|
+
allowOwnerMint: allowOwnerMint,
|
|
170
|
+
transfersPausable: transfersPausable,
|
|
171
|
+
cantBeRemoved: false,
|
|
172
|
+
cantIncreaseDiscountPercent: false,
|
|
173
|
+
cantBuyWithCredits: false
|
|
174
|
+
}),
|
|
175
|
+
splitPercent: storedTier.splitPercent,
|
|
176
|
+
resolvedUri: ""
|
|
177
|
+
});
|
|
178
|
+
// Set the next sort index.
|
|
179
|
+
currentSortIndex = _nextSortedTierIdOf(nft, currentSortIndex, maxTierId);
|
|
180
|
+
}
|
|
181
|
+
// Drop the empty tiers at the end of the array.
|
|
182
|
+
// The array's size is based on `maxTierIdOf`, which *might* exceed the actual number of tiers.
|
|
183
|
+
for (uint256 i = tiers.length - 1; i >= 0; i--) {
|
|
184
|
+
if (tiers[i].id == 0) {
|
|
185
|
+
assembly ("memory-safe") {
|
|
186
|
+
mstore(tiers, sub(mload(tiers), 1))
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
195
|
+
function ForTest_setTier(address hook, uint256 index, JBStored721Tier calldata newTier) public override {
|
|
196
|
+
_storedTierOf[address(hook)][index] = newTier;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
200
|
+
function ForTest_setTierVotingUnits(address hook, uint256 tierId, uint32 votingUnits) public override {
|
|
201
|
+
_tierVotingUnitsOf[address(hook)][tierId] = votingUnits;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
205
|
+
function ForTest_setBalanceOf(address hook, address holder, uint256 tier, uint256 balance) public override {
|
|
206
|
+
tierBalanceOf[address(hook)][holder][tier] = balance;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
210
|
+
function ForTest_setReservesMintedFor(address hook, uint256 tier, uint256 amount) public override {
|
|
211
|
+
numberOfReservesMintedFor[address(hook)][tier] = amount;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
215
|
+
function ForTest_setIsTierRemoved(address hook, uint256 tokenId) public override {
|
|
216
|
+
_removedTiersBitmapWordOf[hook].removeTier(tokenId);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
220
|
+
function ForTest_packBools(
|
|
221
|
+
bool allowOwnerMint,
|
|
222
|
+
bool transfersPausable,
|
|
223
|
+
bool useVotingUnits,
|
|
224
|
+
bool cantBeRemoved,
|
|
225
|
+
bool cantIncreaseDiscountPercent,
|
|
226
|
+
bool cantBuyWithCredits
|
|
227
|
+
)
|
|
228
|
+
public
|
|
229
|
+
pure
|
|
230
|
+
returns (uint8)
|
|
231
|
+
{
|
|
232
|
+
return _packBools(
|
|
233
|
+
allowOwnerMint,
|
|
234
|
+
transfersPausable,
|
|
235
|
+
useVotingUnits,
|
|
236
|
+
cantBeRemoved,
|
|
237
|
+
cantIncreaseDiscountPercent,
|
|
238
|
+
cantBuyWithCredits
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
243
|
+
function ForTest_unpackBools(uint8 packed) public pure returns (bool, bool, bool, bool, bool, bool) {
|
|
244
|
+
return _unpackBools(packed);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
+
import "@bananapus/core-v6/src/JBController.sol";
|
|
6
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
7
|
+
import "@bananapus/core-v6/src/JBDirectory.sol";
|
|
8
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
9
|
+
import "@bananapus/core-v6/src/JBMultiTerminal.sol";
|
|
10
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
11
|
+
import "@bananapus/core-v6/src/JBFundAccessLimits.sol";
|
|
12
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
13
|
+
import "@bananapus/core-v6/src/JBFeelessAddresses.sol";
|
|
14
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
15
|
+
import "@bananapus/core-v6/src/JBTerminalStore.sol";
|
|
16
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
17
|
+
import "@bananapus/core-v6/src/JBRulesets.sol";
|
|
18
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
19
|
+
import "@bananapus/core-v6/src/JBPermissions.sol";
|
|
20
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
21
|
+
import "@bananapus/core-v6/src/JBPrices.sol";
|
|
22
|
+
import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
|
|
23
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
24
|
+
import "@bananapus/core-v6/src/JBSplits.sol";
|
|
25
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
26
|
+
import "@bananapus/core-v6/src/JBERC20.sol";
|
|
27
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
28
|
+
import "@bananapus/core-v6/src/JBTokens.sol";
|
|
29
|
+
|
|
30
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
31
|
+
import "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
|
|
32
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
33
|
+
import "@bananapus/core-v6/src/structs/JBAfterCashOutRecordedContext.sol";
|
|
34
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
35
|
+
import "@bananapus/core-v6/src/structs/JBFee.sol";
|
|
36
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
37
|
+
import "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
38
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
39
|
+
import "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
40
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
41
|
+
import "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
42
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
43
|
+
import "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
44
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
45
|
+
import "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
|
|
46
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
47
|
+
import "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
|
|
48
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
49
|
+
import "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
|
|
50
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
51
|
+
import "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
52
|
+
|
|
53
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
54
|
+
import "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
55
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
56
|
+
import "@bananapus/core-v6/src/interfaces/IJBToken.sol";
|
|
57
|
+
|
|
58
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
59
|
+
import "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
60
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
61
|
+
import "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
62
|
+
|
|
63
|
+
// forge-lint: disable-next-line(unused-import)
|
|
64
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
65
|
+
|
|
66
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
67
|
+
import "forge-std/Test.sol";
|
|
68
|
+
|
|
69
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
70
|
+
import "./AccessJBLib.sol";
|
|
71
|
+
|
|
72
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
73
|
+
import "../../src/structs/JBPayDataHookRulesetConfig.sol";
|
|
74
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
75
|
+
import "../../src/structs/JBPayDataHookRulesetMetadata.sol";
|
|
76
|
+
|
|
77
|
+
/// @notice Base contract for Juicebox system tests.
|
|
78
|
+
/// @dev Provides common functionality, such as deploying contracts on test setup.
|
|
79
|
+
contract TestBaseWorkflow is Test {
|
|
80
|
+
//*********************************************************************//
|
|
81
|
+
// --------------------- internal stored properties ------------------- //
|
|
82
|
+
//*********************************************************************//
|
|
83
|
+
|
|
84
|
+
address internal projectOwner = address(123);
|
|
85
|
+
address internal beneficiary = address(69_420);
|
|
86
|
+
address internal caller = address(696_969);
|
|
87
|
+
|
|
88
|
+
JBPermissions internal jbPermissions;
|
|
89
|
+
JBProjects internal jbProjects;
|
|
90
|
+
JBPrices internal jbPrices;
|
|
91
|
+
JBDirectory internal jbDirectory;
|
|
92
|
+
JBRulesets internal jbRulesets;
|
|
93
|
+
JBTokens internal jbTokens;
|
|
94
|
+
JBFundAccessLimits internal jbFundAccessLimits;
|
|
95
|
+
JBFeelessAddresses internal jbFeelessAddresses;
|
|
96
|
+
JBSplits internal jbSplits;
|
|
97
|
+
JBController internal jbController;
|
|
98
|
+
JBTerminalStore internal jbTerminalStore;
|
|
99
|
+
JBMultiTerminal internal jbMultiTerminal;
|
|
100
|
+
string internal projectUri;
|
|
101
|
+
IJBToken internal tokenV2;
|
|
102
|
+
|
|
103
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
104
|
+
AccessJBLib internal accessJBLib;
|
|
105
|
+
|
|
106
|
+
//*********************************************************************//
|
|
107
|
+
// --------------------------- test setup ---------------------------- //
|
|
108
|
+
//*********************************************************************//
|
|
109
|
+
|
|
110
|
+
// Deploys and initializes contracts for testing.
|
|
111
|
+
function setUp() public virtual {
|
|
112
|
+
// ---- Set up project ---- //
|
|
113
|
+
jbPermissions = new JBPermissions(address(0));
|
|
114
|
+
vm.label(address(jbPermissions), "JBPermissions");
|
|
115
|
+
|
|
116
|
+
jbProjects = new JBProjects(projectOwner, address(0), address(0));
|
|
117
|
+
vm.label(address(jbProjects), "JBProjects");
|
|
118
|
+
|
|
119
|
+
jbDirectory = new JBDirectory(jbPermissions, jbProjects, projectOwner);
|
|
120
|
+
vm.label(address(jbDirectory), "JBDirectory");
|
|
121
|
+
|
|
122
|
+
jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, projectOwner, address(0));
|
|
123
|
+
vm.label(address(jbPrices), "JBPrices");
|
|
124
|
+
|
|
125
|
+
jbRulesets = new JBRulesets(jbDirectory);
|
|
126
|
+
vm.label(address(jbRulesets), "JBRulesets");
|
|
127
|
+
|
|
128
|
+
jbFundAccessLimits = new JBFundAccessLimits(jbDirectory);
|
|
129
|
+
vm.label(address(jbFundAccessLimits), "JBFundAccessLimits");
|
|
130
|
+
|
|
131
|
+
jbFeelessAddresses = new JBFeelessAddresses(address(69));
|
|
132
|
+
vm.label(address(jbFeelessAddresses), "JBFeelessAddresses");
|
|
133
|
+
|
|
134
|
+
jbTokens = new JBTokens(jbDirectory, new JBERC20(jbPermissions, jbProjects));
|
|
135
|
+
vm.label(address(jbTokens), "JBTokens");
|
|
136
|
+
|
|
137
|
+
jbSplits = new JBSplits(jbDirectory);
|
|
138
|
+
vm.label(address(jbSplits), "JBSplits");
|
|
139
|
+
|
|
140
|
+
jbController = new JBController(
|
|
141
|
+
jbDirectory,
|
|
142
|
+
jbFundAccessLimits,
|
|
143
|
+
jbPermissions,
|
|
144
|
+
jbPrices,
|
|
145
|
+
jbProjects,
|
|
146
|
+
jbRulesets,
|
|
147
|
+
jbSplits,
|
|
148
|
+
jbTokens,
|
|
149
|
+
address(0),
|
|
150
|
+
address(0)
|
|
151
|
+
);
|
|
152
|
+
vm.label(address(jbController), "JBController");
|
|
153
|
+
|
|
154
|
+
vm.prank(projectOwner);
|
|
155
|
+
jbDirectory.setIsAllowedToSetFirstController(address(jbController), true);
|
|
156
|
+
|
|
157
|
+
jbTerminalStore = new JBTerminalStore(jbDirectory, jbPrices, jbRulesets);
|
|
158
|
+
vm.label(address(jbTerminalStore), "JBTerminalStore");
|
|
159
|
+
|
|
160
|
+
accessJBLib = new AccessJBLib();
|
|
161
|
+
|
|
162
|
+
jbMultiTerminal = new JBMultiTerminal(
|
|
163
|
+
jbFeelessAddresses,
|
|
164
|
+
jbPermissions,
|
|
165
|
+
jbProjects,
|
|
166
|
+
jbSplits,
|
|
167
|
+
jbTerminalStore,
|
|
168
|
+
jbTokens,
|
|
169
|
+
IPermit2(address(0)),
|
|
170
|
+
address(0)
|
|
171
|
+
);
|
|
172
|
+
vm.label(address(jbMultiTerminal), "JBMultiTerminal");
|
|
173
|
+
|
|
174
|
+
projectUri = "myIPFSHash";
|
|
175
|
+
|
|
176
|
+
// ---- general setup ---- //
|
|
177
|
+
vm.deal(beneficiary, 100 ether);
|
|
178
|
+
vm.deal(projectOwner, 100 ether);
|
|
179
|
+
vm.deal(caller, 100 ether);
|
|
180
|
+
|
|
181
|
+
vm.label(projectOwner, "projectOwner");
|
|
182
|
+
vm.label(beneficiary, "beneficiary");
|
|
183
|
+
vm.label(caller, "caller");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//https://ethereum.stackexchange.com/questions/24248/how-to-calculate-an-ethereum-contracts-address-during-its-creation-using-the-so
|
|
187
|
+
function addressFrom(address origin, uint256 nonce) internal pure returns (address addr) {
|
|
188
|
+
bytes memory data;
|
|
189
|
+
if (nonce == 0x00) {
|
|
190
|
+
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, bytes1(0x80));
|
|
191
|
+
} else if (nonce <= 0x7f) {
|
|
192
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
193
|
+
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, uint8(nonce));
|
|
194
|
+
} else if (nonce <= 0xff) {
|
|
195
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
196
|
+
data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), origin, bytes1(0x81), uint8(nonce));
|
|
197
|
+
} else if (nonce <= 0xffff) {
|
|
198
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
199
|
+
data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), origin, bytes1(0x82), uint16(nonce));
|
|
200
|
+
} else if (nonce <= 0xffffff) {
|
|
201
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
202
|
+
data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), origin, bytes1(0x83), uint24(nonce));
|
|
203
|
+
} else {
|
|
204
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
205
|
+
data = abi.encodePacked(bytes1(0xda), bytes1(0x94), origin, bytes1(0x84), uint32(nonce));
|
|
206
|
+
}
|
|
207
|
+
bytes32 hash = keccak256(data);
|
|
208
|
+
assembly ("memory-safe") {
|
|
209
|
+
mstore(0, hash)
|
|
210
|
+
addr := mload(0)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|