@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.
Files changed (86) hide show
  1. package/foundry.lock +1 -7
  2. package/foundry.toml +1 -1
  3. package/package.json +20 -9
  4. package/script/Deploy.s.sol +2 -2
  5. package/src/JB721Checkpoints.sol +61 -19
  6. package/src/JB721CheckpointsDeployer.sol +10 -5
  7. package/src/JB721TiersHook.sol +66 -53
  8. package/src/JB721TiersHookDeployer.sol +8 -5
  9. package/src/JB721TiersHookProjectDeployer.sol +87 -46
  10. package/src/JB721TiersHookStore.sol +137 -107
  11. package/src/abstract/JB721Hook.sol +8 -6
  12. package/src/interfaces/IJB721Checkpoints.sol +21 -14
  13. package/src/interfaces/IJB721CheckpointsDeployer.sol +7 -3
  14. package/src/interfaces/IJB721TiersHook.sol +3 -3
  15. package/src/interfaces/IJB721TiersHookProjectDeployer.sol +4 -2
  16. package/src/interfaces/IJB721TiersHookStore.sol +11 -11
  17. package/src/libraries/JB721TiersHookLib.sol +1 -1
  18. package/src/structs/JB721TiersHookFlags.sol +1 -1
  19. package/src/structs/JBPayDataHookRulesetMetadata.sol +1 -1
  20. package/test/utils/AccessJBLib.sol +49 -0
  21. package/test/utils/ForTest_JB721TiersHook.sol +246 -0
  22. package/test/utils/TestBaseWorkflow.sol +213 -0
  23. package/test/utils/UnitTestSetup.sol +805 -0
  24. package/.gas-snapshot +0 -152
  25. package/ADMINISTRATION.md +0 -87
  26. package/ARCHITECTURE.md +0 -98
  27. package/AUDIT_INSTRUCTIONS.md +0 -77
  28. package/RISKS.md +0 -118
  29. package/SKILLS.md +0 -43
  30. package/STYLE_GUIDE.md +0 -610
  31. package/USER_JOURNEYS.md +0 -121
  32. package/assets/findings/nana-721-hook-v6-pashov-ai-audit-report-20260330-091257.md +0 -83
  33. package/slither-ci.config.json +0 -10
  34. package/test/721HookAttacks.t.sol +0 -408
  35. package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +0 -985
  36. package/test/Fork.t.sol +0 -2346
  37. package/test/TestAuditGaps.sol +0 -1075
  38. package/test/TestCheckpoints.t.sol +0 -341
  39. package/test/TestSafeTransferReentrancy.t.sol +0 -305
  40. package/test/TestVotingUnitsLifecycle.t.sol +0 -313
  41. package/test/audit/AuditRegressions.t.sol +0 -83
  42. package/test/audit/CodexNemesisReserveSellout.t.sol +0 -66
  43. package/test/audit/CrossCurrencySplitNoPrices.t.sol +0 -123
  44. package/test/audit/FreshAudit.t.sol +0 -197
  45. package/test/audit/FutureTierPoC.t.sol +0 -39
  46. package/test/audit/FutureTierRemoval.t.sol +0 -47
  47. package/test/audit/Pass12L18.t.sol +0 -80
  48. package/test/audit/PayCreditsBypassTierSplits.t.sol +0 -200
  49. package/test/audit/ProjectDeployerAuth.t.sol +0 -266
  50. package/test/audit/RepoFindings.t.sol +0 -195
  51. package/test/audit/ReserveActivation.t.sol +0 -87
  52. package/test/audit/ReserveSlotProtection.t.sol +0 -273
  53. package/test/audit/RetroactiveReserveBeneficiaryDilution.t.sol +0 -149
  54. package/test/audit/SameCurrencyDecimalMismatch.t.sol +0 -249
  55. package/test/audit/SplitCreditsMismatch.t.sol +0 -219
  56. package/test/audit/SplitFailureRedistribution.t.sol +0 -143
  57. package/test/audit/USDTVoidReturnCompat.t.sol +0 -301
  58. package/test/fork/ERC20CashOutFork.t.sol +0 -633
  59. package/test/fork/ERC20TierSplitFork.t.sol +0 -596
  60. package/test/fork/IssueTokensForSplitsFork.t.sol +0 -516
  61. package/test/invariants/TierLifecycleInvariant.t.sol +0 -188
  62. package/test/invariants/TieredHookStoreInvariant.t.sol +0 -86
  63. package/test/invariants/handlers/TierLifecycleHandler.sol +0 -300
  64. package/test/invariants/handlers/TierStoreHandler.sol +0 -165
  65. package/test/regression/BrokenTerminalDoesNotDos.t.sol +0 -277
  66. package/test/regression/CacheTierLookup.t.sol +0 -190
  67. package/test/regression/ProjectDeployerRulesets.t.sol +0 -358
  68. package/test/regression/ReserveBeneficiaryOverwrite.t.sol +0 -155
  69. package/test/regression/SplitDistributionBugs.t.sol +0 -751
  70. package/test/regression/SplitNoBeneficiary.t.sol +0 -140
  71. package/test/unit/AuditFixes_Unit.t.sol +0 -624
  72. package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +0 -116
  73. package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +0 -144
  74. package/test/unit/JBBitmap.t.sol +0 -170
  75. package/test/unit/JBIpfsDecoder.t.sol +0 -136
  76. package/test/unit/TierSupplyReserveCheck.t.sol +0 -221
  77. package/test/unit/adjustTier_Unit.t.sol +0 -1942
  78. package/test/unit/deployer_Unit.t.sol +0 -114
  79. package/test/unit/getters_constructor_Unit.t.sol +0 -593
  80. package/test/unit/mintFor_mintReservesFor_Unit.t.sol +0 -452
  81. package/test/unit/pay_CrossCurrency_Unit.t.sol +0 -530
  82. package/test/unit/pay_Unit.t.sol +0 -1661
  83. package/test/unit/redeem_Unit.t.sol +0 -473
  84. package/test/unit/relayBeneficiary_Unit.t.sol +0 -182
  85. package/test/unit/splitHookDistribution_Unit.t.sol +0 -604
  86. package/test/unit/tierSplitRouting_Unit.t.sol +0 -757
@@ -1,197 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {UnitTestSetup} from "../utils/UnitTestSetup.sol";
5
- import {ForTest_JB721TiersHook} from "../utils/ForTest_JB721TiersHook.sol";
6
- import {IJB721TiersHookStore} from "../../src/interfaces/IJB721TiersHookStore.sol";
7
- import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
8
- import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
9
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
10
- import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
11
- import {JBAfterPayRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
12
- import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
13
- import {JBTokenAmount} from "@bananapus/core-v6/src/structs/JBTokenAmount.sol";
14
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
15
- import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
16
- import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
17
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
18
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
19
-
20
- contract FreshAudit is UnitTestSetup {
21
- function _buildPayMetadata(
22
- address hookAddress,
23
- bool allowOverspending,
24
- uint16[] memory tierIdsToMint
25
- )
26
- internal
27
- view
28
- returns (bytes memory)
29
- {
30
- bytes[] memory data = new bytes[](1);
31
- data[0] = abi.encode(allowOverspending, tierIdsToMint);
32
-
33
- bytes4[] memory ids = new bytes4[](1);
34
- ids[0] = metadataHelper.getId("pay", hookAddress);
35
-
36
- return metadataHelper.createMetadata(ids, data);
37
- }
38
-
39
- function _nativeAmount(uint256 value) internal pure returns (JBTokenAmount memory) {
40
- return JBTokenAmount({
41
- token: JBConstants.NATIVE_TOKEN,
42
- value: value,
43
- decimals: 18,
44
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
45
- });
46
- }
47
-
48
- function test_payCredits_can_underfund_split_bearing_tier_mints() public {
49
- ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
50
- IJB721TiersHookStore hookStore = testHook.STORE();
51
- address splitReceiver = makeAddr("splitReceiver");
52
-
53
- JB721TierConfig[] memory tiersToAdd = new JB721TierConfig[](1);
54
- tiersToAdd[0] = JB721TierConfig({
55
- price: uint104(1 ether),
56
- initialSupply: uint32(10),
57
- votingUnits: 0,
58
- reserveFrequency: 0,
59
- reserveBeneficiary: address(0),
60
- encodedIPFSUri: bytes32(uint256(1)),
61
- category: uint24(1),
62
- discountPercent: 0,
63
- flags: JB721TierConfigFlags({
64
- allowOwnerMint: false,
65
- useReserveBeneficiaryAsDefault: false,
66
- transfersPausable: false,
67
- useVotingUnits: false,
68
- cantBeRemoved: false,
69
- cantIncreaseDiscountPercent: false,
70
- cantBuyWithCredits: false
71
- }),
72
- splitPercent: uint32(JBConstants.SPLITS_TOTAL_PERCENT),
73
- splits: new JBSplit[](0)
74
- });
75
-
76
- vm.prank(owner);
77
- testHook.adjustTiers(tiersToAdd, new uint256[](0));
78
-
79
- JBSplit[] memory splits = new JBSplit[](1);
80
- splits[0] = JBSplit({
81
- percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT),
82
- projectId: 0,
83
- beneficiary: payable(splitReceiver),
84
- preferAddToBalance: false,
85
- lockedUntil: 0,
86
- hook: IJBSplitHook(address(0))
87
- });
88
-
89
- uint256 groupId = uint256(uint160(address(testHook))) | (uint256(1) << 160);
90
- vm.mockCall(
91
- mockJBSplits, abi.encodeWithSelector(IJBSplits.splitsOf.selector, projectId, 0, groupId), abi.encode(splits)
92
- );
93
- vm.mockCall(
94
- mockJBDirectory,
95
- abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
96
- abi.encode(true)
97
- );
98
-
99
- JBAfterPayRecordedContext memory seedCredits = JBAfterPayRecordedContext({
100
- payer: beneficiary,
101
- projectId: projectId,
102
- rulesetId: 0,
103
- amount: _nativeAmount(1 ether),
104
- forwardedAmount: _nativeAmount(0),
105
- weight: 10e18,
106
- newlyIssuedTokenCount: 0,
107
- beneficiary: beneficiary,
108
- hookMetadata: bytes(""),
109
- payerMetadata: bytes("")
110
- });
111
-
112
- vm.prank(mockTerminalAddress);
113
- testHook.afterPayRecordedWith(seedCredits);
114
- assertEq(testHook.payCreditsOf(beneficiary), 1 ether, "setup: credits should be seeded");
115
-
116
- uint16[] memory tierIds = new uint16[](1);
117
- tierIds[0] = 1;
118
- bytes memory payerMetadata = _buildPayMetadata(address(testHook), true, tierIds);
119
-
120
- JBBeforePayRecordedContext memory beforeContext = JBBeforePayRecordedContext({
121
- terminal: mockTerminalAddress,
122
- payer: beneficiary,
123
- amount: _nativeAmount(1),
124
- projectId: projectId,
125
- rulesetId: 0,
126
- beneficiary: beneficiary,
127
- weight: 10e18,
128
- reservedPercent: 0,
129
- metadata: payerMetadata
130
- });
131
-
132
- (uint256 weight, JBPayHookSpecification[] memory hookSpecifications) =
133
- testHook.beforePayRecordedWith(beforeContext);
134
-
135
- assertEq(weight, 0, "all fresh payment value is treated as split-routed");
136
- assertEq(hookSpecifications.length, 1, "expected single pay hook spec");
137
- assertEq(hookSpecifications[0].amount, 1, "split forwarding is capped to the fresh payment only");
138
-
139
- JBAfterPayRecordedContext memory mintWithCredits = JBAfterPayRecordedContext({
140
- payer: beneficiary,
141
- projectId: projectId,
142
- rulesetId: 0,
143
- amount: _nativeAmount(1),
144
- forwardedAmount: _nativeAmount(hookSpecifications[0].amount),
145
- weight: weight,
146
- newlyIssuedTokenCount: 0,
147
- beneficiary: beneficiary,
148
- hookMetadata: hookSpecifications[0].metadata,
149
- payerMetadata: payerMetadata
150
- });
151
-
152
- vm.deal(mockTerminalAddress, 1);
153
- vm.prank(mockTerminalAddress);
154
- testHook.afterPayRecordedWith{value: 1}(mintWithCredits);
155
-
156
- assertEq(testHook.balanceOf(beneficiary), 1, "beneficiary still mints the split-bearing NFT");
157
- assertEq(testHook.payCreditsOf(beneficiary), 1, "stored credits fund essentially the entire mint");
158
- assertEq(splitReceiver.balance, 1, "split receiver only receives the fresh 1 wei payment");
159
- assertEq(hookStore.totalCashOutWeight(address(testHook)), 1 ether, "full-price NFT still enters cash-out math");
160
- }
161
-
162
- /// @notice Creating a tier with reserveFrequency > 0 and no beneficiary (tier-specific or default)
163
- /// is now rejected at creation time, preventing the retroactive dilution bug.
164
- function test_new_default_reserve_beneficiary_retroactively_dilutes_existing_tiers() public {
165
- ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
166
-
167
- JB721TierConfig[] memory initialTier = new JB721TierConfig[](1);
168
- initialTier[0] = JB721TierConfig({
169
- price: uint104(1 ether),
170
- initialSupply: uint32(5),
171
- votingUnits: 0,
172
- reserveFrequency: uint16(2),
173
- reserveBeneficiary: address(0),
174
- encodedIPFSUri: bytes32(uint256(2)),
175
- category: uint24(1),
176
- discountPercent: 0,
177
- flags: JB721TierConfigFlags({
178
- allowOwnerMint: false,
179
- useReserveBeneficiaryAsDefault: false,
180
- transfersPausable: false,
181
- useVotingUnits: false,
182
- cantBeRemoved: false,
183
- cantIncreaseDiscountPercent: false,
184
- cantBuyWithCredits: false
185
- }),
186
- splitPercent: 0,
187
- splits: new JBSplit[](0)
188
- });
189
-
190
- // The new creation-time check prevents tiers with reserves but no beneficiary.
191
- vm.expectRevert(
192
- abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_MissingReserveBeneficiary.selector, 1)
193
- );
194
- vm.prank(owner);
195
- testHook.adjustTiers(initialTier, new uint256[](0));
196
- }
197
- }
@@ -1,39 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {UnitTestSetup} from "../utils/UnitTestSetup.sol";
5
- import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
6
- import {IJB721TokenUriResolver} from "../../src/interfaces/IJB721TokenUriResolver.sol";
7
- import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
8
-
9
- contract FutureTierPoC is UnitTestSetup {
10
- function test_futureTierRemovalPersistsIntoNewTierAndBricksMint() external {
11
- hook = _initHookDefaultTiers(0);
12
-
13
- uint256[] memory futureTierIds = new uint256[](1);
14
- futureTierIds[0] = 1;
15
-
16
- // L-18 FIX: Removing a future (nonexistent) tier ID now reverts,
17
- // preventing the "born removed" bug entirely.
18
- vm.prank(owner);
19
- vm.expectRevert(abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_UnrecognizedTier.selector, 1));
20
- hook.adjustTiers(new JB721TierConfig[](0), futureTierIds);
21
- }
22
-
23
- function test_futureTierUriCanBePoisonedBeforeTierExists() external {
24
- hook = _initHookDefaultTiers(0);
25
-
26
- bytes32 poisonedUri = bytes32(uint256(0x1234));
27
-
28
- vm.prank(owner);
29
- hook.setMetadata("", "", "", "", IJB721TokenUriResolver(address(hook)), 1, poisonedUri);
30
-
31
- (JB721TierConfig[] memory tiersToAdd,) = _createTiers(defaultTierConfig, 1);
32
- tiersToAdd[0].encodedIPFSUri = bytes32(0);
33
-
34
- vm.prank(owner);
35
- hook.adjustTiers(tiersToAdd, new uint256[](0));
36
-
37
- assertEq(hook.STORE().encodedIPFSUriOf(address(hook), 1), poisonedUri, "future tier inherited stale uri");
38
- }
39
- }
@@ -1,47 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
6
-
7
- import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
8
- import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
9
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
10
-
11
- contract Test_FutureTierRemoval is Test {
12
- JB721TiersHookStore internal store;
13
-
14
- function setUp() external {
15
- store = new JB721TiersHookStore();
16
- }
17
-
18
- function test_futureRemovedTierIdIsBornRemovedAndCannotMint() external {
19
- JB721TierConfig[] memory firstTier = new JB721TierConfig[](1);
20
- firstTier[0] = _tier(1);
21
- uint256[] memory firstIds = store.recordAddTiers(firstTier);
22
- assertEq(firstIds[0], 1);
23
-
24
- // Attempting to remove a future (nonexistent) tier ID should now revert
25
- // thanks to the L-18 fix, preventing the "born removed" bug.
26
- uint256[] memory futureIds = new uint256[](1);
27
- futureIds[0] = 2;
28
- vm.expectRevert(abi.encodeWithSignature("JB721TiersHookStore_UnrecognizedTier(uint256)", 2));
29
- store.recordRemoveTierIds(futureIds);
30
- }
31
-
32
- function _tier(uint24 category) internal pure returns (JB721TierConfig memory tier) {
33
- tier.price = 1;
34
- tier.initialSupply = 10;
35
- tier.category = category;
36
- tier.flags = JB721TierConfigFlags({
37
- allowOwnerMint: false,
38
- useReserveBeneficiaryAsDefault: false,
39
- transfersPausable: false,
40
- useVotingUnits: false,
41
- cantBeRemoved: false,
42
- cantIncreaseDiscountPercent: false,
43
- cantBuyWithCredits: false
44
- });
45
- tier.splits = new JBSplit[](0);
46
- }
47
- }
@@ -1,80 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
6
-
7
- import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
8
- import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
9
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
10
-
11
- /// @notice L-18: Prevent future tier pre-removal.
12
- /// Removing a tier ID that does not yet exist should revert with `UnrecognizedTier`.
13
- contract Pass12L18 is Test {
14
- JB721TiersHookStore internal store;
15
-
16
- function setUp() external {
17
- store = new JB721TiersHookStore();
18
- }
19
-
20
- /// @notice Removing a future tier ID (beyond maxTierIdOf) must revert.
21
- function test_L18_fix_reverts_future_removal() external {
22
- // Add 5 tiers so maxTierIdOf == 5.
23
- JB721TierConfig[] memory tiers = new JB721TierConfig[](5);
24
- for (uint256 i; i < 5; i++) {
25
- tiers[i] = _tier(uint24(i + 1));
26
- }
27
- store.recordAddTiers(tiers);
28
- assertEq(store.maxTierIdOf(address(this)), 5);
29
-
30
- // Attempt to remove tier 10 (future) — should revert.
31
- uint256[] memory futureTierIds = new uint256[](1);
32
- futureTierIds[0] = 10;
33
- vm.expectRevert(abi.encodeWithSignature("JB721TiersHookStore_UnrecognizedTier(uint256)", 10));
34
- store.recordRemoveTierIds(futureTierIds);
35
-
36
- // Attempt to remove tier 0 (invalid) — should also revert.
37
- uint256[] memory zeroTierIds = new uint256[](1);
38
- zeroTierIds[0] = 0;
39
- vm.expectRevert(abi.encodeWithSignature("JB721TiersHookStore_UnrecognizedTier(uint256)", 0));
40
- store.recordRemoveTierIds(zeroTierIds);
41
- }
42
-
43
- /// @notice Removing an existing tier still works as before.
44
- function test_L18_existing_tier_removal_works() external {
45
- // Add 5 tiers.
46
- JB721TierConfig[] memory tiers = new JB721TierConfig[](5);
47
- for (uint256 i; i < 5; i++) {
48
- tiers[i] = _tier(uint24(i + 1));
49
- }
50
- store.recordAddTiers(tiers);
51
-
52
- // Remove tier 3 — should succeed.
53
- uint256[] memory removeTierIds = new uint256[](1);
54
- removeTierIds[0] = 3;
55
- store.recordRemoveTierIds(removeTierIds);
56
- assertTrue(store.isTierRemoved({hook: address(this), tierId: 3}));
57
-
58
- // Other tiers should not be affected.
59
- assertFalse(store.isTierRemoved({hook: address(this), tierId: 1}));
60
- assertFalse(store.isTierRemoved({hook: address(this), tierId: 2}));
61
- assertFalse(store.isTierRemoved({hook: address(this), tierId: 4}));
62
- assertFalse(store.isTierRemoved({hook: address(this), tierId: 5}));
63
- }
64
-
65
- function _tier(uint24 category) internal pure returns (JB721TierConfig memory tier) {
66
- tier.price = 1;
67
- tier.initialSupply = 10;
68
- tier.category = category;
69
- tier.flags = JB721TierConfigFlags({
70
- allowOwnerMint: false,
71
- useReserveBeneficiaryAsDefault: false,
72
- transfersPausable: false,
73
- useVotingUnits: false,
74
- cantBeRemoved: false,
75
- cantIncreaseDiscountPercent: false,
76
- cantBuyWithCredits: false
77
- });
78
- tier.splits = new JBSplit[](0);
79
- }
80
- }
@@ -1,200 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- // forge-lint: disable-next-line(unaliased-plain-import)
5
- import "../utils/UnitTestSetup.sol";
6
- import {IJB721TiersHookStore} from "../../src/interfaces/IJB721TiersHookStore.sol";
7
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
8
- import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
9
- import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
10
- import {JBAfterPayRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
11
- import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
12
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
13
- import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
14
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
15
-
16
- contract PayCreditsBypassTierSplits is UnitTestSetup {
17
- address internal splitBeneficiary = makeAddr("splitBeneficiary");
18
-
19
- function setUp() public override {
20
- super.setUp();
21
- vm.etch(mockJBSplits, new bytes(0x69));
22
- }
23
-
24
- function _buildPayMetadata(
25
- address hookAddress,
26
- bool allowOverspending,
27
- uint16[] memory tierIdsToMint
28
- )
29
- internal
30
- view
31
- returns (bytes memory)
32
- {
33
- bytes[] memory data = new bytes[](1);
34
- data[0] = abi.encode(allowOverspending, tierIdsToMint);
35
- bytes4[] memory ids = new bytes4[](1);
36
- ids[0] = metadataHelper.getId("pay", hookAddress);
37
- return metadataHelper.createMetadata(ids, data);
38
- }
39
-
40
- function _beforePayContext(
41
- address hookAddress,
42
- uint256 amountValue,
43
- uint16[] memory tierIdsToMint
44
- )
45
- internal
46
- view
47
- returns (JBBeforePayRecordedContext memory)
48
- {
49
- return JBBeforePayRecordedContext({
50
- terminal: mockTerminalAddress,
51
- payer: beneficiary,
52
- amount: JBTokenAmount({
53
- token: JBConstants.NATIVE_TOKEN,
54
- value: amountValue,
55
- decimals: 18,
56
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
57
- }),
58
- projectId: projectId,
59
- rulesetId: 0,
60
- beneficiary: beneficiary,
61
- weight: 10e18,
62
- reservedPercent: 5000,
63
- metadata: _buildPayMetadata(hookAddress, false, tierIdsToMint)
64
- });
65
- }
66
-
67
- function _afterPayContext(
68
- address hookAddress,
69
- uint256 amountValue,
70
- uint256 forwardedAmountValue,
71
- bytes memory hookMetadata,
72
- uint16[] memory tierIdsToMint
73
- )
74
- internal
75
- view
76
- returns (JBAfterPayRecordedContext memory)
77
- {
78
- return JBAfterPayRecordedContext({
79
- payer: beneficiary,
80
- projectId: projectId,
81
- rulesetId: 0,
82
- amount: JBTokenAmount({
83
- token: JBConstants.NATIVE_TOKEN,
84
- value: amountValue,
85
- decimals: 18,
86
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
87
- }),
88
- forwardedAmount: JBTokenAmount({
89
- token: JBConstants.NATIVE_TOKEN,
90
- value: forwardedAmountValue,
91
- decimals: 18,
92
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
93
- }),
94
- weight: 10e18,
95
- newlyIssuedTokenCount: 0,
96
- beneficiary: beneficiary,
97
- hookMetadata: hookMetadata,
98
- payerMetadata: _buildPayMetadata(hookAddress, true, tierIdsToMint)
99
- });
100
- }
101
-
102
- function test_payCredits_can_bypass_most_of_tier_split_payment() public {
103
- ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
104
- IJB721TiersHookStore hookStore = testHook.STORE();
105
-
106
- vm.mockCall(
107
- mockJBDirectory,
108
- abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
109
- abi.encode(true)
110
- );
111
-
112
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
113
- tierConfigs[0] = JB721TierConfig({
114
- price: 1 ether,
115
- initialSupply: 100,
116
- votingUnits: 0,
117
- reserveFrequency: 0,
118
- reserveBeneficiary: reserveBeneficiary,
119
- encodedIPFSUri: bytes32(uint256(0x1234)),
120
- category: 1,
121
- discountPercent: 0,
122
- flags: JB721TierConfigFlags({
123
- allowOwnerMint: false,
124
- useReserveBeneficiaryAsDefault: false,
125
- transfersPausable: false,
126
- useVotingUnits: false,
127
- cantBeRemoved: false,
128
- cantIncreaseDiscountPercent: false,
129
- cantBuyWithCredits: false
130
- }),
131
- splitPercent: 1_000_000_000,
132
- splits: new JBSplit[](0)
133
- });
134
-
135
- vm.prank(address(testHook));
136
- uint256[] memory tierIds = hookStore.recordAddTiers(tierConfigs);
137
-
138
- uint16[] memory noTiers = new uint16[](0);
139
- JBAfterPayRecordedContext memory creditSeedContext = JBAfterPayRecordedContext({
140
- payer: beneficiary,
141
- projectId: projectId,
142
- rulesetId: 0,
143
- amount: JBTokenAmount({
144
- token: JBConstants.NATIVE_TOKEN,
145
- value: 1 ether,
146
- decimals: 18,
147
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
148
- }),
149
- forwardedAmount: JBTokenAmount({
150
- token: JBConstants.NATIVE_TOKEN,
151
- value: 0,
152
- decimals: 18,
153
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
154
- }),
155
- weight: 10e18,
156
- newlyIssuedTokenCount: 0,
157
- beneficiary: beneficiary,
158
- hookMetadata: "",
159
- payerMetadata: _buildPayMetadata(address(testHook), true, noTiers)
160
- });
161
-
162
- vm.prank(mockTerminalAddress);
163
- testHook.afterPayRecordedWith(creditSeedContext);
164
- assertEq(testHook.payCreditsOf(beneficiary), 1 ether, "setup: credits should be seeded");
165
-
166
- uint16[] memory mintIds = new uint16[](1);
167
- mintIds[0] = uint16(tierIds[0]);
168
-
169
- (, JBPayHookSpecification[] memory specs) =
170
- testHook.beforePayRecordedWith(_beforePayContext(address(testHook), 1, mintIds));
171
- assertEq(specs[0].amount, 1, "forwarded amount is capped to the fresh payment");
172
-
173
- JBSplit[] memory splits = new JBSplit[](1);
174
- splits[0] = JBSplit({
175
- percent: 1_000_000_000,
176
- projectId: 0,
177
- beneficiary: payable(splitBeneficiary),
178
- preferAddToBalance: false,
179
- lockedUntil: 0,
180
- hook: IJBSplitHook(address(0))
181
- });
182
-
183
- uint256 groupId = uint256(uint160(address(testHook))) | (uint256(tierIds[0]) << 160);
184
- vm.mockCall(
185
- mockJBSplits, abi.encodeWithSelector(IJBSplits.splitsOf.selector, projectId, 0, groupId), abi.encode(splits)
186
- );
187
-
188
- uint256 splitBalanceBefore = splitBeneficiary.balance;
189
-
190
- JBAfterPayRecordedContext memory creditMintContext =
191
- _afterPayContext(address(testHook), 1, 1, specs[0].metadata, mintIds);
192
- vm.deal(mockTerminalAddress, 1);
193
- vm.prank(mockTerminalAddress);
194
- testHook.afterPayRecordedWith{value: 1}(creditMintContext);
195
-
196
- assertEq(testHook.balanceOf(beneficiary), 1, "beneficiary should receive the NFT");
197
- assertEq(testHook.payCreditsOf(beneficiary), 1, "credits should fund almost the entire mint");
198
- assertEq(splitBeneficiary.balance - splitBalanceBefore, 1, "split recipient only gets the fresh payment");
199
- }
200
- }