@bananapus/omnichain-deployers-v6 0.0.24 → 0.0.25
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/package.json +7 -7
- package/src/JBOmnichainDeployer.sol +31 -10
- package/test/JBOmnichainDeployer.t.sol +14 -2
- package/test/JBOmnichainDeployerGuard.t.sol +12 -0
- package/test/OmnichainDeployerAttacks.t.sol +20 -6
- package/test/OmnichainDeployerEdgeCases.t.sol +33 -15
- package/test/TestAuditGaps.sol +37 -15
- package/test/Tiered721HookComposition.t.sol +22 -8
- package/test/audit/CarryForwardRejectedHook.t.sol +12 -0
- package/test/audit/CodexNemesisAudit.t.sol +158 -0
- package/test/audit/JBOmnichainDeployer.t.sol +17 -5
- package/test/audit/WeightScalingComparison.t.sol +12 -0
- package/test/fork/OmnichainForkTestBase.sol +9 -1
- package/test/fork/TestSuckerDeploymentFork.t.sol +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/omnichain-deployers-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-omnichain-deployers-v6'"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
21
|
-
"@bananapus/buyback-hook-v6": "^0.0.
|
|
22
|
-
"@bananapus/core-v6": "^0.0.
|
|
20
|
+
"@bananapus/721-hook-v6": "^0.0.33",
|
|
21
|
+
"@bananapus/buyback-hook-v6": "^0.0.27",
|
|
22
|
+
"@bananapus/core-v6": "^0.0.34",
|
|
23
23
|
"@bananapus/ownable-v6": "^0.0.17",
|
|
24
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
25
|
-
"@bananapus/suckers-v6": "^0.0.
|
|
24
|
+
"@bananapus/permission-ids-v6": "^0.0.17",
|
|
25
|
+
"@bananapus/suckers-v6": "^0.0.25",
|
|
26
26
|
"@openzeppelin/contracts": "^5.6.1",
|
|
27
27
|
"@uniswap/v4-core": "^1.0.2"
|
|
28
28
|
},
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
"@bananapus/address-registry-v6": "^0.0.17",
|
|
31
31
|
"@sphinx-labs/plugins": "^0.33.2"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|
|
@@ -133,13 +133,14 @@ contract JBOmnichainDeployer is
|
|
|
133
133
|
// ------------------------- external views -------------------------- //
|
|
134
134
|
//*********************************************************************//
|
|
135
135
|
|
|
136
|
-
/// @notice Allow cash outs from suckers without a tax.
|
|
136
|
+
/// @notice Allow cash outs from suckers without a tax, and compute cross-chain tax supply for non-sucker cash outs.
|
|
137
137
|
/// @dev This function is part of `IJBRulesetDataHook`, and gets called before the revnet processes a cash out.
|
|
138
138
|
/// @param context Standard Juicebox cash out context. See `JBBeforeCashOutRecordedContext`.
|
|
139
139
|
/// @return cashOutTaxRate The cash out tax rate, which influences the amount of terminal tokens which get cashed
|
|
140
140
|
/// out.
|
|
141
141
|
/// @return cashOutCount The number of project tokens that are cashed out.
|
|
142
|
-
/// @return totalSupply The total
|
|
142
|
+
/// @return totalSupply The total token supply across all chains (for both proportional reclaim and tax).
|
|
143
|
+
/// @return effectiveSurplusValue The global surplus across all chains for proportional reclaim.
|
|
143
144
|
/// @return hookSpecifications The amount of funds and the data to send to cash out hooks (this contract).
|
|
144
145
|
function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
|
|
145
146
|
external
|
|
@@ -149,18 +150,28 @@ contract JBOmnichainDeployer is
|
|
|
149
150
|
uint256 cashOutTaxRate,
|
|
150
151
|
uint256 cashOutCount,
|
|
151
152
|
uint256 totalSupply,
|
|
153
|
+
uint256 effectiveSurplusValue,
|
|
152
154
|
JBCashOutHookSpecification[] memory hookSpecifications
|
|
153
155
|
)
|
|
154
156
|
{
|
|
155
157
|
// If the cash out is from a sucker, bypass all taxes and fees.
|
|
158
|
+
// Pass through the local surplus so the reclaim calculation can compute the pro-rata share.
|
|
156
159
|
if (SUCKER_REGISTRY.isSuckerOf({projectId: context.projectId, addr: context.holder})) {
|
|
157
|
-
return (0, context.cashOutCount, context.totalSupply, hookSpecifications);
|
|
160
|
+
return (0, context.cashOutCount, context.totalSupply, context.surplus.value, hookSpecifications);
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
// Start with the values from the context. Hooks below may override these.
|
|
161
164
|
cashOutTaxRate = context.cashOutTaxRate;
|
|
162
165
|
cashOutCount = context.cashOutCount;
|
|
163
|
-
|
|
166
|
+
|
|
167
|
+
// Compute the cross-chain total supply: local supply + sum of known peer chain supplies.
|
|
168
|
+
// This prevents the cash out tax from vanishing when a holder dominates the local supply.
|
|
169
|
+
totalSupply = context.totalSupply + SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
|
|
170
|
+
|
|
171
|
+
// Compute the cross-chain surplus: local surplus + sum of known peer chain surpluses.
|
|
172
|
+
// This prevents disproportionate reclaim when tokens bridge away but surplus stays.
|
|
173
|
+
effectiveSurplusValue = context.surplus.value
|
|
174
|
+
+ SUCKER_REGISTRY.remoteSurplusOf(context.projectId, 18, uint256(uint160(context.surplus.token)));
|
|
164
175
|
|
|
165
176
|
// Will hold the 721 hook's cash out specifications (always 0 or 1 element).
|
|
166
177
|
JBCashOutHookSpecification[] memory tiered721HookSpecifications;
|
|
@@ -170,8 +181,11 @@ contract JBOmnichainDeployer is
|
|
|
170
181
|
|
|
171
182
|
// If a 721 hook is set and opted into cash out handling, let it adjust the cash out parameters.
|
|
172
183
|
if (address(tiered721Config.hook) != address(0) && tiered721Config.useDataHookForCashOut) {
|
|
173
|
-
// Forward to the 721 hook. It may change the tax rate, count,
|
|
174
|
-
|
|
184
|
+
// Forward to the 721 hook. It may change the tax rate, count, and return hook specs.
|
|
185
|
+
// We discard the inner hook's effectiveSurplusValue — this contract computes the cross-chain values.
|
|
186
|
+
// We also discard its totalSupply since this contract computes the cross-chain supply.
|
|
187
|
+
// slither-disable-next-line unused-return
|
|
188
|
+
(cashOutTaxRate, cashOutCount,,, tiered721HookSpecifications) =
|
|
175
189
|
IJBRulesetDataHook(address(tiered721Config.hook)).beforeCashOutRecordedWith(context);
|
|
176
190
|
}
|
|
177
191
|
|
|
@@ -189,14 +203,17 @@ contract JBOmnichainDeployer is
|
|
|
189
203
|
hookContext.cashOutCount = cashOutCount;
|
|
190
204
|
hookContext.totalSupply = totalSupply;
|
|
191
205
|
|
|
192
|
-
// Forward to the extra hook. It may further change the tax rate, count,
|
|
193
|
-
|
|
206
|
+
// Forward to the extra hook. It may further change the tax rate, count, and return hook specs.
|
|
207
|
+
// We discard the inner hook's effectiveSurplusValue — this contract computes the cross-chain values.
|
|
208
|
+
// We also discard its totalSupply since this contract computes the cross-chain supply.
|
|
209
|
+
// slither-disable-next-line unused-return
|
|
210
|
+
(cashOutTaxRate, cashOutCount,,, extraHookSpecifications) =
|
|
194
211
|
extraHook.dataHook.beforeCashOutRecordedWith(hookContext);
|
|
195
212
|
}
|
|
196
213
|
|
|
197
214
|
// If neither hook returned any specifications, return the adjusted values with no hook specs.
|
|
198
215
|
if (tiered721HookSpecifications.length == 0 && extraHookSpecifications.length == 0) {
|
|
199
|
-
return (cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications);
|
|
216
|
+
return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
|
|
200
217
|
}
|
|
201
218
|
|
|
202
219
|
// Merge both hooks' specifications: 721 spec (if any) first, then extra hook specs.
|
|
@@ -215,7 +232,7 @@ contract JBOmnichainDeployer is
|
|
|
215
232
|
hookSpecifications = extraHookSpecifications;
|
|
216
233
|
}
|
|
217
234
|
|
|
218
|
-
return (cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications);
|
|
235
|
+
return (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, hookSpecifications);
|
|
219
236
|
}
|
|
220
237
|
|
|
221
238
|
/// @notice Forward the call to the original data hook.
|
|
@@ -614,6 +631,10 @@ contract JBOmnichainDeployer is
|
|
|
614
631
|
});
|
|
615
632
|
}
|
|
616
633
|
|
|
634
|
+
//*********************************************************************//
|
|
635
|
+
// -------------------------- internal views ------------------------ //
|
|
636
|
+
//*********************************************************************//
|
|
637
|
+
|
|
617
638
|
//*********************************************************************//
|
|
618
639
|
// ------------------------ internal functions ----------------------- //
|
|
619
640
|
//*********************************************************************//
|
|
@@ -89,6 +89,18 @@ contract TestJBOmnichainDeployer is Test {
|
|
|
89
89
|
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
90
90
|
abi.encode(uint256(1000), new JBPayHookSpecification[](0))
|
|
91
91
|
);
|
|
92
|
+
|
|
93
|
+
// Default: no remote supply or surplus (non-omnichain project).
|
|
94
|
+
vm.mockCall(
|
|
95
|
+
address(suckerRegistry),
|
|
96
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector),
|
|
97
|
+
abi.encode(uint256(0))
|
|
98
|
+
);
|
|
99
|
+
vm.mockCall(
|
|
100
|
+
address(suckerRegistry),
|
|
101
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteSurplusOf.selector),
|
|
102
|
+
abi.encode(uint256(0))
|
|
103
|
+
);
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
//*********************************************************************//
|
|
@@ -207,7 +219,7 @@ contract TestJBOmnichainDeployer is Test {
|
|
|
207
219
|
|
|
208
220
|
JBBeforeCashOutRecordedContext memory context = _makeCashOutContext(projectId, rulesetId, sucker);
|
|
209
221
|
|
|
210
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
222
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) =
|
|
211
223
|
deployer.beforeCashOutRecordedWith(context);
|
|
212
224
|
|
|
213
225
|
assertEq(cashOutTaxRate, 0, "sucker should get 0 tax");
|
|
@@ -223,7 +235,7 @@ contract TestJBOmnichainDeployer is Test {
|
|
|
223
235
|
|
|
224
236
|
JBBeforeCashOutRecordedContext memory context = _makeCashOutContext(projectId, rulesetId, randomAddr);
|
|
225
237
|
|
|
226
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
238
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) =
|
|
227
239
|
deployer.beforeCashOutRecordedWith(context);
|
|
228
240
|
|
|
229
241
|
assertEq(cashOutTaxRate, context.cashOutTaxRate, "non-sucker should get original tax");
|
|
@@ -82,6 +82,18 @@ contract MockSuckerRegistry is IJBSuckerRegistry {
|
|
|
82
82
|
return new address[](0);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function remoteTotalSupplyOf(uint256) external pure override returns (uint256) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function remoteBalanceOf(uint256, uint256, uint256) external pure override returns (uint256) {
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function remoteSurplusOf(uint256, uint256, uint256) external pure override returns (uint256) {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
function removeDeprecatedSucker(uint256, address) external override {}
|
|
86
98
|
function removeSuckerDeployer(address) external override {}
|
|
87
99
|
}
|
|
@@ -43,7 +43,7 @@ contract RevertingDataHook is IJBRulesetDataHook {
|
|
|
43
43
|
external
|
|
44
44
|
pure
|
|
45
45
|
override
|
|
46
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
46
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
47
47
|
{
|
|
48
48
|
revert("Hook always reverts");
|
|
49
49
|
}
|
|
@@ -72,9 +72,11 @@ contract InflatingDataHook is IJBRulesetDataHook {
|
|
|
72
72
|
external
|
|
73
73
|
pure
|
|
74
74
|
override
|
|
75
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
75
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
76
76
|
{
|
|
77
|
-
return (
|
|
77
|
+
return (
|
|
78
|
+
context.cashOutTaxRate, context.cashOutCount, context.totalSupply, 0, new JBCashOutHookSpecification[](0)
|
|
79
|
+
);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
function hasMintPermissionFor(uint256, JBRuleset calldata, address) external pure override returns (bool) {
|
|
@@ -142,6 +144,18 @@ contract OmnichainDeployerAttacks is Test {
|
|
|
142
144
|
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
143
145
|
abi.encode(uint256(1000), new JBPayHookSpecification[](0))
|
|
144
146
|
);
|
|
147
|
+
|
|
148
|
+
// Default: no remote supply or surplus (non-omnichain project).
|
|
149
|
+
vm.mockCall(
|
|
150
|
+
address(suckerRegistry),
|
|
151
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector),
|
|
152
|
+
abi.encode(uint256(0))
|
|
153
|
+
);
|
|
154
|
+
vm.mockCall(
|
|
155
|
+
address(suckerRegistry),
|
|
156
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteSurplusOf.selector),
|
|
157
|
+
abi.encode(uint256(0))
|
|
158
|
+
);
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
// =========================================================================
|
|
@@ -166,7 +180,7 @@ contract OmnichainDeployerAttacks is Test {
|
|
|
166
180
|
|
|
167
181
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, sucker);
|
|
168
182
|
|
|
169
|
-
(uint256 cashOutTaxRate
|
|
183
|
+
(uint256 cashOutTaxRate,,,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
170
184
|
assertEq(cashOutTaxRate, 0, "Sucker should get 0 tax");
|
|
171
185
|
}
|
|
172
186
|
|
|
@@ -182,7 +196,7 @@ contract OmnichainDeployerAttacks is Test {
|
|
|
182
196
|
|
|
183
197
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
184
198
|
|
|
185
|
-
(uint256 cashOutTaxRate
|
|
199
|
+
(uint256 cashOutTaxRate,,,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
186
200
|
assertEq(cashOutTaxRate, 5000, "Non-sucker should get original tax");
|
|
187
201
|
}
|
|
188
202
|
|
|
@@ -291,7 +305,7 @@ contract OmnichainDeployerAttacks is Test {
|
|
|
291
305
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, storedRulesetId, sucker);
|
|
292
306
|
|
|
293
307
|
// Sucker gets early return with 0 tax — never hits the reverting hook.
|
|
294
|
-
(uint256 cashOutTaxRate
|
|
308
|
+
(uint256 cashOutTaxRate,,,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
295
309
|
assertEq(cashOutTaxRate, 0, "Sucker bypasses hook and gets 0 tax");
|
|
296
310
|
}
|
|
297
311
|
|
|
@@ -78,7 +78,7 @@ contract CustomCashOutHook is IJBRulesetDataHook {
|
|
|
78
78
|
external
|
|
79
79
|
view
|
|
80
80
|
override
|
|
81
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
81
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
82
82
|
{
|
|
83
83
|
JBCashOutHookSpecification[] memory hookSpecifications;
|
|
84
84
|
|
|
@@ -92,7 +92,7 @@ contract CustomCashOutHook is IJBRulesetDataHook {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
return (cashOutTaxRateReturn, cashOutCountReturn, totalSupplyReturn, hookSpecifications);
|
|
95
|
+
return (cashOutTaxRateReturn, cashOutCountReturn, totalSupplyReturn, 0, hookSpecifications);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function hasMintPermissionFor(uint256, JBRuleset calldata, address) external view override returns (bool) {
|
|
@@ -145,6 +145,18 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
145
145
|
address(suckerRegistry), abi.encodeWithSelector(IJBSuckerRegistry.isSuckerOf.selector), abi.encode(false)
|
|
146
146
|
);
|
|
147
147
|
|
|
148
|
+
// Default: no remote supply or surplus (non-omnichain project).
|
|
149
|
+
vm.mockCall(
|
|
150
|
+
address(suckerRegistry),
|
|
151
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector),
|
|
152
|
+
abi.encode(uint256(0))
|
|
153
|
+
);
|
|
154
|
+
vm.mockCall(
|
|
155
|
+
address(suckerRegistry),
|
|
156
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteSurplusOf.selector),
|
|
157
|
+
abi.encode(uint256(0))
|
|
158
|
+
);
|
|
159
|
+
|
|
148
160
|
// Hook deployer mocks (every path now deploys a 721 hook).
|
|
149
161
|
vm.mockCall(
|
|
150
162
|
address(hookDeployer),
|
|
@@ -324,7 +336,7 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
324
336
|
|
|
325
337
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
326
338
|
|
|
327
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
339
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
328
340
|
assertEq(cashOutTaxRate, 5000, "Should return original tax rate");
|
|
329
341
|
assertEq(cashOutCount, 1000, "Should return original cashOutCount");
|
|
330
342
|
assertEq(totalSupply, 10_000, "Should return original totalSupply");
|
|
@@ -341,10 +353,12 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
341
353
|
|
|
342
354
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
343
355
|
|
|
344
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
356
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
345
357
|
assertEq(cashOutTaxRate, 2000, "Should return custom hook tax rate");
|
|
346
358
|
assertEq(cashOutCount, 500, "Should return custom hook cashOutCount");
|
|
347
|
-
|
|
359
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
360
|
+
// With no suckers, this equals context.totalSupply.
|
|
361
|
+
assertEq(totalSupply, ctx.totalSupply, "Should return cross-chain totalSupply (context value with no suckers)");
|
|
348
362
|
}
|
|
349
363
|
|
|
350
364
|
// =========================================================================
|
|
@@ -370,7 +384,7 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
370
384
|
|
|
371
385
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, sucker);
|
|
372
386
|
|
|
373
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
387
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
374
388
|
assertEq(cashOutTaxRate, 0, "Sucker should get 0 tax regardless of hooks");
|
|
375
389
|
assertEq(cashOutCount, 1000, "Sucker should get original cashOutCount");
|
|
376
390
|
assertEq(totalSupply, 10_000, "Sucker should get original totalSupply");
|
|
@@ -391,14 +405,14 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
391
405
|
vm.mockCall(
|
|
392
406
|
mock721,
|
|
393
407
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
394
|
-
abi.encode(uint256(9999), uint256(1), uint256(1), new JBCashOutHookSpecification[](0))
|
|
408
|
+
abi.encode(uint256(9999), uint256(1), uint256(1), uint256(0), new JBCashOutHookSpecification[](0))
|
|
395
409
|
);
|
|
396
410
|
|
|
397
411
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
398
412
|
|
|
399
413
|
// Since useDataHookForCashOut is false, the 721 hook should NOT be called.
|
|
400
414
|
// Original values should be returned.
|
|
401
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
415
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
402
416
|
assertEq(cashOutTaxRate, 5000, "Should return original tax rate, not 721 hook's 9999");
|
|
403
417
|
assertEq(cashOutCount, 1000, "Should return original cashOutCount, not 721 hook's 1");
|
|
404
418
|
assertEq(totalSupply, 10_000, "Should return original totalSupply, not 721 hook's 1");
|
|
@@ -417,10 +431,12 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
417
431
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
418
432
|
|
|
419
433
|
// Since useDataHookForCashOut is true, the custom hook SHOULD be called.
|
|
420
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
434
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
421
435
|
assertEq(cashOutTaxRate, 9999, "Should return custom hook's tax rate");
|
|
422
436
|
assertEq(cashOutCount, 1, "Should return custom hook's cashOutCount");
|
|
423
|
-
|
|
437
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
438
|
+
// With no suckers, this equals context.totalSupply.
|
|
439
|
+
assertEq(totalSupply, ctx.totalSupply, "Should return cross-chain totalSupply (context value with no suckers)");
|
|
424
440
|
}
|
|
425
441
|
|
|
426
442
|
function test_beforeCashOut_merges721AndCustomHookSpecifications() public {
|
|
@@ -441,7 +457,7 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
441
457
|
vm.mockCall(
|
|
442
458
|
mock721,
|
|
443
459
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
444
|
-
abi.encode(uint256(4000), uint256(700), uint256(9000), specs)
|
|
460
|
+
abi.encode(uint256(4000), uint256(700), uint256(9000), uint256(0), specs)
|
|
445
461
|
);
|
|
446
462
|
|
|
447
463
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
@@ -449,13 +465,15 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
449
465
|
(
|
|
450
466
|
uint256 cashOutTaxRate,
|
|
451
467
|
uint256 cashOutCount,
|
|
452
|
-
uint256 totalSupply
|
|
468
|
+
uint256 totalSupply,,
|
|
453
469
|
JBCashOutHookSpecification[] memory hookSpecifications
|
|
454
470
|
) = deployer.beforeCashOutRecordedWith(ctx);
|
|
455
471
|
|
|
456
472
|
assertEq(cashOutTaxRate, 2000, "Custom hook should receive and override 721-adjusted tax rate");
|
|
457
473
|
assertEq(cashOutCount, 500, "Custom hook should receive and override 721-adjusted cashOutCount");
|
|
458
|
-
|
|
474
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
475
|
+
// With no suckers, this equals context.totalSupply.
|
|
476
|
+
assertEq(totalSupply, ctx.totalSupply, "Should return cross-chain totalSupply (context value with no suckers)");
|
|
459
477
|
assertEq(hookSpecifications.length, 2, "721 and custom cash out specs should both be returned");
|
|
460
478
|
assertEq(address(hookSpecifications[0].hook), mock721, "721 hook spec should come first");
|
|
461
479
|
assertEq(hookSpecifications[0].amount, 11, "721 hook spec amount should be preserved");
|
|
@@ -477,7 +495,7 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
477
495
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
478
496
|
|
|
479
497
|
// Since useDataHookForCashOut is false, the custom hook should NOT be called.
|
|
480
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
498
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
481
499
|
assertEq(cashOutTaxRate, 5000, "Should return original tax rate, not custom hook's 2000");
|
|
482
500
|
assertEq(cashOutCount, 1000, "Should return original cashOutCount, not custom hook's 1");
|
|
483
501
|
assertEq(totalSupply, 10_000, "Should return original totalSupply, not custom hook's 1");
|
|
@@ -494,7 +512,7 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
494
512
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, rulesetId, attacker);
|
|
495
513
|
|
|
496
514
|
// No 721 hook, no custom hook — should fall through to original values.
|
|
497
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
515
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
498
516
|
assertEq(cashOutTaxRate, 5000, "Should return original tax rate");
|
|
499
517
|
assertEq(cashOutCount, 1000, "Should return original cashOutCount");
|
|
500
518
|
assertEq(totalSupply, 10_000, "Should return original totalSupply");
|
package/test/TestAuditGaps.sol
CHANGED
|
@@ -55,9 +55,11 @@ contract PayRevertingHook is IJBRulesetDataHook {
|
|
|
55
55
|
external
|
|
56
56
|
pure
|
|
57
57
|
override
|
|
58
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
58
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
59
59
|
{
|
|
60
|
-
return (
|
|
60
|
+
return (
|
|
61
|
+
context.cashOutTaxRate, context.cashOutCount, context.totalSupply, 0, new JBCashOutHookSpecification[](0)
|
|
62
|
+
);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
function hasMintPermissionFor(uint256, JBRuleset calldata, address) external pure override returns (bool) {
|
|
@@ -86,7 +88,7 @@ contract CashOutRevertingHook is IJBRulesetDataHook {
|
|
|
86
88
|
external
|
|
87
89
|
pure
|
|
88
90
|
override
|
|
89
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
91
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
90
92
|
{
|
|
91
93
|
revert CustomCashOutError();
|
|
92
94
|
}
|
|
@@ -117,9 +119,11 @@ contract MintPermissionRevertingHook is IJBRulesetDataHook {
|
|
|
117
119
|
external
|
|
118
120
|
pure
|
|
119
121
|
override
|
|
120
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
122
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
121
123
|
{
|
|
122
|
-
return (
|
|
124
|
+
return (
|
|
125
|
+
context.cashOutTaxRate, context.cashOutCount, context.totalSupply, 0, new JBCashOutHookSpecification[](0)
|
|
126
|
+
);
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
function hasMintPermissionFor(uint256, JBRuleset calldata, address) external pure override returns (bool) {
|
|
@@ -146,10 +150,10 @@ contract ExtremeCashOutHook is IJBRulesetDataHook {
|
|
|
146
150
|
external
|
|
147
151
|
pure
|
|
148
152
|
override
|
|
149
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
153
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
150
154
|
{
|
|
151
155
|
// Return extreme values: max tax rate, zero count, max supply.
|
|
152
|
-
return (type(uint256).max, 0, type(uint256).max, new JBCashOutHookSpecification[](0));
|
|
156
|
+
return (type(uint256).max, 0, type(uint256).max, 0, new JBCashOutHookSpecification[](0));
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
function hasMintPermissionFor(uint256, JBRuleset calldata, address) external pure override returns (bool) {
|
|
@@ -189,9 +193,11 @@ contract ManySpecsHook is IJBRulesetDataHook {
|
|
|
189
193
|
external
|
|
190
194
|
pure
|
|
191
195
|
override
|
|
192
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
196
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
193
197
|
{
|
|
194
|
-
return (
|
|
198
|
+
return (
|
|
199
|
+
context.cashOutTaxRate, context.cashOutCount, context.totalSupply, 0, new JBCashOutHookSpecification[](0)
|
|
200
|
+
);
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
function hasMintPermissionFor(uint256, JBRuleset calldata, address) external pure override returns (bool) {
|
|
@@ -219,9 +225,11 @@ contract ZeroWeightHook is IJBRulesetDataHook {
|
|
|
219
225
|
external
|
|
220
226
|
pure
|
|
221
227
|
override
|
|
222
|
-
returns (uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
228
|
+
returns (uint256, uint256, uint256, uint256, JBCashOutHookSpecification[] memory)
|
|
223
229
|
{
|
|
224
|
-
return (
|
|
230
|
+
return (
|
|
231
|
+
context.cashOutTaxRate, context.cashOutCount, context.totalSupply, 0, new JBCashOutHookSpecification[](0)
|
|
232
|
+
);
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
function hasMintPermissionFor(uint256, JBRuleset calldata, address) external pure override returns (bool) {
|
|
@@ -310,6 +318,18 @@ contract TestAuditGaps is Test {
|
|
|
310
318
|
address(suckerRegistry), abi.encodeWithSelector(IJBSuckerRegistry.isSuckerOf.selector), abi.encode(false)
|
|
311
319
|
);
|
|
312
320
|
|
|
321
|
+
// Default: no remote supply or surplus (non-omnichain project).
|
|
322
|
+
vm.mockCall(
|
|
323
|
+
address(suckerRegistry),
|
|
324
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector),
|
|
325
|
+
abi.encode(uint256(0))
|
|
326
|
+
);
|
|
327
|
+
vm.mockCall(
|
|
328
|
+
address(suckerRegistry),
|
|
329
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteSurplusOf.selector),
|
|
330
|
+
abi.encode(uint256(0))
|
|
331
|
+
);
|
|
332
|
+
|
|
313
333
|
// Default 721 hook mock: returns context weight and empty specs (0 tiers, no splits).
|
|
314
334
|
// A real 721 hook with no tiers returns contextWeight unchanged.
|
|
315
335
|
vm.mockCall(
|
|
@@ -369,7 +389,7 @@ contract TestAuditGaps is Test {
|
|
|
369
389
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, storedRulesetId, sucker);
|
|
370
390
|
|
|
371
391
|
// Sucker gets early return -- never hits the reverting hook.
|
|
372
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
392
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
373
393
|
assertEq(cashOutTaxRate, 0, "Sucker should get 0 tax even with reverting hook");
|
|
374
394
|
assertEq(cashOutCount, ctx.cashOutCount, "Sucker cashOutCount should pass through");
|
|
375
395
|
assertEq(totalSupply, ctx.totalSupply, "Sucker totalSupply should pass through");
|
|
@@ -427,10 +447,12 @@ contract TestAuditGaps is Test {
|
|
|
427
447
|
|
|
428
448
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, storedRulesetId, attacker);
|
|
429
449
|
|
|
430
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
450
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
431
451
|
assertEq(cashOutTaxRate, type(uint256).max, "Should pass through max tax rate from hook");
|
|
432
452
|
assertEq(cashOutCount, 0, "Should pass through 0 count from hook");
|
|
433
|
-
|
|
453
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
454
|
+
// With no suckers, this equals context.totalSupply.
|
|
455
|
+
assertEq(totalSupply, ctx.totalSupply, "Should return cross-chain totalSupply (context value with no suckers)");
|
|
434
456
|
}
|
|
435
457
|
|
|
436
458
|
// -------------------------------------------------------------------------
|
|
@@ -482,7 +504,7 @@ contract TestAuditGaps is Test {
|
|
|
482
504
|
// Cash out should work since the hook only reverts for pay (useDataHookForCashOut = false).
|
|
483
505
|
JBBeforeCashOutRecordedContext memory ctx = _makeCashOutContext(projectId, storedRulesetId, attacker);
|
|
484
506
|
|
|
485
|
-
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply
|
|
507
|
+
(uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(ctx);
|
|
486
508
|
assertEq(cashOutTaxRate, ctx.cashOutTaxRate, "Cash out should return original tax rate");
|
|
487
509
|
assertEq(cashOutCount, ctx.cashOutCount, "Cash out should return original count");
|
|
488
510
|
assertEq(totalSupply, ctx.totalSupply, "Cash out should return original supply");
|
|
@@ -106,6 +106,16 @@ contract Tiered721HookComposition is Test {
|
|
|
106
106
|
vm.mockCall(
|
|
107
107
|
address(suckerRegistry), abi.encodeWithSelector(IJBSuckerRegistry.isSuckerOf.selector), abi.encode(false)
|
|
108
108
|
);
|
|
109
|
+
vm.mockCall(
|
|
110
|
+
address(suckerRegistry),
|
|
111
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector),
|
|
112
|
+
abi.encode(uint256(0))
|
|
113
|
+
);
|
|
114
|
+
vm.mockCall(
|
|
115
|
+
address(suckerRegistry),
|
|
116
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteSurplusOf.selector),
|
|
117
|
+
abi.encode(uint256(0))
|
|
118
|
+
);
|
|
109
119
|
JBPayHookSpecification[] memory default721Specs = new JBPayHookSpecification[](1);
|
|
110
120
|
default721Specs[0] =
|
|
111
121
|
JBPayHookSpecification({hook: IJBPayHook(hookAddr), noop: false, amount: 0, metadata: bytes("")});
|
|
@@ -330,7 +340,7 @@ contract Tiered721HookComposition is Test {
|
|
|
330
340
|
abi.encode(true)
|
|
331
341
|
);
|
|
332
342
|
JBBeforeCashOutRecordedContext memory context = _makeCashOutContext(projectId, block.timestamp, sucker);
|
|
333
|
-
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply
|
|
343
|
+
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
|
|
334
344
|
assertEq(taxRate, 0, "sucker gets 0 tax");
|
|
335
345
|
assertEq(cashOutCount, context.cashOutCount);
|
|
336
346
|
assertEq(totalSupply, context.totalSupply);
|
|
@@ -349,13 +359,15 @@ contract Tiered721HookComposition is Test {
|
|
|
349
359
|
vm.mockCall(
|
|
350
360
|
buybackHookAddr,
|
|
351
361
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
352
|
-
abi.encode(uint256(3000), uint256(500), uint256(5000), cashOutSpecs)
|
|
362
|
+
abi.encode(uint256(3000), uint256(500), uint256(5000), uint256(0), cashOutSpecs)
|
|
353
363
|
);
|
|
354
364
|
JBBeforeCashOutRecordedContext memory context = _makeCashOutContext(projectId, block.timestamp, randomAddr);
|
|
355
|
-
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply
|
|
365
|
+
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
|
|
356
366
|
assertEq(taxRate, 3000, "buyback hook's tax rate");
|
|
357
367
|
assertEq(cashOutCount, 500, "buyback hook's cashOutCount");
|
|
358
|
-
|
|
368
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
369
|
+
// With no suckers, this equals context.totalSupply.
|
|
370
|
+
assertEq(totalSupply, context.totalSupply, "cross-chain totalSupply (context value with no suckers)");
|
|
359
371
|
}
|
|
360
372
|
|
|
361
373
|
function test_beforeCashOut_zeroTiers_forwardsToUserHook() public {
|
|
@@ -375,13 +387,15 @@ contract Tiered721HookComposition is Test {
|
|
|
375
387
|
vm.mockCall(
|
|
376
388
|
customHookAddr,
|
|
377
389
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
378
|
-
abi.encode(uint256(2000), uint256(100), uint256(1000), cashOutSpecs)
|
|
390
|
+
abi.encode(uint256(2000), uint256(100), uint256(1000), uint256(0), cashOutSpecs)
|
|
379
391
|
);
|
|
380
392
|
JBBeforeCashOutRecordedContext memory context = _makeCashOutContext(projectId, block.timestamp, randomAddr);
|
|
381
|
-
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply
|
|
393
|
+
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
|
|
382
394
|
assertEq(taxRate, 2000, "user hook's tax rate");
|
|
383
395
|
assertEq(cashOutCount, 100, "user hook's cashOutCount");
|
|
384
|
-
|
|
396
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
397
|
+
// With no suckers, this equals context.totalSupply.
|
|
398
|
+
assertEq(totalSupply, context.totalSupply, "cross-chain totalSupply (context value with no suckers)");
|
|
385
399
|
}
|
|
386
400
|
|
|
387
401
|
function test_beforeCashOut_zeroTiers_noUserHook_returnsOriginal() public {
|
|
@@ -398,7 +412,7 @@ contract Tiered721HookComposition is Test {
|
|
|
398
412
|
controller
|
|
399
413
|
);
|
|
400
414
|
JBBeforeCashOutRecordedContext memory context = _makeCashOutContext(projectId, block.timestamp, randomAddr);
|
|
401
|
-
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply
|
|
415
|
+
(uint256 taxRate, uint256 cashOutCount, uint256 totalSupply,,) = deployer.beforeCashOutRecordedWith(context);
|
|
402
416
|
assertEq(taxRate, context.cashOutTaxRate, "original tax rate");
|
|
403
417
|
assertEq(cashOutCount, context.cashOutCount);
|
|
404
418
|
assertEq(totalSupply, context.totalSupply);
|
|
@@ -126,6 +126,18 @@ contract MockSuckerRegistryCarryForward is IJBSuckerRegistry {
|
|
|
126
126
|
return new address[](0);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
function remoteTotalSupplyOf(uint256) external pure override returns (uint256) {
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function remoteBalanceOf(uint256, uint256, uint256) external pure override returns (uint256) {
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function remoteSurplusOf(uint256, uint256, uint256) external pure override returns (uint256) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
129
141
|
function removeDeprecatedSucker(uint256, address) external override {}
|
|
130
142
|
function removeSuckerDeployer(address) external override {}
|
|
131
143
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
|
|
6
|
+
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
7
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
8
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
9
|
+
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
10
|
+
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
11
|
+
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
12
|
+
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
13
|
+
import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
|
|
14
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
15
|
+
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
16
|
+
import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookProjectDeployer.sol";
|
|
17
|
+
import {IJBOwnable} from "@bananapus/ownable-v6/src/interfaces/IJBOwnable.sol";
|
|
18
|
+
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
19
|
+
import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
|
|
20
|
+
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
21
|
+
import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
|
|
22
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
23
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
24
|
+
|
|
25
|
+
import {JBOmnichainDeployer} from "../../src/JBOmnichainDeployer.sol";
|
|
26
|
+
import {JBOmnichain721Config} from "../../src/structs/JBOmnichain721Config.sol";
|
|
27
|
+
import {JBSuckerDeploymentConfig} from "../../src/structs/JBSuckerDeploymentConfig.sol";
|
|
28
|
+
|
|
29
|
+
contract CodexNemesisAudit is Test {
|
|
30
|
+
IJBPermissions internal permissions = IJBPermissions(makeAddr("permissions"));
|
|
31
|
+
IJBProjects internal projects = IJBProjects(makeAddr("projects"));
|
|
32
|
+
IJB721TiersHookDeployer internal hookDeployer = IJB721TiersHookDeployer(makeAddr("hookDeployer"));
|
|
33
|
+
IJBSuckerRegistry internal mockSuckerRegistry = IJBSuckerRegistry(makeAddr("suckerRegistry"));
|
|
34
|
+
IJBController internal controller = IJBController(makeAddr("controller"));
|
|
35
|
+
IJBDirectory internal directory = IJBDirectory(makeAddr("directory"));
|
|
36
|
+
address internal hookAddr = makeAddr("hook721");
|
|
37
|
+
address internal projectOwner = makeAddr("projectOwner");
|
|
38
|
+
address internal operator = makeAddr("operator");
|
|
39
|
+
|
|
40
|
+
uint256 internal constant PROJECT_ID = 42;
|
|
41
|
+
|
|
42
|
+
function setUp() public {
|
|
43
|
+
vm.mockCall(
|
|
44
|
+
address(permissions), abi.encodeWithSelector(IJBPermissions.setPermissionsFor.selector), abi.encode()
|
|
45
|
+
);
|
|
46
|
+
vm.mockCall(
|
|
47
|
+
address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(true)
|
|
48
|
+
);
|
|
49
|
+
vm.mockCall(
|
|
50
|
+
address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, PROJECT_ID), abi.encode(projectOwner)
|
|
51
|
+
);
|
|
52
|
+
vm.mockCall(
|
|
53
|
+
address(hookDeployer),
|
|
54
|
+
abi.encodeWithSelector(IJB721TiersHookDeployer.deployHookFor.selector),
|
|
55
|
+
abi.encode(IJB721TiersHook(hookAddr))
|
|
56
|
+
);
|
|
57
|
+
vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.transferOwnershipToProject.selector), abi.encode());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function test_poc_launchRulesetsFor_revertsWhenProjectHasNoControllerYet() public {
|
|
61
|
+
JBOmnichainDeployer deployer =
|
|
62
|
+
new JBOmnichainDeployer(mockSuckerRegistry, hookDeployer, permissions, projects, address(0));
|
|
63
|
+
|
|
64
|
+
vm.mockCall(
|
|
65
|
+
address(controller), abi.encodeWithSelector(IJBController.DIRECTORY.selector), abi.encode(directory)
|
|
66
|
+
);
|
|
67
|
+
// A freshly created project with no controller yet is valid for core launchRulesetsFor,
|
|
68
|
+
// but the deployer rejects it before the controller can set itself as first controller.
|
|
69
|
+
vm.mockCall(
|
|
70
|
+
address(directory),
|
|
71
|
+
abi.encodeWithSelector(IJBDirectory.controllerOf.selector, PROJECT_ID),
|
|
72
|
+
abi.encode(IERC165(address(0)))
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
JBRulesetConfig[] memory configs = new JBRulesetConfig[](1);
|
|
76
|
+
configs[0] = _makeRulesetConfig();
|
|
77
|
+
|
|
78
|
+
vm.prank(projectOwner);
|
|
79
|
+
vm.expectRevert(JBOmnichainDeployer.JBOmnichainDeployer_ControllerMismatch.selector);
|
|
80
|
+
deployer.launchRulesetsFor(PROJECT_ID, configs, new JBTerminalConfig[](0), "memo", controller);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function test_poc_deploySuckersFor_requiresHiddenPermissionForDeployerItself() public {
|
|
84
|
+
vm.mockCall(address(directory), abi.encodeWithSelector(IJBDirectory.PROJECTS.selector), abi.encode(projects));
|
|
85
|
+
|
|
86
|
+
JBSuckerRegistry registry = new JBSuckerRegistry(directory, permissions, address(this), address(0));
|
|
87
|
+
JBOmnichainDeployer deployer = new JBOmnichainDeployer(
|
|
88
|
+
IJBSuckerRegistry(address(registry)), hookDeployer, permissions, projects, address(0)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
vm.mockCall(
|
|
92
|
+
address(permissions),
|
|
93
|
+
abi.encodeWithSelector(
|
|
94
|
+
IJBPermissions.hasPermission.selector,
|
|
95
|
+
operator,
|
|
96
|
+
projectOwner,
|
|
97
|
+
PROJECT_ID,
|
|
98
|
+
JBPermissionIds.DEPLOY_SUCKERS,
|
|
99
|
+
true,
|
|
100
|
+
true
|
|
101
|
+
),
|
|
102
|
+
abi.encode(true)
|
|
103
|
+
);
|
|
104
|
+
vm.mockCall(
|
|
105
|
+
address(permissions),
|
|
106
|
+
abi.encodeWithSelector(
|
|
107
|
+
IJBPermissions.hasPermission.selector,
|
|
108
|
+
address(deployer),
|
|
109
|
+
projectOwner,
|
|
110
|
+
PROJECT_ID,
|
|
111
|
+
JBPermissionIds.DEPLOY_SUCKERS,
|
|
112
|
+
true,
|
|
113
|
+
true
|
|
114
|
+
),
|
|
115
|
+
abi.encode(false)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
JBSuckerDeploymentConfig memory config = JBSuckerDeploymentConfig({
|
|
119
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32("SUCKER_SALT")
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
vm.prank(operator);
|
|
123
|
+
vm.expectRevert(
|
|
124
|
+
abi.encodeWithSelector(
|
|
125
|
+
JBPermissioned.JBPermissioned_Unauthorized.selector,
|
|
126
|
+
projectOwner,
|
|
127
|
+
address(deployer),
|
|
128
|
+
PROJECT_ID,
|
|
129
|
+
JBPermissionIds.DEPLOY_SUCKERS
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
deployer.deploySuckersFor(PROJECT_ID, config);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function _makeRulesetConfig() internal pure returns (JBRulesetConfig memory config) {
|
|
136
|
+
config.metadata = JBRulesetMetadata({
|
|
137
|
+
reservedPercent: 0,
|
|
138
|
+
cashOutTaxRate: 0,
|
|
139
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
140
|
+
pausePay: false,
|
|
141
|
+
pauseCreditTransfers: false,
|
|
142
|
+
allowOwnerMinting: false,
|
|
143
|
+
allowSetCustomToken: false,
|
|
144
|
+
allowTerminalMigration: false,
|
|
145
|
+
allowSetTerminals: false,
|
|
146
|
+
allowSetController: false,
|
|
147
|
+
allowAddAccountingContext: false,
|
|
148
|
+
allowAddPriceFeed: false,
|
|
149
|
+
ownerMustSendPayouts: false,
|
|
150
|
+
holdFees: false,
|
|
151
|
+
useTotalSurplusForCashOuts: false,
|
|
152
|
+
useDataHookForPay: false,
|
|
153
|
+
useDataHookForCashOut: false,
|
|
154
|
+
dataHook: address(0),
|
|
155
|
+
metadata: 0
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -145,6 +145,14 @@ contract JBOmnichainDeployerTest is Test {
|
|
|
145
145
|
abi.encode()
|
|
146
146
|
);
|
|
147
147
|
vm.mockCall(suckerRegistry, abi.encodeWithSelector(IJBSuckerRegistry.isSuckerOf.selector), abi.encode(false));
|
|
148
|
+
vm.mockCall(
|
|
149
|
+
suckerRegistry,
|
|
150
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector),
|
|
151
|
+
abi.encode(uint256(0))
|
|
152
|
+
);
|
|
153
|
+
vm.mockCall(
|
|
154
|
+
suckerRegistry, abi.encodeWithSelector(IJBSuckerRegistry.remoteSurplusOf.selector), abi.encode(uint256(0))
|
|
155
|
+
);
|
|
148
156
|
|
|
149
157
|
JBOmnichainDeployer deployer =
|
|
150
158
|
new JBOmnichainDeployer(IJBSuckerRegistry(suckerRegistry), hookDeployer, permissions, projects, address(0));
|
|
@@ -153,7 +161,7 @@ contract JBOmnichainDeployerTest is Test {
|
|
|
153
161
|
vm.mockCall(
|
|
154
162
|
hookAddr,
|
|
155
163
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
156
|
-
abi.encode(uint256(1234), uint256(55), uint256(999), emptySpecs)
|
|
164
|
+
abi.encode(uint256(1234), uint256(55), uint256(999), uint256(0), emptySpecs)
|
|
157
165
|
);
|
|
158
166
|
|
|
159
167
|
JBRulesetConfig[] memory launchConfigs = new JBRulesetConfig[](1);
|
|
@@ -177,11 +185,13 @@ contract JBOmnichainDeployerTest is Test {
|
|
|
177
185
|
assertTrue(initialUseForCashOut, "launch should store the initial cash-out flag");
|
|
178
186
|
|
|
179
187
|
JBBeforeCashOutRecordedContext memory initialContext = _cashOutContext(initialRulesetId);
|
|
180
|
-
(uint256 initialTaxRate, uint256 initialCashOutCount, uint256 initialTotalSupply
|
|
188
|
+
(uint256 initialTaxRate, uint256 initialCashOutCount, uint256 initialTotalSupply,,) =
|
|
181
189
|
deployer.beforeCashOutRecordedWith(initialContext);
|
|
182
190
|
assertEq(initialTaxRate, 1234, "initial ruleset should forward cash-outs into the 721 hook");
|
|
183
191
|
assertEq(initialCashOutCount, 55, "initial ruleset should use the 721 hook cash-out count");
|
|
184
|
-
|
|
192
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
193
|
+
// With no suckers, this equals context.totalSupply (777).
|
|
194
|
+
assertEq(initialTotalSupply, 777, "initial ruleset should use cross-chain totalSupply (context value)");
|
|
185
195
|
|
|
186
196
|
vm.mockCall(
|
|
187
197
|
address(controller), abi.encodeWithSelector(IJBController.DIRECTORY.selector), abi.encode(directory)
|
|
@@ -239,13 +249,15 @@ contract JBOmnichainDeployerTest is Test {
|
|
|
239
249
|
assertTrue(queuedUseForCashOut, "queue should preserve the existing 721 cash-out flag");
|
|
240
250
|
|
|
241
251
|
JBBeforeCashOutRecordedContext memory queuedContext = _cashOutContext(queuedRulesetId);
|
|
242
|
-
(uint256 queuedTaxRate, uint256 queuedCashOutCount, uint256 queuedTotalSupply
|
|
252
|
+
(uint256 queuedTaxRate, uint256 queuedCashOutCount, uint256 queuedTotalSupply,,) =
|
|
243
253
|
deployer.beforeCashOutRecordedWith(queuedContext);
|
|
244
254
|
|
|
245
255
|
// The 721 hook is properly consulted for cash-outs in the queued ruleset.
|
|
246
256
|
assertEq(queuedTaxRate, 1234, "queued ruleset should forward cash-outs into the 721 hook");
|
|
247
257
|
assertEq(queuedCashOutCount, 55, "queued ruleset should use the 721 hook cash-out count");
|
|
248
|
-
|
|
258
|
+
// The deployer discards the inner hook's totalSupply and computes cross-chain supply instead.
|
|
259
|
+
// With no suckers, this equals context.totalSupply (777).
|
|
260
|
+
assertEq(queuedTotalSupply, 777, "queued ruleset should use cross-chain totalSupply (context value)");
|
|
249
261
|
}
|
|
250
262
|
|
|
251
263
|
function _cashOutContext(uint256 rulesetId) internal pure returns (JBBeforeCashOutRecordedContext memory context) {
|
|
@@ -70,6 +70,18 @@ contract WeightScalingComparisonTest is Test {
|
|
|
70
70
|
address(suckerRegistry), abi.encodeWithSelector(IJBSuckerRegistry.isSuckerOf.selector), abi.encode(false)
|
|
71
71
|
);
|
|
72
72
|
|
|
73
|
+
// Default: no remote supply or surplus (non-omnichain project).
|
|
74
|
+
vm.mockCall(
|
|
75
|
+
address(suckerRegistry),
|
|
76
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteTotalSupplyOf.selector),
|
|
77
|
+
abi.encode(uint256(0))
|
|
78
|
+
);
|
|
79
|
+
vm.mockCall(
|
|
80
|
+
address(suckerRegistry),
|
|
81
|
+
abi.encodeWithSelector(IJBSuckerRegistry.remoteSurplusOf.selector),
|
|
82
|
+
abi.encode(uint256(0))
|
|
83
|
+
);
|
|
84
|
+
|
|
73
85
|
// Mock hook deployer to return our mock hook address.
|
|
74
86
|
vm.mockCall(
|
|
75
87
|
address(hookDeployer),
|
|
@@ -27,6 +27,7 @@ import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
|
|
|
27
27
|
import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
|
|
28
28
|
import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
|
|
29
29
|
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
30
|
+
import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
|
|
30
31
|
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
31
32
|
import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookProjectDeployer.sol";
|
|
32
33
|
import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
|
|
@@ -181,7 +182,14 @@ abstract contract OmnichainForkTestBase is TestBaseWorkflow {
|
|
|
181
182
|
suckerRegistry = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
|
|
182
183
|
hookStore = new JB721TiersHookStore();
|
|
183
184
|
exampleHook = new JB721TiersHook(
|
|
184
|
-
jbDirectory(),
|
|
185
|
+
jbDirectory(),
|
|
186
|
+
jbPermissions(),
|
|
187
|
+
jbPrices(),
|
|
188
|
+
jbRulesets(),
|
|
189
|
+
hookStore,
|
|
190
|
+
jbSplits(),
|
|
191
|
+
new JB721CheckpointsDeployer(),
|
|
192
|
+
address(0)
|
|
185
193
|
);
|
|
186
194
|
addressRegistry = new JBAddressRegistry();
|
|
187
195
|
hookDeployer721 = new JB721TiersHookDeployer(exampleHook, hookStore, addressRegistry, multisig());
|
|
@@ -179,7 +179,7 @@ contract TestSuckerDeploymentFork is OmnichainForkTestBase {
|
|
|
179
179
|
});
|
|
180
180
|
|
|
181
181
|
// Call beforeCashOutRecordedWith directly on the omnichain deployer.
|
|
182
|
-
(uint256 returnedTaxRate, uint256 returnedCashOutCount, uint256 returnedTotalSupply
|
|
182
|
+
(uint256 returnedTaxRate, uint256 returnedCashOutCount, uint256 returnedTotalSupply,,) =
|
|
183
183
|
omnichainDeployer.beforeCashOutRecordedWith(context);
|
|
184
184
|
|
|
185
185
|
// Sucker should get 0% tax.
|