@bananapus/721-hook-v6 0.0.41 → 0.0.43
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 +60 -18
- package/src/JB721CheckpointsDeployer.sol +10 -5
- package/src/JB721TiersHook.sol +4 -1
- package/src/JB721TiersHookProjectDeployer.sol +68 -30
- package/src/JB721TiersHookStore.sol +1 -4
- package/src/interfaces/IJB721Checkpoints.sol +21 -14
- package/src/interfaces/IJB721CheckpointsDeployer.sol +6 -2
- package/src/interfaces/IJB721TiersHookProjectDeployer.sol +2 -0
- 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/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/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
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import "../utils/UnitTestSetup.sol";
|
|
5
|
-
|
|
6
|
-
import {JB721TiersHookProjectDeployer} from "../../src/JB721TiersHookProjectDeployer.sol";
|
|
7
|
-
import {JBDeploy721TiersHookConfig} from "../../src/structs/JBDeploy721TiersHookConfig.sol";
|
|
8
|
-
import {JBLaunchRulesetsConfig} from "../../src/structs/JBLaunchRulesetsConfig.sol";
|
|
9
|
-
import {JBQueueRulesetsConfig} from "../../src/structs/JBQueueRulesetsConfig.sol";
|
|
10
|
-
import {JBPayDataHookRulesetConfig} from "../../src/structs/JBPayDataHookRulesetConfig.sol";
|
|
11
|
-
import {JBPayDataHookRulesetMetadata} from "../../src/structs/JBPayDataHookRulesetMetadata.sol";
|
|
12
|
-
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
13
|
-
import {JB721InitTiersConfig} from "../../src/structs/JB721InitTiersConfig.sol";
|
|
14
|
-
import {JB721TiersHookFlags} from "../../src/structs/JB721TiersHookFlags.sol";
|
|
15
|
-
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
16
|
-
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
17
|
-
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
18
|
-
import {IJBRulesetApprovalHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
|
|
19
|
-
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
20
|
-
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
21
|
-
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
22
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
23
|
-
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
24
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
25
|
-
|
|
26
|
-
contract MockProjects {
|
|
27
|
-
uint256 internal _count;
|
|
28
|
-
address internal _owner;
|
|
29
|
-
|
|
30
|
-
function setup(uint256 count_, address owner_) external {
|
|
31
|
-
_count = count_;
|
|
32
|
-
_owner = owner_;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function count() external view returns (uint256) {
|
|
36
|
-
return _count;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function ownerOf(uint256) external view returns (address) {
|
|
40
|
-
return _owner;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
fallback() external {}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
contract StrictController {
|
|
47
|
-
address internal immutable EXPECTED_CALLER;
|
|
48
|
-
|
|
49
|
-
error UnexpectedCaller(address caller);
|
|
50
|
-
|
|
51
|
-
constructor(address expectedCaller) {
|
|
52
|
-
EXPECTED_CALLER = expectedCaller;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
fallback() external payable {
|
|
56
|
-
if (msg.sender != EXPECTED_CALLER) revert UnexpectedCaller(msg.sender);
|
|
57
|
-
bytes memory result = abi.encode(uint256(42));
|
|
58
|
-
assembly {
|
|
59
|
-
return(add(result, 32), mload(result))
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
contract Test_ProjectDeployerAuth is UnitTestSetup {
|
|
65
|
-
JB721TiersHookProjectDeployer internal projectDeployer;
|
|
66
|
-
address internal operator = address(0xBEEF);
|
|
67
|
-
uint256 internal testProjectId = 5;
|
|
68
|
-
|
|
69
|
-
function setUp() public override {
|
|
70
|
-
super.setUp();
|
|
71
|
-
|
|
72
|
-
MockProjects projects = new MockProjects();
|
|
73
|
-
vm.etch(mockJBProjects, address(projects).code);
|
|
74
|
-
MockProjects(mockJBProjects).setup(testProjectId, owner);
|
|
75
|
-
|
|
76
|
-
vm.mockCall(mockJBDirectory, abi.encodeWithSelector(IJBDirectory.PROJECTS.selector), abi.encode(mockJBProjects));
|
|
77
|
-
|
|
78
|
-
projectDeployer = new JB721TiersHookProjectDeployer(
|
|
79
|
-
IJBDirectory(mockJBDirectory), IJBPermissions(mockJBPermissions), jbHookDeployer, address(0)
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function test_launchRulesetsFor_revertsBecauseControllerSeesProjectDeployerAsCaller() external {
|
|
84
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
|
|
85
|
-
_launchConfig(testProjectId);
|
|
86
|
-
|
|
87
|
-
StrictController controller = new StrictController(owner);
|
|
88
|
-
|
|
89
|
-
vm.prank(owner);
|
|
90
|
-
vm.expectRevert(abi.encodeWithSelector(StrictController.UnexpectedCaller.selector, address(projectDeployer)));
|
|
91
|
-
projectDeployer.launchRulesetsFor(
|
|
92
|
-
testProjectId, hookConfig, launchConfig, IJBController(address(controller)), bytes32(0)
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function test_queueRulesetsOf_revertsBecauseControllerSeesProjectDeployerAsCaller() external {
|
|
97
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
|
|
98
|
-
_queueConfig(testProjectId);
|
|
99
|
-
|
|
100
|
-
StrictController controller = new StrictController(owner);
|
|
101
|
-
|
|
102
|
-
vm.prank(owner);
|
|
103
|
-
vm.expectRevert(abi.encodeWithSelector(StrictController.UnexpectedCaller.selector, address(projectDeployer)));
|
|
104
|
-
projectDeployer.queueRulesetsOf(
|
|
105
|
-
testProjectId, hookConfig, queueConfig, IJBController(address(controller)), bytes32(0)
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function test_launchRulesetsFor_checksLaunchPermission() external {
|
|
110
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
|
|
111
|
-
_launchConfig(testProjectId);
|
|
112
|
-
|
|
113
|
-
address account = owner;
|
|
114
|
-
StrictController controller = new StrictController(owner);
|
|
115
|
-
|
|
116
|
-
// Grant LAUNCH_RULESETS and SET_TERMINALS, deny QUEUE_RULESETS.
|
|
117
|
-
vm.mockCall(
|
|
118
|
-
mockJBPermissions,
|
|
119
|
-
abi.encodeWithSelector(
|
|
120
|
-
IJBPermissions.hasPermission.selector,
|
|
121
|
-
operator,
|
|
122
|
-
account,
|
|
123
|
-
testProjectId,
|
|
124
|
-
JBPermissionIds.QUEUE_RULESETS,
|
|
125
|
-
true,
|
|
126
|
-
true
|
|
127
|
-
),
|
|
128
|
-
abi.encode(false)
|
|
129
|
-
);
|
|
130
|
-
vm.mockCall(
|
|
131
|
-
mockJBPermissions,
|
|
132
|
-
abi.encodeWithSelector(
|
|
133
|
-
IJBPermissions.hasPermission.selector,
|
|
134
|
-
operator,
|
|
135
|
-
account,
|
|
136
|
-
testProjectId,
|
|
137
|
-
JBPermissionIds.LAUNCH_RULESETS,
|
|
138
|
-
true,
|
|
139
|
-
true
|
|
140
|
-
),
|
|
141
|
-
abi.encode(true)
|
|
142
|
-
);
|
|
143
|
-
vm.mockCall(
|
|
144
|
-
mockJBPermissions,
|
|
145
|
-
abi.encodeWithSelector(
|
|
146
|
-
IJBPermissions.hasPermission.selector,
|
|
147
|
-
operator,
|
|
148
|
-
account,
|
|
149
|
-
testProjectId,
|
|
150
|
-
JBPermissionIds.SET_TERMINALS,
|
|
151
|
-
true,
|
|
152
|
-
true
|
|
153
|
-
),
|
|
154
|
-
abi.encode(true)
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// The permission check passes with LAUNCH_RULESETS. The call proceeds to the controller, which reverts
|
|
158
|
-
// because it sees the deployer contract as the caller rather than the original operator.
|
|
159
|
-
vm.prank(operator);
|
|
160
|
-
vm.expectRevert(abi.encodeWithSelector(StrictController.UnexpectedCaller.selector, address(projectDeployer)));
|
|
161
|
-
projectDeployer.launchRulesetsFor(
|
|
162
|
-
testProjectId, hookConfig, launchConfig, IJBController(address(controller)), bytes32(0)
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function _launchConfig(uint256 projectId_)
|
|
167
|
-
internal
|
|
168
|
-
view
|
|
169
|
-
returns (JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig)
|
|
170
|
-
{
|
|
171
|
-
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
|
|
172
|
-
tierConfigs[0] = JB721TierConfig({
|
|
173
|
-
price: uint104(10),
|
|
174
|
-
initialSupply: uint32(100),
|
|
175
|
-
votingUnits: uint16(0),
|
|
176
|
-
reserveFrequency: uint16(0),
|
|
177
|
-
reserveBeneficiary: reserveBeneficiary,
|
|
178
|
-
encodedIPFSUri: tokenUris[0],
|
|
179
|
-
category: uint24(1),
|
|
180
|
-
discountPercent: uint8(0),
|
|
181
|
-
flags: JB721TierConfigFlags({
|
|
182
|
-
allowOwnerMint: false,
|
|
183
|
-
useReserveBeneficiaryAsDefault: false,
|
|
184
|
-
transfersPausable: false,
|
|
185
|
-
useVotingUnits: false,
|
|
186
|
-
cantBeRemoved: false,
|
|
187
|
-
cantIncreaseDiscountPercent: false,
|
|
188
|
-
cantBuyWithCredits: false
|
|
189
|
-
}),
|
|
190
|
-
splitPercent: 0,
|
|
191
|
-
splits: new JBSplit[](0)
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
hookConfig = JBDeploy721TiersHookConfig({
|
|
195
|
-
name: name,
|
|
196
|
-
symbol: symbol,
|
|
197
|
-
baseUri: baseUri,
|
|
198
|
-
tokenUriResolver: IJB721TokenUriResolver(address(0)),
|
|
199
|
-
contractUri: contractUri,
|
|
200
|
-
tiersConfig: JB721InitTiersConfig({
|
|
201
|
-
tiers: tierConfigs, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
|
|
202
|
-
}),
|
|
203
|
-
flags: JB721TiersHookFlags({
|
|
204
|
-
preventOverspending: false,
|
|
205
|
-
issueTokensForSplits: false,
|
|
206
|
-
noNewTiersWithReserves: true,
|
|
207
|
-
noNewTiersWithVotes: false,
|
|
208
|
-
noNewTiersWithOwnerMinting: true
|
|
209
|
-
})
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
JBPayDataHookRulesetConfig[] memory rulesetConfigs = new JBPayDataHookRulesetConfig[](1);
|
|
213
|
-
rulesetConfigs[0].mustStartAtOrAfter = 0;
|
|
214
|
-
rulesetConfigs[0].duration = 14;
|
|
215
|
-
rulesetConfigs[0].weight = 1e18;
|
|
216
|
-
rulesetConfigs[0].weightCutPercent = 0;
|
|
217
|
-
rulesetConfigs[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
218
|
-
rulesetConfigs[0].metadata = JBPayDataHookRulesetMetadata({
|
|
219
|
-
reservedPercent: 5000,
|
|
220
|
-
cashOutTaxRate: 5000,
|
|
221
|
-
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
222
|
-
pausePay: false,
|
|
223
|
-
pauseCreditTransfers: false,
|
|
224
|
-
allowOwnerMinting: false,
|
|
225
|
-
allowSetCustomToken: false,
|
|
226
|
-
allowTerminalMigration: false,
|
|
227
|
-
allowSetTerminals: false,
|
|
228
|
-
allowSetController: false,
|
|
229
|
-
ownerMustSendPayouts: false,
|
|
230
|
-
allowAddAccountingContext: false,
|
|
231
|
-
allowAddPriceFeed: false,
|
|
232
|
-
holdFees: false,
|
|
233
|
-
useTotalSurplusForCashOuts: false,
|
|
234
|
-
useDataHookForCashOut: false,
|
|
235
|
-
metadata: 0x00
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
|
|
239
|
-
accountingContexts[0] = JBAccountingContext({
|
|
240
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18, token: JBConstants.NATIVE_TOKEN
|
|
241
|
-
});
|
|
242
|
-
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
243
|
-
terminalConfigs[0] = JBTerminalConfig({
|
|
244
|
-
terminal: IJBTerminal(mockTerminalAddress), accountingContextsToAccept: accountingContexts
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
launchConfig = JBLaunchRulesetsConfig({
|
|
248
|
-
projectId: uint56(projectId_),
|
|
249
|
-
rulesetConfigurations: rulesetConfigs,
|
|
250
|
-
terminalConfigurations: terminalConfigs,
|
|
251
|
-
memo: "launch"
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function _queueConfig(uint256 projectId_)
|
|
256
|
-
internal
|
|
257
|
-
view
|
|
258
|
-
returns (JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig)
|
|
259
|
-
{
|
|
260
|
-
JBLaunchRulesetsConfig memory launchConfig;
|
|
261
|
-
(hookConfig, launchConfig) = _launchConfig(projectId_);
|
|
262
|
-
queueConfig = JBQueueRulesetsConfig({
|
|
263
|
-
projectId: uint56(projectId_), rulesetConfigurations: launchConfig.rulesetConfigurations, memo: "queue"
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import "../utils/UnitTestSetup.sol";
|
|
5
|
-
|
|
6
|
-
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
7
|
-
import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
|
|
8
|
-
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
9
|
-
|
|
10
|
-
contract RepoFindings is UnitTestSetup {
|
|
11
|
-
address payable internal splitBeneficiary = payable(makeAddr("splitBeneficiary"));
|
|
12
|
-
|
|
13
|
-
function _payMetadata(
|
|
14
|
-
address hookAddress,
|
|
15
|
-
bool allowOverspending,
|
|
16
|
-
uint16[] memory tierIds
|
|
17
|
-
)
|
|
18
|
-
internal
|
|
19
|
-
view
|
|
20
|
-
returns (bytes memory)
|
|
21
|
-
{
|
|
22
|
-
bytes[] memory data = new bytes[](1);
|
|
23
|
-
data[0] = abi.encode(allowOverspending, tierIds);
|
|
24
|
-
bytes4[] memory ids = new bytes4[](1);
|
|
25
|
-
ids[0] = metadataHelper.getId("pay", hookAddress);
|
|
26
|
-
return metadataHelper.createMetadata(ids, data);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function _nativeTokenAmount(uint256 value) internal pure returns (JBTokenAmount memory) {
|
|
30
|
-
return JBTokenAmount({
|
|
31
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
32
|
-
value: value,
|
|
33
|
-
decimals: 18,
|
|
34
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function test_payCredits_can_underfund_split_bearing_tier_mints() public {
|
|
39
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
40
|
-
|
|
41
|
-
vm.mockCall(mockJBSplits, abi.encodeWithSelector(IJBSplits.setSplitGroupsOf.selector), abi.encode());
|
|
42
|
-
|
|
43
|
-
JBSplit[] memory tierSplits = new JBSplit[](1);
|
|
44
|
-
tierSplits[0] = JBSplit({
|
|
45
|
-
preferAddToBalance: false,
|
|
46
|
-
percent: JBConstants.SPLITS_TOTAL_PERCENT,
|
|
47
|
-
projectId: 0,
|
|
48
|
-
beneficiary: splitBeneficiary,
|
|
49
|
-
lockedUntil: 0,
|
|
50
|
-
hook: IJBSplitHook(address(0))
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
|
|
54
|
-
tierConfigs[0] = JB721TierConfig({
|
|
55
|
-
price: 1 ether,
|
|
56
|
-
initialSupply: 10,
|
|
57
|
-
votingUnits: 0,
|
|
58
|
-
reserveFrequency: 0,
|
|
59
|
-
reserveBeneficiary: address(0),
|
|
60
|
-
encodedIPFSUri: bytes32(uint256(0x1234)),
|
|
61
|
-
category: 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: JBConstants.SPLITS_TOTAL_PERCENT,
|
|
73
|
-
splits: tierSplits
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
vm.prank(owner);
|
|
77
|
-
testHook.adjustTiers(tierConfigs, new uint256[](0));
|
|
78
|
-
|
|
79
|
-
uint256 groupId = uint256(uint160(address(testHook))) | (uint256(1) << 160);
|
|
80
|
-
vm.mockCall(
|
|
81
|
-
mockJBSplits,
|
|
82
|
-
abi.encodeWithSelector(IJBSplits.splitsOf.selector, projectId, uint256(0), groupId),
|
|
83
|
-
abi.encode(tierSplits)
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
mockAndExpect(
|
|
87
|
-
mockJBDirectory,
|
|
88
|
-
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
89
|
-
abi.encode(true)
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
vm.prank(mockTerminalAddress);
|
|
93
|
-
testHook.afterPayRecordedWith(
|
|
94
|
-
JBAfterPayRecordedContext({
|
|
95
|
-
payer: beneficiary,
|
|
96
|
-
projectId: projectId,
|
|
97
|
-
rulesetId: 0,
|
|
98
|
-
amount: _nativeTokenAmount(1 ether),
|
|
99
|
-
forwardedAmount: _nativeTokenAmount(0),
|
|
100
|
-
weight: 10e18,
|
|
101
|
-
newlyIssuedTokenCount: 0,
|
|
102
|
-
beneficiary: beneficiary,
|
|
103
|
-
hookMetadata: bytes(""),
|
|
104
|
-
payerMetadata: bytes("")
|
|
105
|
-
})
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
assertEq(testHook.payCreditsOf(beneficiary), 1 ether, "setup: credits should be seeded");
|
|
109
|
-
|
|
110
|
-
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
111
|
-
tierIdsToMint[0] = 1;
|
|
112
|
-
bytes memory payerMetadata = _payMetadata(address(testHook), true, tierIdsToMint);
|
|
113
|
-
|
|
114
|
-
JBBeforePayRecordedContext memory beforeContext = JBBeforePayRecordedContext({
|
|
115
|
-
terminal: mockTerminalAddress,
|
|
116
|
-
payer: beneficiary,
|
|
117
|
-
amount: _nativeTokenAmount(1),
|
|
118
|
-
projectId: projectId,
|
|
119
|
-
rulesetId: 0,
|
|
120
|
-
beneficiary: beneficiary,
|
|
121
|
-
weight: 10e18,
|
|
122
|
-
reservedPercent: 5000,
|
|
123
|
-
metadata: payerMetadata
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
(uint256 weight, JBPayHookSpecification[] memory specs) = testHook.beforePayRecordedWith(beforeContext);
|
|
127
|
-
|
|
128
|
-
assertEq(weight, 0, "only the fresh 1 wei payment is considered for split weight adjustment");
|
|
129
|
-
assertEq(specs.length, 1);
|
|
130
|
-
assertEq(specs[0].amount, 1, "split forwarding is capped to the fresh payment, not the credit-backed mint");
|
|
131
|
-
|
|
132
|
-
uint256 splitBeneficiaryBalanceBefore = splitBeneficiary.balance;
|
|
133
|
-
vm.deal(mockTerminalAddress, 1);
|
|
134
|
-
|
|
135
|
-
vm.prank(mockTerminalAddress);
|
|
136
|
-
testHook.afterPayRecordedWith{value: 1}(
|
|
137
|
-
JBAfterPayRecordedContext({
|
|
138
|
-
payer: beneficiary,
|
|
139
|
-
projectId: projectId,
|
|
140
|
-
rulesetId: 0,
|
|
141
|
-
amount: _nativeTokenAmount(1),
|
|
142
|
-
forwardedAmount: _nativeTokenAmount(1),
|
|
143
|
-
weight: weight,
|
|
144
|
-
newlyIssuedTokenCount: 0,
|
|
145
|
-
beneficiary: beneficiary,
|
|
146
|
-
hookMetadata: specs[0].metadata,
|
|
147
|
-
payerMetadata: payerMetadata
|
|
148
|
-
})
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
assertEq(testHook.balanceOf(beneficiary), 1, "beneficiary still receives the split-bearing NFT");
|
|
152
|
-
assertEq(testHook.payCreditsOf(beneficiary), 1, "stored credits fund essentially the entire mint");
|
|
153
|
-
assertEq(
|
|
154
|
-
splitBeneficiary.balance - splitBeneficiaryBalanceBefore,
|
|
155
|
-
1,
|
|
156
|
-
"split beneficiary only receives the fresh 1 wei payment instead of the tier's 1 ether split amount"
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/// @notice Creating a tier with reserveFrequency > 0 and no beneficiary (tier-specific or default)
|
|
161
|
-
/// is now rejected at creation time, preventing the retroactive dilution bug.
|
|
162
|
-
function test_new_default_reserve_beneficiary_retroactively_dilutes_existing_tiers() public {
|
|
163
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
164
|
-
|
|
165
|
-
JB721TierConfig[] memory initialTier = new JB721TierConfig[](1);
|
|
166
|
-
initialTier[0] = JB721TierConfig({
|
|
167
|
-
price: 1 ether,
|
|
168
|
-
initialSupply: 10,
|
|
169
|
-
votingUnits: 0,
|
|
170
|
-
reserveFrequency: 2,
|
|
171
|
-
reserveBeneficiary: address(0),
|
|
172
|
-
encodedIPFSUri: bytes32(uint256(0x1111)),
|
|
173
|
-
category: 1,
|
|
174
|
-
discountPercent: 0,
|
|
175
|
-
flags: JB721TierConfigFlags({
|
|
176
|
-
allowOwnerMint: false,
|
|
177
|
-
useReserveBeneficiaryAsDefault: false,
|
|
178
|
-
transfersPausable: false,
|
|
179
|
-
useVotingUnits: false,
|
|
180
|
-
cantBeRemoved: false,
|
|
181
|
-
cantIncreaseDiscountPercent: false,
|
|
182
|
-
cantBuyWithCredits: false
|
|
183
|
-
}),
|
|
184
|
-
splitPercent: 0,
|
|
185
|
-
splits: new JBSplit[](0)
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// The new creation-time check prevents tiers with reserves but no beneficiary.
|
|
189
|
-
vm.expectRevert(
|
|
190
|
-
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_MissingReserveBeneficiary.selector, 1)
|
|
191
|
-
);
|
|
192
|
-
vm.prank(owner);
|
|
193
|
-
testHook.adjustTiers(initialTier, new uint256[](0));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
@@ -1,87 +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 {JB721Tier} from "../../src/structs/JB721Tier.sol";
|
|
9
|
-
import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
|
|
10
|
-
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
11
|
-
|
|
12
|
-
contract Test_ReserveActivation is Test {
|
|
13
|
-
JB721TiersHookStore internal store;
|
|
14
|
-
|
|
15
|
-
function setUp() external {
|
|
16
|
-
store = new JB721TiersHookStore();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/// @notice Creating a tier with reserveFrequency > 0 and no beneficiary (tier-specific or default)
|
|
20
|
-
/// is now rejected at creation time. This prevents the phantom-reserves scenario entirely.
|
|
21
|
-
function test_soldOutTier_noPhantomReserves_afterDefaultBeneficiaryChange() external {
|
|
22
|
-
// Attempt to add a tier with reserve frequency but no beneficiary — should revert.
|
|
23
|
-
JB721TierConfig[] memory initialTiers = new JB721TierConfig[](1);
|
|
24
|
-
initialTiers[0] = _tier({
|
|
25
|
-
price: 1,
|
|
26
|
-
initialSupply: 10,
|
|
27
|
-
reserveFrequency: 2,
|
|
28
|
-
reserveBeneficiary: address(0),
|
|
29
|
-
useReserveBeneficiaryAsDefault: false,
|
|
30
|
-
category: 1
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
vm.expectRevert(
|
|
34
|
-
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_MissingReserveBeneficiary.selector, 1)
|
|
35
|
-
);
|
|
36
|
-
store.recordAddTiers(initialTiers);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/// @notice Creating a tier with reserveFrequency > 0 and no beneficiary (tier-specific or default)
|
|
40
|
-
/// is now rejected at creation time. This prevents the retroactive reserve activation scenario entirely.
|
|
41
|
-
function test_nonSoldOutTier_reservesStillWork_afterDefaultBeneficiaryChange() external {
|
|
42
|
-
// Attempt to add a tier with reserve frequency but no beneficiary — should revert.
|
|
43
|
-
JB721TierConfig[] memory initialTiers = new JB721TierConfig[](1);
|
|
44
|
-
initialTiers[0] = _tier({
|
|
45
|
-
price: 1,
|
|
46
|
-
initialSupply: 100,
|
|
47
|
-
reserveFrequency: 5,
|
|
48
|
-
reserveBeneficiary: address(0),
|
|
49
|
-
useReserveBeneficiaryAsDefault: false,
|
|
50
|
-
category: 1
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
vm.expectRevert(
|
|
54
|
-
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_MissingReserveBeneficiary.selector, 1)
|
|
55
|
-
);
|
|
56
|
-
store.recordAddTiers(initialTiers);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function _tier(
|
|
60
|
-
uint104 price,
|
|
61
|
-
uint32 initialSupply,
|
|
62
|
-
uint16 reserveFrequency,
|
|
63
|
-
address reserveBeneficiary,
|
|
64
|
-
bool useReserveBeneficiaryAsDefault,
|
|
65
|
-
uint24 category
|
|
66
|
-
)
|
|
67
|
-
internal
|
|
68
|
-
pure
|
|
69
|
-
returns (JB721TierConfig memory tier)
|
|
70
|
-
{
|
|
71
|
-
tier.price = price;
|
|
72
|
-
tier.initialSupply = initialSupply;
|
|
73
|
-
tier.reserveFrequency = reserveFrequency;
|
|
74
|
-
tier.reserveBeneficiary = reserveBeneficiary;
|
|
75
|
-
tier.category = category;
|
|
76
|
-
tier.flags = JB721TierConfigFlags({
|
|
77
|
-
allowOwnerMint: false,
|
|
78
|
-
useReserveBeneficiaryAsDefault: useReserveBeneficiaryAsDefault,
|
|
79
|
-
transfersPausable: false,
|
|
80
|
-
useVotingUnits: false,
|
|
81
|
-
cantBeRemoved: false,
|
|
82
|
-
cantIncreaseDiscountPercent: false,
|
|
83
|
-
cantBuyWithCredits: false
|
|
84
|
-
});
|
|
85
|
-
tier.splits = new JBSplit[](0);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,149 +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 {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
7
|
-
import {JBAfterPayRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
|
|
8
|
-
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
9
|
-
import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
|
|
10
|
-
|
|
11
|
-
contract RetroactiveReserveBeneficiaryDilution is UnitTestSetup {
|
|
12
|
-
/// @notice Creating a tier with reserveFrequency > 0 but no beneficiary (tier-specific or default) now reverts,
|
|
13
|
-
/// preventing the phantom reserve scenario where a later default beneficiary retroactively inflates
|
|
14
|
-
/// totalCashOutWeight and dilutes existing holders.
|
|
15
|
-
function test_adjustTier_reverts_when_reserve_has_no_beneficiary() public {
|
|
16
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
17
|
-
|
|
18
|
-
JB721TierConfig[] memory tier1 = new JB721TierConfig[](1);
|
|
19
|
-
tier1[0] = JB721TierConfig({
|
|
20
|
-
price: 1 ether,
|
|
21
|
-
initialSupply: 100,
|
|
22
|
-
votingUnits: 0,
|
|
23
|
-
reserveFrequency: 2,
|
|
24
|
-
reserveBeneficiary: address(0),
|
|
25
|
-
encodedIPFSUri: bytes32(uint256(0x1234)),
|
|
26
|
-
category: 1,
|
|
27
|
-
discountPercent: 0,
|
|
28
|
-
flags: JB721TierConfigFlags({
|
|
29
|
-
allowOwnerMint: false,
|
|
30
|
-
useReserveBeneficiaryAsDefault: false,
|
|
31
|
-
transfersPausable: false,
|
|
32
|
-
useVotingUnits: false,
|
|
33
|
-
cantBeRemoved: false,
|
|
34
|
-
cantIncreaseDiscountPercent: false,
|
|
35
|
-
cantBuyWithCredits: false
|
|
36
|
-
}),
|
|
37
|
-
splitPercent: 0,
|
|
38
|
-
splits: new JBSplit[](0)
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
vm.prank(owner);
|
|
42
|
-
vm.expectRevert(
|
|
43
|
-
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_MissingReserveBeneficiary.selector, 1)
|
|
44
|
-
);
|
|
45
|
-
testHook.adjustTiers(tier1, new uint256[](0));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// @notice A tier with reserveFrequency > 0 succeeds when an explicit beneficiary is provided.
|
|
49
|
-
function test_adjustTier_succeeds_with_explicit_reserve_beneficiary() public {
|
|
50
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
51
|
-
|
|
52
|
-
JB721TierConfig[] memory tier1 = new JB721TierConfig[](1);
|
|
53
|
-
tier1[0] = JB721TierConfig({
|
|
54
|
-
price: 1 ether,
|
|
55
|
-
initialSupply: 100,
|
|
56
|
-
votingUnits: 0,
|
|
57
|
-
reserveFrequency: 2,
|
|
58
|
-
reserveBeneficiary: owner,
|
|
59
|
-
encodedIPFSUri: bytes32(uint256(0x1234)),
|
|
60
|
-
category: 1,
|
|
61
|
-
discountPercent: 0,
|
|
62
|
-
flags: JB721TierConfigFlags({
|
|
63
|
-
allowOwnerMint: false,
|
|
64
|
-
useReserveBeneficiaryAsDefault: false,
|
|
65
|
-
transfersPausable: false,
|
|
66
|
-
useVotingUnits: false,
|
|
67
|
-
cantBeRemoved: false,
|
|
68
|
-
cantIncreaseDiscountPercent: false,
|
|
69
|
-
cantBuyWithCredits: false
|
|
70
|
-
}),
|
|
71
|
-
splitPercent: 0,
|
|
72
|
-
splits: new JBSplit[](0)
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
vm.prank(owner);
|
|
76
|
-
testHook.adjustTiers(tier1, new uint256[](0));
|
|
77
|
-
|
|
78
|
-
assertEq(
|
|
79
|
-
testHook.STORE().reserveBeneficiaryOf(address(testHook), 1),
|
|
80
|
-
owner,
|
|
81
|
-
"tier-specific beneficiary should be set"
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/// @notice A tier with reserveFrequency > 0 and no explicit beneficiary succeeds when a default beneficiary
|
|
86
|
-
/// was previously set by an earlier tier.
|
|
87
|
-
function test_adjustTier_succeeds_with_default_reserve_beneficiary() public {
|
|
88
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
89
|
-
|
|
90
|
-
// Add two tiers: first sets the default beneficiary, second relies on it.
|
|
91
|
-
JB721TierConfig[] memory tiers = new JB721TierConfig[](2);
|
|
92
|
-
|
|
93
|
-
// Tier 1: sets default beneficiary via useReserveBeneficiaryAsDefault.
|
|
94
|
-
tiers[0] = JB721TierConfig({
|
|
95
|
-
price: 1 ether,
|
|
96
|
-
initialSupply: 100,
|
|
97
|
-
votingUnits: 0,
|
|
98
|
-
reserveFrequency: 2,
|
|
99
|
-
reserveBeneficiary: owner,
|
|
100
|
-
encodedIPFSUri: bytes32(uint256(0x1234)),
|
|
101
|
-
category: 1,
|
|
102
|
-
discountPercent: 0,
|
|
103
|
-
flags: JB721TierConfigFlags({
|
|
104
|
-
allowOwnerMint: false,
|
|
105
|
-
useReserveBeneficiaryAsDefault: true,
|
|
106
|
-
transfersPausable: false,
|
|
107
|
-
useVotingUnits: false,
|
|
108
|
-
cantBeRemoved: false,
|
|
109
|
-
cantIncreaseDiscountPercent: false,
|
|
110
|
-
cantBuyWithCredits: false
|
|
111
|
-
}),
|
|
112
|
-
splitPercent: 0,
|
|
113
|
-
splits: new JBSplit[](0)
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Tier 2: relies on the default beneficiary (no explicit one).
|
|
117
|
-
tiers[1] = JB721TierConfig({
|
|
118
|
-
price: 2 ether,
|
|
119
|
-
initialSupply: 100,
|
|
120
|
-
votingUnits: 0,
|
|
121
|
-
reserveFrequency: 3,
|
|
122
|
-
reserveBeneficiary: address(0),
|
|
123
|
-
encodedIPFSUri: bytes32(uint256(0x5678)),
|
|
124
|
-
category: 2,
|
|
125
|
-
discountPercent: 0,
|
|
126
|
-
flags: JB721TierConfigFlags({
|
|
127
|
-
allowOwnerMint: false,
|
|
128
|
-
useReserveBeneficiaryAsDefault: false,
|
|
129
|
-
transfersPausable: false,
|
|
130
|
-
useVotingUnits: false,
|
|
131
|
-
cantBeRemoved: false,
|
|
132
|
-
cantIncreaseDiscountPercent: false,
|
|
133
|
-
cantBuyWithCredits: false
|
|
134
|
-
}),
|
|
135
|
-
splitPercent: 0,
|
|
136
|
-
splits: new JBSplit[](0)
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
vm.prank(owner);
|
|
140
|
-
testHook.adjustTiers(tiers, new uint256[](0));
|
|
141
|
-
|
|
142
|
-
// Tier 2 should inherit the default beneficiary set by tier 1.
|
|
143
|
-
assertEq(
|
|
144
|
-
testHook.STORE().reserveBeneficiaryOf(address(testHook), 2),
|
|
145
|
-
owner,
|
|
146
|
-
"tier 2 should inherit default beneficiary"
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
}
|