@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,358 +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
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
7
|
-
import "../../src/JB721TiersHookProjectDeployer.sol";
|
|
8
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
9
|
-
import "../../src/structs/JBLaunchRulesetsConfig.sol";
|
|
10
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
11
|
-
import "../../src/structs/JBQueueRulesetsConfig.sol";
|
|
12
|
-
|
|
13
|
-
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
14
|
-
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.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 {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
18
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
19
|
-
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
20
|
-
|
|
21
|
-
/// @notice A mock projects contract that returns configurable owner and count.
|
|
22
|
-
contract MockProjects {
|
|
23
|
-
uint256 private _count;
|
|
24
|
-
address private _owner;
|
|
25
|
-
|
|
26
|
-
function setup(uint256 initialCount, address projectOwner) external {
|
|
27
|
-
_count = initialCount;
|
|
28
|
-
_owner = projectOwner;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function count() external view returns (uint256) {
|
|
32
|
-
return _count;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function ownerOf(uint256) external view returns (address) {
|
|
36
|
-
return _owner;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// @notice A mock controller that records calls and returns a ruleset ID.
|
|
41
|
-
contract MockController {
|
|
42
|
-
uint256 public lastProjectId;
|
|
43
|
-
uint256 public lastRulesetConfigCount;
|
|
44
|
-
bool public launchRulesetsForCalled;
|
|
45
|
-
bool public queueRulesetsOfCalled;
|
|
46
|
-
|
|
47
|
-
receive() external payable {}
|
|
48
|
-
|
|
49
|
-
// The fallback accepts any call and returns uint256(42) as the ruleset ID.
|
|
50
|
-
fallback() external payable {
|
|
51
|
-
// Decode the selector to track which function was called.
|
|
52
|
-
bytes4 selector = msg.sig;
|
|
53
|
-
|
|
54
|
-
// launchRulesetsFor(uint256,JBRulesetConfig[],JBTerminalConfig[],string)
|
|
55
|
-
if (selector == IJBController.launchRulesetsFor.selector) {
|
|
56
|
-
launchRulesetsForCalled = true;
|
|
57
|
-
}
|
|
58
|
-
// queueRulesetsOf(uint256,JBRulesetConfig[],string)
|
|
59
|
-
if (selector == IJBController.queueRulesetsOf.selector) {
|
|
60
|
-
queueRulesetsOfCalled = true;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Return uint256(42) as the ruleset ID.
|
|
64
|
-
bytes memory result = abi.encode(uint256(42));
|
|
65
|
-
assembly {
|
|
66
|
-
return(add(result, 32), mload(result))
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/// @notice Regression tests for JB721TiersHookProjectDeployer's launchRulesetsFor and queueRulesetsOf.
|
|
72
|
-
/// @dev These verify that the deployer correctly deploys a hook, transfers ownership, wires up the data hook,
|
|
73
|
-
/// and delegates to the controller for both launch and queue paths.
|
|
74
|
-
contract Test_ProjectDeployerRulesets is UnitTestSetup {
|
|
75
|
-
using stdStorage for StdStorage;
|
|
76
|
-
|
|
77
|
-
JB721TiersHookProjectDeployer deployer;
|
|
78
|
-
MockProjects mockProj;
|
|
79
|
-
MockController mockCtrl;
|
|
80
|
-
|
|
81
|
-
uint256 testProjectId = 5;
|
|
82
|
-
|
|
83
|
-
function setUp() public override {
|
|
84
|
-
super.setUp();
|
|
85
|
-
|
|
86
|
-
// Deploy mock projects contract and etch onto the existing mockJBProjects address.
|
|
87
|
-
mockProj = new MockProjects();
|
|
88
|
-
vm.etch(mockJBProjects, address(mockProj).code);
|
|
89
|
-
MockProjects(mockJBProjects).setup(testProjectId, owner);
|
|
90
|
-
|
|
91
|
-
// Mock DIRECTORY.PROJECTS() to return mockJBProjects.
|
|
92
|
-
vm.mockCall(mockJBDirectory, abi.encodeWithSelector(IJBDirectory.PROJECTS.selector), abi.encode(mockJBProjects));
|
|
93
|
-
|
|
94
|
-
// Mock all permission checks to return true.
|
|
95
|
-
vm.mockCall(mockJBPermissions, abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(true));
|
|
96
|
-
|
|
97
|
-
// Deploy the mock controller.
|
|
98
|
-
mockCtrl = new MockController();
|
|
99
|
-
|
|
100
|
-
// Deploy the project deployer.
|
|
101
|
-
deployer = new JB721TiersHookProjectDeployer(
|
|
102
|
-
IJBDirectory(mockJBDirectory), IJBPermissions(mockJBPermissions), jbHookDeployer, address(0)
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/// @notice Build a minimal deploy config and launch rulesets config for testing.
|
|
107
|
-
function _buildLaunchRulesetsConfigs()
|
|
108
|
-
internal
|
|
109
|
-
view
|
|
110
|
-
returns (JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig)
|
|
111
|
-
{
|
|
112
|
-
// Build a minimal tier config (1 tier).
|
|
113
|
-
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
|
|
114
|
-
tierConfigs[0] = JB721TierConfig({
|
|
115
|
-
price: uint104(10),
|
|
116
|
-
initialSupply: uint32(100),
|
|
117
|
-
votingUnits: uint16(0),
|
|
118
|
-
reserveFrequency: uint16(0),
|
|
119
|
-
reserveBeneficiary: reserveBeneficiary,
|
|
120
|
-
encodedIPFSUri: tokenUris[0],
|
|
121
|
-
category: uint24(100),
|
|
122
|
-
discountPercent: uint8(0),
|
|
123
|
-
flags: JB721TierConfigFlags({
|
|
124
|
-
allowOwnerMint: false,
|
|
125
|
-
useReserveBeneficiaryAsDefault: false,
|
|
126
|
-
transfersPausable: false,
|
|
127
|
-
useVotingUnits: true,
|
|
128
|
-
cantBeRemoved: false,
|
|
129
|
-
cantIncreaseDiscountPercent: false,
|
|
130
|
-
cantBuyWithCredits: false
|
|
131
|
-
}),
|
|
132
|
-
splitPercent: 0,
|
|
133
|
-
splits: new JBSplit[](0)
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
hookConfig = JBDeploy721TiersHookConfig({
|
|
137
|
-
name: name,
|
|
138
|
-
symbol: symbol,
|
|
139
|
-
baseUri: baseUri,
|
|
140
|
-
tokenUriResolver: IJB721TokenUriResolver(mockTokenUriResolver),
|
|
141
|
-
contractUri: contractUri,
|
|
142
|
-
tiersConfig: JB721InitTiersConfig({
|
|
143
|
-
tiers: tierConfigs, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
|
|
144
|
-
}),
|
|
145
|
-
flags: JB721TiersHookFlags({
|
|
146
|
-
preventOverspending: false,
|
|
147
|
-
issueTokensForSplits: false,
|
|
148
|
-
noNewTiersWithReserves: true,
|
|
149
|
-
noNewTiersWithVotes: true,
|
|
150
|
-
noNewTiersWithOwnerMinting: true
|
|
151
|
-
})
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Build a minimal ruleset config.
|
|
155
|
-
JBPayDataHookRulesetConfig[] memory rulesetConfigs = new JBPayDataHookRulesetConfig[](1);
|
|
156
|
-
rulesetConfigs[0].mustStartAtOrAfter = 0;
|
|
157
|
-
rulesetConfigs[0].duration = 14;
|
|
158
|
-
rulesetConfigs[0].weight = 10 ** 18;
|
|
159
|
-
rulesetConfigs[0].weightCutPercent = 0;
|
|
160
|
-
rulesetConfigs[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
161
|
-
rulesetConfigs[0].metadata = JBPayDataHookRulesetMetadata({
|
|
162
|
-
reservedPercent: 5000,
|
|
163
|
-
cashOutTaxRate: 5000,
|
|
164
|
-
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
165
|
-
pausePay: false,
|
|
166
|
-
pauseCreditTransfers: false,
|
|
167
|
-
allowOwnerMinting: false,
|
|
168
|
-
allowSetCustomToken: false,
|
|
169
|
-
allowTerminalMigration: false,
|
|
170
|
-
allowSetTerminals: false,
|
|
171
|
-
allowSetController: false,
|
|
172
|
-
ownerMustSendPayouts: false,
|
|
173
|
-
allowAddAccountingContext: false,
|
|
174
|
-
allowAddPriceFeed: false,
|
|
175
|
-
holdFees: false,
|
|
176
|
-
useTotalSurplusForCashOuts: false,
|
|
177
|
-
useDataHookForCashOut: false,
|
|
178
|
-
metadata: 0x00
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
182
|
-
JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
|
|
183
|
-
accountingContexts[0] = JBAccountingContext({
|
|
184
|
-
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18, token: JBConstants.NATIVE_TOKEN
|
|
185
|
-
});
|
|
186
|
-
terminalConfigs[0] = JBTerminalConfig({
|
|
187
|
-
terminal: IJBTerminal(mockTerminalAddress), accountingContextsToAccept: accountingContexts
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
launchConfig = JBLaunchRulesetsConfig({
|
|
191
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
192
|
-
projectId: uint56(testProjectId),
|
|
193
|
-
rulesetConfigurations: rulesetConfigs,
|
|
194
|
-
terminalConfigurations: terminalConfigs,
|
|
195
|
-
memo: "launch rulesets"
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/// @notice Build a minimal deploy config and queue rulesets config for testing.
|
|
200
|
-
function _buildQueueRulesetsConfigs()
|
|
201
|
-
internal
|
|
202
|
-
view
|
|
203
|
-
returns (JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig)
|
|
204
|
-
{
|
|
205
|
-
// Reuse the launch config builder for the hook config.
|
|
206
|
-
JBLaunchRulesetsConfig memory launchConfig;
|
|
207
|
-
(hookConfig, launchConfig) = _buildLaunchRulesetsConfigs();
|
|
208
|
-
|
|
209
|
-
queueConfig = JBQueueRulesetsConfig({
|
|
210
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
211
|
-
projectId: uint56(testProjectId),
|
|
212
|
-
rulesetConfigurations: launchConfig.rulesetConfigurations,
|
|
213
|
-
memo: "queue rulesets"
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// -----------------------------------------------------------------------
|
|
218
|
-
// launchRulesetsFor
|
|
219
|
-
// -----------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
/// @notice launchRulesetsFor deploys a hook, transfers ownership to the project, and calls the controller.
|
|
222
|
-
function test_launchRulesetsFor_deploysHookAndCallsController() external {
|
|
223
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
|
|
224
|
-
_buildLaunchRulesetsConfigs();
|
|
225
|
-
|
|
226
|
-
// Call as the project owner.
|
|
227
|
-
vm.prank(owner);
|
|
228
|
-
(uint256 rulesetId, IJB721TiersHook deployedHook) = deployer.launchRulesetsFor(
|
|
229
|
-
testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), bytes32(0)
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
// Verify the ruleset ID returned by the mock controller.
|
|
233
|
-
assertEq(rulesetId, 42, "Should return the ruleset ID from the controller");
|
|
234
|
-
|
|
235
|
-
// Verify a hook was deployed (non-zero address).
|
|
236
|
-
assertTrue(address(deployedHook) != address(0), "Hook should be deployed");
|
|
237
|
-
|
|
238
|
-
// Verify the controller's launchRulesetsFor was called.
|
|
239
|
-
assertTrue(mockCtrl.launchRulesetsForCalled(), "Controller launchRulesetsFor should be called");
|
|
240
|
-
|
|
241
|
-
// Verify the hook's PROJECT_ID is correct.
|
|
242
|
-
assertEq(deployedHook.PROJECT_ID(), testProjectId, "Hook PROJECT_ID should match");
|
|
243
|
-
|
|
244
|
-
// Verify the hook's ownership was transferred to the project.
|
|
245
|
-
(, uint88 ownerProjectId,) = JBOwnable(address(deployedHook)).jbOwner();
|
|
246
|
-
assertEq(ownerProjectId, testProjectId, "Hook should be owned by project");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/// @notice launchRulesetsFor with a deterministic salt produces a hook at a predictable address.
|
|
250
|
-
function test_launchRulesetsFor_deterministicSalt() external {
|
|
251
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
|
|
252
|
-
_buildLaunchRulesetsConfigs();
|
|
253
|
-
|
|
254
|
-
bytes32 salt = bytes32(uint256(0xdead));
|
|
255
|
-
|
|
256
|
-
vm.prank(owner);
|
|
257
|
-
(, IJB721TiersHook hook1) =
|
|
258
|
-
deployer.launchRulesetsFor(testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), salt);
|
|
259
|
-
|
|
260
|
-
// Deploy a second hook with a different salt to verify addresses differ.
|
|
261
|
-
bytes32 salt2 = bytes32(uint256(0xbeef));
|
|
262
|
-
|
|
263
|
-
vm.prank(owner);
|
|
264
|
-
(, IJB721TiersHook hook2) =
|
|
265
|
-
deployer.launchRulesetsFor(testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), salt2);
|
|
266
|
-
|
|
267
|
-
assertTrue(address(hook1) != address(hook2), "Different salts should produce different hook addresses");
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/// @notice launchRulesetsFor reverts when the caller lacks QUEUE_RULESETS permission.
|
|
271
|
-
function test_launchRulesetsFor_revertsWithoutPermission() external {
|
|
272
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
|
|
273
|
-
_buildLaunchRulesetsConfigs();
|
|
274
|
-
|
|
275
|
-
// Override: mock permissions to deny.
|
|
276
|
-
vm.mockCall(mockJBPermissions, abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(false));
|
|
277
|
-
|
|
278
|
-
address unauthorized = makeAddr("unauthorized");
|
|
279
|
-
vm.prank(unauthorized);
|
|
280
|
-
vm.expectRevert();
|
|
281
|
-
deployer.launchRulesetsFor(
|
|
282
|
-
testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), bytes32(0)
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// -----------------------------------------------------------------------
|
|
287
|
-
// queueRulesetsOf
|
|
288
|
-
// -----------------------------------------------------------------------
|
|
289
|
-
|
|
290
|
-
/// @notice queueRulesetsOf deploys a hook, transfers ownership to the project, and calls the controller.
|
|
291
|
-
function test_queueRulesetsOf_deploysHookAndCallsController() external {
|
|
292
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
|
|
293
|
-
_buildQueueRulesetsConfigs();
|
|
294
|
-
|
|
295
|
-
vm.prank(owner);
|
|
296
|
-
(uint256 rulesetId, IJB721TiersHook deployedHook) = deployer.queueRulesetsOf(
|
|
297
|
-
testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0)
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
// Verify the ruleset ID returned by the mock controller.
|
|
301
|
-
assertEq(rulesetId, 42, "Should return the ruleset ID from the controller");
|
|
302
|
-
|
|
303
|
-
// Verify a hook was deployed (non-zero address).
|
|
304
|
-
assertTrue(address(deployedHook) != address(0), "Hook should be deployed");
|
|
305
|
-
|
|
306
|
-
// Verify the controller's queueRulesetsOf was called.
|
|
307
|
-
assertTrue(mockCtrl.queueRulesetsOfCalled(), "Controller queueRulesetsOf should be called");
|
|
308
|
-
|
|
309
|
-
// Verify the hook's PROJECT_ID is correct.
|
|
310
|
-
assertEq(deployedHook.PROJECT_ID(), testProjectId, "Hook PROJECT_ID should match");
|
|
311
|
-
|
|
312
|
-
// Verify the hook's ownership was transferred to the project.
|
|
313
|
-
(, uint88 ownerProjectId,) = JBOwnable(address(deployedHook)).jbOwner();
|
|
314
|
-
assertEq(ownerProjectId, testProjectId, "Hook should be owned by project");
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/// @notice queueRulesetsOf correctly wires useDataHookForPay = true in the forwarded ruleset metadata.
|
|
318
|
-
function test_queueRulesetsOf_wiresDataHookForPay() external {
|
|
319
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
|
|
320
|
-
_buildQueueRulesetsConfigs();
|
|
321
|
-
|
|
322
|
-
vm.prank(owner);
|
|
323
|
-
(, IJB721TiersHook deployedHook) = deployer.queueRulesetsOf(
|
|
324
|
-
testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0)
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
// The hook address should not be zero -- this indirectly validates the dataHook was wired.
|
|
328
|
-
assertTrue(address(deployedHook) != address(0), "Hook should be deployed and wired as data hook");
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/// @notice queueRulesetsOf reverts when the caller lacks QUEUE_RULESETS permission.
|
|
332
|
-
function test_queueRulesetsOf_revertsWithoutPermission() external {
|
|
333
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
|
|
334
|
-
_buildQueueRulesetsConfigs();
|
|
335
|
-
|
|
336
|
-
// Override: mock permissions to deny.
|
|
337
|
-
vm.mockCall(mockJBPermissions, abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(false));
|
|
338
|
-
|
|
339
|
-
address unauthorized = makeAddr("unauthorized");
|
|
340
|
-
vm.prank(unauthorized);
|
|
341
|
-
vm.expectRevert();
|
|
342
|
-
deployer.queueRulesetsOf(testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0));
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/// @notice queueRulesetsOf with a zero salt uses non-deterministic deployment.
|
|
346
|
-
function test_queueRulesetsOf_zeroSaltNonDeterministic() external {
|
|
347
|
-
(JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
|
|
348
|
-
_buildQueueRulesetsConfigs();
|
|
349
|
-
|
|
350
|
-
vm.prank(owner);
|
|
351
|
-
(uint256 rulesetId, IJB721TiersHook deployedHook) = deployer.queueRulesetsOf(
|
|
352
|
-
testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0)
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
assertEq(rulesetId, 42, "Should return the ruleset ID");
|
|
356
|
-
assertTrue(address(deployedHook) != address(0), "Hook should be deployed");
|
|
357
|
-
}
|
|
358
|
-
}
|
|
@@ -1,155 +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
|
-
|
|
8
|
-
/// @notice defaultReserveBeneficiaryOf is globally overwritten when adding a tier with
|
|
9
|
-
/// useReserveBeneficiaryAsDefault=true.
|
|
10
|
-
contract Test_L34_ReserveBeneficiaryOverwrite is UnitTestSetup {
|
|
11
|
-
using stdStorage for StdStorage;
|
|
12
|
-
|
|
13
|
-
address alice = makeAddr("alice");
|
|
14
|
-
address bob = makeAddr("bob");
|
|
15
|
-
|
|
16
|
-
/// @notice Verify that adding a tier with useReserveBeneficiaryAsDefault=true overwrites the global default
|
|
17
|
-
/// and emits the SetDefaultReserveBeneficiary event.
|
|
18
|
-
function test_addTierWithDefaultBeneficiary_overwritesGlobal() public {
|
|
19
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
20
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
21
|
-
|
|
22
|
-
// Add tier 1: alice as default reserve beneficiary.
|
|
23
|
-
JB721TierConfig[] memory tier1Configs = new JB721TierConfig[](1);
|
|
24
|
-
tier1Configs[0].price = 1 ether;
|
|
25
|
-
tier1Configs[0].initialSupply = uint32(100);
|
|
26
|
-
tier1Configs[0].category = uint24(1);
|
|
27
|
-
tier1Configs[0].encodedIPFSUri = bytes32(uint256(0x1234));
|
|
28
|
-
tier1Configs[0].reserveFrequency = 5;
|
|
29
|
-
tier1Configs[0].reserveBeneficiary = alice;
|
|
30
|
-
tier1Configs[0].flags.useReserveBeneficiaryAsDefault = true;
|
|
31
|
-
|
|
32
|
-
vm.prank(address(testHook));
|
|
33
|
-
uint256[] memory tier1Ids = hookStore.recordAddTiers(tier1Configs);
|
|
34
|
-
|
|
35
|
-
// Verify alice is the default reserve beneficiary.
|
|
36
|
-
assertEq(hookStore.defaultReserveBeneficiaryOf(address(testHook)), alice);
|
|
37
|
-
|
|
38
|
-
// Verify tier 1 uses alice as its reserve beneficiary (via the default).
|
|
39
|
-
assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier1Ids[0]), alice);
|
|
40
|
-
|
|
41
|
-
// Add tier 2: no useReserveBeneficiaryAsDefault, with a per-tier beneficiary.
|
|
42
|
-
JB721TierConfig[] memory tier2Configs = new JB721TierConfig[](1);
|
|
43
|
-
tier2Configs[0].price = 2 ether;
|
|
44
|
-
tier2Configs[0].initialSupply = uint32(100);
|
|
45
|
-
tier2Configs[0].category = uint24(2);
|
|
46
|
-
tier2Configs[0].encodedIPFSUri = bytes32(uint256(0x5678));
|
|
47
|
-
tier2Configs[0].reserveFrequency = 5;
|
|
48
|
-
tier2Configs[0].reserveBeneficiary = bob;
|
|
49
|
-
tier2Configs[0].flags.useReserveBeneficiaryAsDefault = false;
|
|
50
|
-
|
|
51
|
-
vm.prank(address(testHook));
|
|
52
|
-
uint256[] memory tier2Ids = hookStore.recordAddTiers(tier2Configs);
|
|
53
|
-
|
|
54
|
-
// Default should still be alice.
|
|
55
|
-
assertEq(hookStore.defaultReserveBeneficiaryOf(address(testHook)), alice);
|
|
56
|
-
|
|
57
|
-
// Tier 2 should use bob (tier-specific).
|
|
58
|
-
assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier2Ids[0]), bob);
|
|
59
|
-
|
|
60
|
-
// Now add tier 3: bob as the NEW default reserve beneficiary.
|
|
61
|
-
// This should overwrite the default, affecting tier 1 which relies on the default.
|
|
62
|
-
JB721TierConfig[] memory tier3Configs = new JB721TierConfig[](1);
|
|
63
|
-
tier3Configs[0].price = 3 ether;
|
|
64
|
-
tier3Configs[0].initialSupply = uint32(100);
|
|
65
|
-
tier3Configs[0].category = uint24(3);
|
|
66
|
-
tier3Configs[0].encodedIPFSUri = bytes32(uint256(0x9ABC));
|
|
67
|
-
tier3Configs[0].reserveFrequency = 5;
|
|
68
|
-
tier3Configs[0].reserveBeneficiary = bob;
|
|
69
|
-
tier3Configs[0].flags.useReserveBeneficiaryAsDefault = true;
|
|
70
|
-
|
|
71
|
-
vm.prank(address(testHook));
|
|
72
|
-
uint256[] memory tier3Ids = hookStore.recordAddTiers(tier3Configs);
|
|
73
|
-
|
|
74
|
-
// Default should now be bob.
|
|
75
|
-
assertEq(hookStore.defaultReserveBeneficiaryOf(address(testHook)), bob);
|
|
76
|
-
|
|
77
|
-
// Tier 1 should now resolve to bob (the new default), NOT alice.
|
|
78
|
-
// This is the documented global overwrite behavior.
|
|
79
|
-
assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier1Ids[0]), bob);
|
|
80
|
-
|
|
81
|
-
// Tier 2 should still use bob (tier-specific, unaffected by default change).
|
|
82
|
-
assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier2Ids[0]), bob);
|
|
83
|
-
|
|
84
|
-
// Tier 3 should also resolve to bob (via the default).
|
|
85
|
-
assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier3Ids[0]), bob);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/// @notice Verify that the SetDefaultReserveBeneficiary event is emitted when the default changes.
|
|
89
|
-
function test_addTierWithDefaultBeneficiary_emitsEvent() public {
|
|
90
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
91
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
92
|
-
|
|
93
|
-
JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
|
|
94
|
-
tierConfigs[0].price = 1 ether;
|
|
95
|
-
tierConfigs[0].initialSupply = uint32(100);
|
|
96
|
-
tierConfigs[0].category = uint24(1);
|
|
97
|
-
tierConfigs[0].encodedIPFSUri = bytes32(uint256(0x1234));
|
|
98
|
-
tierConfigs[0].reserveFrequency = 5;
|
|
99
|
-
tierConfigs[0].reserveBeneficiary = alice;
|
|
100
|
-
tierConfigs[0].flags.useReserveBeneficiaryAsDefault = true;
|
|
101
|
-
|
|
102
|
-
// Expect the SetDefaultReserveBeneficiary event.
|
|
103
|
-
vm.expectEmit(true, true, false, true, address(hookStore));
|
|
104
|
-
emit IJB721TiersHookStore.SetDefaultReserveBeneficiary({
|
|
105
|
-
hook: address(testHook), newBeneficiary: alice, caller: address(testHook)
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
vm.prank(address(testHook));
|
|
109
|
-
hookStore.recordAddTiers(tierConfigs);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/// @notice Verify that no event is emitted when the beneficiary is already the same.
|
|
113
|
-
function test_addTierWithSameDefaultBeneficiary_noEvent() public {
|
|
114
|
-
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
115
|
-
IJB721TiersHookStore hookStore = testHook.STORE();
|
|
116
|
-
|
|
117
|
-
// First, set alice as the default.
|
|
118
|
-
JB721TierConfig[] memory tier1Configs = new JB721TierConfig[](1);
|
|
119
|
-
tier1Configs[0].price = 1 ether;
|
|
120
|
-
tier1Configs[0].initialSupply = uint32(100);
|
|
121
|
-
tier1Configs[0].category = uint24(1);
|
|
122
|
-
tier1Configs[0].encodedIPFSUri = bytes32(uint256(0x1234));
|
|
123
|
-
tier1Configs[0].reserveFrequency = 5;
|
|
124
|
-
tier1Configs[0].reserveBeneficiary = alice;
|
|
125
|
-
tier1Configs[0].flags.useReserveBeneficiaryAsDefault = true;
|
|
126
|
-
|
|
127
|
-
vm.prank(address(testHook));
|
|
128
|
-
hookStore.recordAddTiers(tier1Configs);
|
|
129
|
-
|
|
130
|
-
// Now add another tier with the same default — no event should be emitted.
|
|
131
|
-
JB721TierConfig[] memory tier2Configs = new JB721TierConfig[](1);
|
|
132
|
-
tier2Configs[0].price = 2 ether;
|
|
133
|
-
tier2Configs[0].initialSupply = uint32(100);
|
|
134
|
-
tier2Configs[0].category = uint24(2);
|
|
135
|
-
tier2Configs[0].encodedIPFSUri = bytes32(uint256(0x5678));
|
|
136
|
-
tier2Configs[0].reserveFrequency = 5;
|
|
137
|
-
tier2Configs[0].reserveBeneficiary = alice;
|
|
138
|
-
tier2Configs[0].flags.useReserveBeneficiaryAsDefault = true;
|
|
139
|
-
|
|
140
|
-
// Record the logs to verify no SetDefaultReserveBeneficiary event is emitted.
|
|
141
|
-
vm.recordLogs();
|
|
142
|
-
|
|
143
|
-
vm.prank(address(testHook));
|
|
144
|
-
hookStore.recordAddTiers(tier2Configs);
|
|
145
|
-
|
|
146
|
-
Vm.Log[] memory logs = vm.getRecordedLogs();
|
|
147
|
-
for (uint256 i; i < logs.length; i++) {
|
|
148
|
-
// SetDefaultReserveBeneficiary event signature.
|
|
149
|
-
assertTrue(
|
|
150
|
-
logs[i].topics[0] != keccak256("SetDefaultReserveBeneficiary(address,address,address)"),
|
|
151
|
-
"SetDefaultReserveBeneficiary should not be emitted when beneficiary unchanged"
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|