@bananapus/omnichain-deployers-v6 0.0.29 → 0.0.30
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/RISKS.md +5 -0
- package/package.json +3 -3
- package/src/JBOmnichainDeployer.sol +50 -13
- package/test/JBOmnichainDeployerGuard.t.sol +44 -8
- package/test/OmnichainDeployerEdgeCases.t.sol +8 -6
- package/test/OmnichainDeployerReentrancy.t.sol +8 -8
- package/test/Tiered721HookComposition.t.sol +11 -6
- package/test/audit/AuditFixesC2H6M14.t.sol +6 -21
- package/test/audit/CarryForwardRejectedHook.t.sol +5 -5
- package/test/audit/CashOutSpecMerge.t.sol +372 -0
- package/test/audit/{CodexNemesisDeterministicDrift.t.sol → DeterministicDrift.t.sol} +1 -1
- package/test/audit/{CodexNemesisDeterministicPeerDrift.t.sol → DeterministicPeerDrift.t.sol} +3 -3
- package/test/audit/ExtraCashOutHookZeroReclaim.t.sol +340 -0
- package/test/audit/{CodexNemesisForwardedPermissions.t.sol → ForwardedPermissions.t.sol} +1 -1
- package/test/audit/JBOmnichainDeployer.t.sol +6 -6
- package/test/audit/{CodexNemesisNftCashoutSupplyMismatch.t.sol → NftCashoutSupplyMismatch.t.sol} +11 -9
- package/test/audit/{CodexNemesisAudit.t.sol → OmnichainAudit.t.sol} +1 -2
- package/test/audit/SplitCreditWeight.t.sol +437 -0
- package/test/audit/WeightScalingComparison.t.sol +11 -12
- package/test/fork/OmnichainForkTestBase.sol +2 -1
- package/test/fork/TestOmnichain721QueueAndAdjust.t.sol +5 -5
- package/test/fork/TestOmnichainCashOutFork.t.sol +42 -40
- package/test/fork/TestOmnichainStressFork.t.sol +87 -95
- package/test/fork/TestOmnichainWeightFork.t.sol +9 -27
- package/test/invariants/CrossChainDeployerInvariant.t.sol +6 -2
- package/test/invariants/OmnichainDeployerInvariant.t.sol +3 -5
- package/test/invariants/handlers/CrossChainDeployerHandler.sol +0 -1
package/RISKS.md
CHANGED
|
@@ -86,6 +86,11 @@ Extra data hooks provided by the project owner in `_setup721` configuration can
|
|
|
86
86
|
**Missing hook721 alias check enables double invocation.** *(Minor)*
|
|
87
87
|
If the project owner configures the 721 hook as both the primary hook and as an extra data hook, it could be invoked twice. Accepted because this is self-inflicted misconfiguration — the deployer correctly processes each hook independently.
|
|
88
88
|
|
|
89
|
+
### Hook Selection
|
|
90
|
+
|
|
91
|
+
**ApprovalExpected rulesets excluded from hook carry-forward.**
|
|
92
|
+
When no new tiers are provided, the deployer carries forward the 721 hook from the most recent approved ruleset. Rulesets with `ApprovalExpected` status are intentionally excluded even though they may become active. Hook selection is irreversible — if the pending ruleset is later rejected by the approval hook, we'd have locked in a hook from a ruleset that never became active. The deployer falls back to the current (already-approved) ruleset in this case.
|
|
93
|
+
|
|
89
94
|
### Cross-Chain Deployment
|
|
90
95
|
|
|
91
96
|
**`_msgSender()` in deployment salt breaks cross-chain determinism.** *(Minor)*
|
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.30",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,7 @@
|
|
|
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.
|
|
20
|
+
"@bananapus/721-hook-v6": "^0.0.41",
|
|
21
21
|
"@bananapus/buyback-hook-v6": "^0.0.30",
|
|
22
22
|
"@bananapus/core-v6": "^0.0.36",
|
|
23
23
|
"@bananapus/ownable-v6": "^0.0.20",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@uniswap/v4-core": "^1.0.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@bananapus/address-registry-v6": "^0.0.
|
|
30
|
+
"@bananapus/address-registry-v6": "^0.0.20",
|
|
31
31
|
"@sphinx-labs/plugins": "^0.33.2"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -30,6 +30,7 @@ import {JBDeployerHookConfig} from "./structs/JBDeployerHookConfig.sol";
|
|
|
30
30
|
import {JBOmnichain721Config} from "./structs/JBOmnichain721Config.sol";
|
|
31
31
|
import {JBSuckerDeploymentConfig} from "./structs/JBSuckerDeploymentConfig.sol";
|
|
32
32
|
import {JBTiered721HookConfig} from "./structs/JBTiered721HookConfig.sol";
|
|
33
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
33
34
|
|
|
34
35
|
/// @notice Deploys, manages, and operates Juicebox projects with suckers.
|
|
35
36
|
// Project NFTs sent to this contract are not recoverable. The deployer does not
|
|
@@ -434,10 +435,10 @@ contract JBOmnichainDeployer is
|
|
|
434
435
|
// If a 721 hook is set and opted into cash out handling, let it adjust the cash out parameters.
|
|
435
436
|
if (address(tiered721Config.hook) != address(0) && tiered721Config.useDataHookForCashOut) {
|
|
436
437
|
// Forward to the 721 hook. It may change the tax rate, count, and return hook specs.
|
|
437
|
-
//
|
|
438
|
-
//
|
|
438
|
+
// Capture the 721 hook's totalSupply and effectiveSurplusValue — NFT cash-outs should use
|
|
439
|
+
// local-only denominators so holders reclaim against local surplus, not omnichain surplus.
|
|
439
440
|
// slither-disable-next-line unused-return
|
|
440
|
-
(cashOutTaxRate, cashOutCount
|
|
441
|
+
(cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, tiered721HookSpecifications) =
|
|
441
442
|
IJBRulesetDataHook(address(tiered721Config.hook)).beforeCashOutRecordedWith(context);
|
|
442
443
|
}
|
|
443
444
|
|
|
@@ -452,16 +453,23 @@ contract JBOmnichainDeployer is
|
|
|
452
453
|
// Build a mutable copy of the context with the latest values (possibly updated by the 721 hook).
|
|
453
454
|
JBBeforeCashOutRecordedContext memory hookContext = context;
|
|
454
455
|
hookContext.cashOutTaxRate = cashOutTaxRate;
|
|
455
|
-
hookContext.cashOutCount = cashOutCount;
|
|
456
456
|
hookContext.totalSupply = totalSupply;
|
|
457
457
|
hookContext.surplus.value = effectiveSurplusValue;
|
|
458
458
|
|
|
459
|
-
// Forward to the extra hook. It may further change the tax rate
|
|
460
|
-
// We discard
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
(
|
|
464
|
-
|
|
459
|
+
// Forward to the extra hook. It may further change the tax rate and return hook specs.
|
|
460
|
+
// We always discard totalSupply and effectiveSurplusValue — this contract computes
|
|
461
|
+
// cross-chain values for both. When the 721 hook is active, we also discard cashOutCount
|
|
462
|
+
// because the 721 hook redefines both cashOutCount and totalSupply as NFT cash-out weights
|
|
463
|
+
// (sum of tier prices), not fungible token counts. Letting the extra hook override
|
|
464
|
+
// cashOutCount would corrupt NFT pricing in the bonding curve.
|
|
465
|
+
if (address(tiered721Config.hook) != address(0) && tiered721Config.useDataHookForCashOut) {
|
|
466
|
+
// slither-disable-next-line unused-return
|
|
467
|
+
(cashOutTaxRate,,,, extraHookSpecifications) = extraHook.dataHook.beforeCashOutRecordedWith(hookContext);
|
|
468
|
+
} else {
|
|
469
|
+
// slither-disable-next-line unused-return
|
|
470
|
+
(cashOutTaxRate, cashOutCount,,, extraHookSpecifications) =
|
|
471
|
+
extraHook.dataHook.beforeCashOutRecordedWith(hookContext);
|
|
472
|
+
}
|
|
465
473
|
}
|
|
466
474
|
|
|
467
475
|
// If neither hook returned any specifications, return the adjusted values with no hook specs.
|
|
@@ -510,6 +518,8 @@ contract JBOmnichainDeployer is
|
|
|
510
518
|
bool hasTiered721Spec;
|
|
511
519
|
// The weight returned by the 721 hook (already scaled for splits).
|
|
512
520
|
uint256 tiered721Weight;
|
|
521
|
+
// The weight attributable to tier splits when issueTokensForSplits is true.
|
|
522
|
+
uint256 splitCreditWeight;
|
|
513
523
|
// Whether a 721 hook is configured for this project's ruleset.
|
|
514
524
|
bool has721Hook;
|
|
515
525
|
if (address(tiered721Config.hook) != address(0)) {
|
|
@@ -526,6 +536,14 @@ contract JBOmnichainDeployer is
|
|
|
526
536
|
hasTiered721Spec = true;
|
|
527
537
|
tiered721HookSpec = tiered721HookSpecs[0];
|
|
528
538
|
totalSplitAmount = tiered721HookSpec.amount;
|
|
539
|
+
|
|
540
|
+
// Decode splitCreditWeight from the 721 hook's metadata (4th field).
|
|
541
|
+
// When issueTokensForSplits is true and splits exist, this holds the weight portion
|
|
542
|
+
// attributable to tier splits — used to prevent split credit erasure if the extra
|
|
543
|
+
// hook (e.g. buyback) returns weight=0.
|
|
544
|
+
if (tiered721HookSpec.metadata.length >= 128) {
|
|
545
|
+
(,,, splitCreditWeight) = abi.decode(tiered721HookSpec.metadata, (address, address, bytes, uint256));
|
|
546
|
+
}
|
|
529
547
|
}
|
|
530
548
|
}
|
|
531
549
|
|
|
@@ -541,11 +559,27 @@ contract JBOmnichainDeployer is
|
|
|
541
559
|
if (address(extraHook.dataHook) != address(0) && extraHook.useDataHookForPay) {
|
|
542
560
|
JBBeforePayRecordedContext memory hookContext = context;
|
|
543
561
|
hookContext.amount.value = projectAmount;
|
|
544
|
-
// Pass the
|
|
545
|
-
//
|
|
546
|
-
|
|
562
|
+
// Pass the original context.weight — NOT the 721 hook's split-adjusted weight.
|
|
563
|
+
// The extra hook (e.g. buyback) applies its own weight logic; using the 721 hook's
|
|
564
|
+
// already-split-adjusted weight would double-discount the split ratio.
|
|
547
565
|
(weight, dataHookSpecs) = extraHook.dataHook.beforePayRecordedWith(hookContext);
|
|
548
566
|
customHookCalled = true;
|
|
567
|
+
|
|
568
|
+
// The custom hook (e.g. buyback) returned a weight based on the original context.weight.
|
|
569
|
+
// If the 721 hook scaled weight down for tier splits, apply the same ratio so the terminal
|
|
570
|
+
// doesn't over-mint tokens relative to the funds actually entering the project.
|
|
571
|
+
// When issueTokensForSplits is true, tiered721Weight == context.weight and the ratio is 1x.
|
|
572
|
+
if (has721Hook && context.weight > 0 && tiered721Weight != context.weight) {
|
|
573
|
+
weight = mulDiv(weight, tiered721Weight, context.weight);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// When the extra hook returns weight=0 (e.g. buyback found no profitable swap) but tier
|
|
577
|
+
// splits exist with issueTokensForSplits=true, the split credit must still mint fungible tokens.
|
|
578
|
+
// The split credit weight is independent of buyback routing — it represents the token issuance
|
|
579
|
+
// for funds forwarded to tier split beneficiaries.
|
|
580
|
+
if (weight == 0 && splitCreditWeight > 0) {
|
|
581
|
+
weight = splitCreditWeight;
|
|
582
|
+
}
|
|
549
583
|
}
|
|
550
584
|
}
|
|
551
585
|
|
|
@@ -813,6 +847,9 @@ contract JBOmnichainDeployer is
|
|
|
813
847
|
{
|
|
814
848
|
// First try the latest queued ruleset — if it's been explicitly approved
|
|
815
849
|
// (or has no approval hook), its hook config should take precedence.
|
|
850
|
+
// Conservative: only use Approved or Empty status. ApprovalExpected is intentionally
|
|
851
|
+
// excluded because hook selection is irreversible — if the pending ruleset is later rejected
|
|
852
|
+
// by the approval hook, we'd have locked in a hook from a ruleset that never became active.
|
|
816
853
|
(JBRuleset memory latestQueued, JBApprovalStatus approvalStatus) =
|
|
817
854
|
controller.RULESETS().latestQueuedOf(projectId);
|
|
818
855
|
if (
|
|
@@ -263,11 +263,11 @@ contract JBOmnichainDeployerGuardTest is TestBaseWorkflow {
|
|
|
263
263
|
.setPermissionsFor(
|
|
264
264
|
owner,
|
|
265
265
|
JBPermissionsData({
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
266
|
+
operator: address(deployer),
|
|
267
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
268
|
+
projectId: uint64(projectId),
|
|
269
|
+
permissionIds: permissionIds
|
|
270
|
+
})
|
|
271
271
|
);
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -303,9 +303,10 @@ contract JBOmnichainDeployerGuardTest is TestBaseWorkflow {
|
|
|
303
303
|
|
|
304
304
|
JBRulesetConfig[] memory rulesets = _makeRulesetConfigs(1);
|
|
305
305
|
|
|
306
|
-
// Should succeed without reverting.
|
|
307
306
|
JBOmnichain721Config memory empty721;
|
|
308
|
-
|
|
307
|
+
(uint256 rulesetId,) =
|
|
308
|
+
deployer.queueRulesetsOf(projectId, empty721, rulesets, "queue", IJBController(address(jbController())));
|
|
309
|
+
assertGt(rulesetId, 0, "Queued ruleset ID must be non-zero");
|
|
309
310
|
}
|
|
310
311
|
|
|
311
312
|
/// @notice Queue rulesets reverts when called in the same block as launch
|
|
@@ -363,6 +364,41 @@ contract JBOmnichainDeployerGuardTest is TestBaseWorkflow {
|
|
|
363
364
|
|
|
364
365
|
// Now should succeed.
|
|
365
366
|
JBOmnichain721Config memory empty721b;
|
|
366
|
-
|
|
367
|
+
(uint256 rulesetId,) =
|
|
368
|
+
deployer.queueRulesetsOf(projectId, empty721b, rulesets, "ok-now", IJBController(address(jbController())));
|
|
369
|
+
assertGt(rulesetId, 0, "Queued ruleset ID must be non-zero after warping past conflict");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/// @notice When no new tiers are provided and no latestQueued exists (single launch ruleset that
|
|
373
|
+
/// became the current ruleset), the hook is carried forward from the current ruleset (lines 862-864).
|
|
374
|
+
function test_queueRulesetsOf_carriesForwardFromCurrent_whenNoLatestQueued() public {
|
|
375
|
+
// Launch with 1 ruleset — this deploys a 721 hook stored for the launch ruleset.
|
|
376
|
+
uint256 projectId = _launchProject(1);
|
|
377
|
+
uint256 launchRulesetId = block.timestamp;
|
|
378
|
+
_grantDeployerQueuePermission(projectId);
|
|
379
|
+
|
|
380
|
+
// Verify the 721 hook was stored for the launch ruleset.
|
|
381
|
+
(IJB721TiersHook launchHook,) = deployer.tiered721HookOf(projectId, launchRulesetId);
|
|
382
|
+
assertEq(address(launchHook), mockHookAddr, "Launch ruleset should have 721 hook stored");
|
|
383
|
+
|
|
384
|
+
// Warp forward so the launched ruleset is the current active one and the guard passes.
|
|
385
|
+
vm.warp(block.timestamp + 1 days);
|
|
386
|
+
|
|
387
|
+
// Queue a new ruleset with NO new tiers → triggers carry-forward logic.
|
|
388
|
+
JBRulesetConfig[] memory rulesets = _makeRulesetConfigs(1);
|
|
389
|
+
JBOmnichain721Config memory empty721;
|
|
390
|
+
(uint256 queuedRulesetId,) = deployer.queueRulesetsOf(
|
|
391
|
+
projectId, empty721, rulesets, "carry-forward", IJBController(address(jbController()))
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
assertGt(queuedRulesetId, 0, "Queued ruleset ID must be non-zero");
|
|
395
|
+
|
|
396
|
+
// Verify the hook was carried forward to the new queued ruleset.
|
|
397
|
+
(IJB721TiersHook carriedHook,) = deployer.tiered721HookOf(projectId, queuedRulesetId);
|
|
398
|
+
assertEq(
|
|
399
|
+
address(carriedHook),
|
|
400
|
+
address(launchHook),
|
|
401
|
+
"Queued ruleset should carry forward the 721 hook from the current ruleset"
|
|
402
|
+
);
|
|
367
403
|
}
|
|
368
404
|
}
|
|
@@ -322,9 +322,10 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
322
322
|
ctx.amount.value = 1 ether;
|
|
323
323
|
ctx.weight = 1000;
|
|
324
324
|
|
|
325
|
-
// The custom hook's weight is
|
|
325
|
+
// The custom hook's weight is scaled by the 721 split ratio (500/1000 = 50%).
|
|
326
|
+
// mulDiv(type(uint256).max, 500, 1000) = type(uint256).max / 2.
|
|
326
327
|
(uint256 weight,) = deployer.beforePayRecordedWith(ctx);
|
|
327
|
-
assertEq(weight, type(uint256).max, "custom hook's
|
|
328
|
+
assertEq(weight, type(uint256).max / 2, "custom hook's weight scaled by 721 split ratio");
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
// =========================================================================
|
|
@@ -471,10 +472,11 @@ contract OmnichainDeployerEdgeCases is Test {
|
|
|
471
472
|
) = deployer.beforeCashOutRecordedWith(ctx);
|
|
472
473
|
|
|
473
474
|
assertEq(cashOutTaxRate, 2000, "Custom hook should receive and override 721-adjusted tax rate");
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
//
|
|
477
|
-
|
|
475
|
+
// After the security fix, the 721 hook's cashOutCount is preserved — the extra hook cannot override it.
|
|
476
|
+
assertEq(cashOutCount, 700, "721 hook cashOutCount must be preserved (extra hook cannot override NFT pricing)");
|
|
477
|
+
// The deployer captures the 721 hook's totalSupply (9000) — NFT cash-outs use local-only
|
|
478
|
+
// denominators. The extra hook's totalSupply is discarded, preserving the 721 hook's value.
|
|
479
|
+
assertEq(totalSupply, 9000, "Should return 721 hook totalSupply (local denominator)");
|
|
478
480
|
assertEq(hookSpecifications.length, 2, "721 and custom cash out specs should both be returned");
|
|
479
481
|
assertEq(address(hookSpecifications[0].hook), mock721, "721 hook spec should come first");
|
|
480
482
|
assertEq(hookSpecifications[0].amount, 11, "721 hook spec amount should be preserved");
|
|
@@ -240,14 +240,14 @@ contract OmnichainDeployerReentrancy is OmnichainForkTestBase {
|
|
|
240
240
|
vm.prank(address(hook));
|
|
241
241
|
try jbMultiTerminal()
|
|
242
242
|
.cashOutTokensOf({
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
243
|
+
holder: address(hook),
|
|
244
|
+
projectId: projectId,
|
|
245
|
+
cashOutCount: hookTokens / 2,
|
|
246
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
247
|
+
minTokensReclaimed: 0,
|
|
248
|
+
beneficiary: payable(address(hook)),
|
|
249
|
+
metadata: ""
|
|
250
|
+
}) {
|
|
251
251
|
// If it succeeds, check conservation.
|
|
252
252
|
uint256 surplusAfter = _terminalBalance(projectId, JBConstants.NATIVE_TOKEN);
|
|
253
253
|
uint256 remainingTokens = jbTokens().totalBalanceOf(address(hook), projectId);
|
|
@@ -302,7 +302,8 @@ contract Tiered721HookComposition is Test {
|
|
|
302
302
|
);
|
|
303
303
|
JBBeforePayRecordedContext memory context = _makePayContext(projectId, block.timestamp);
|
|
304
304
|
(uint256 weight, JBPayHookSpecification[] memory specs) = deployer.beforePayRecordedWith(context);
|
|
305
|
-
|
|
305
|
+
// Buyback returned weight=555, 721 split ratio=750/1000: mulDiv(555, 750, 1000) = 416.
|
|
306
|
+
assertEq(weight, 416, "weight = buyback weight scaled by 721 split ratio");
|
|
306
307
|
assertEq(specs.length, 2, "721 spec + buyback spec");
|
|
307
308
|
assertEq(address(specs[0].hook), hookAddr, "first = 721 hook");
|
|
308
309
|
assertEq(specs[0].amount, splitAmount, "721 split amount preserved");
|
|
@@ -622,7 +623,8 @@ contract Tiered721HookComposition is Test {
|
|
|
622
623
|
);
|
|
623
624
|
JBBeforePayRecordedContext memory context = _makePayContext(projectId, block.timestamp);
|
|
624
625
|
(uint256 weight,) = deployer.beforePayRecordedWith(context);
|
|
625
|
-
|
|
626
|
+
// Custom hook returned 2000, 721 split ratio=600/1000: mulDiv(2000, 600, 1000) = 1200.
|
|
627
|
+
assertEq(weight, 1200, "weight = custom hook weight scaled by 721 split ratio");
|
|
626
628
|
}
|
|
627
629
|
|
|
628
630
|
function test_beforePay_fullSplit_weightZero() public {
|
|
@@ -690,7 +692,8 @@ contract Tiered721HookComposition is Test {
|
|
|
690
692
|
);
|
|
691
693
|
JBBeforePayRecordedContext memory context = _makePayContext(projectId, block.timestamp);
|
|
692
694
|
(uint256 weight, JBPayHookSpecification[] memory specs) = deployer.beforePayRecordedWith(context);
|
|
693
|
-
|
|
695
|
+
// Buyback returned 2000, 721 split ratio=600/1000: mulDiv(2000, 600, 1000) = 1200.
|
|
696
|
+
assertEq(weight, 1200, "weight = buyback weight scaled by 721 split ratio");
|
|
694
697
|
assertEq(specs.length, 2, "721 spec + buyback spec");
|
|
695
698
|
assertEq(address(specs[0].hook), hookAddr, "first = 721 hook");
|
|
696
699
|
assertEq(specs[0].amount, 0.4 ether, "721 split amount");
|
|
@@ -709,8 +712,9 @@ contract Tiered721HookComposition is Test {
|
|
|
709
712
|
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
710
713
|
abi.encode(uint256(800), hookSpecs)
|
|
711
714
|
);
|
|
712
|
-
// Buyback mint path returns context.weight
|
|
713
|
-
|
|
715
|
+
// Buyback mint path returns context.weight unchanged. The deployer passes the original
|
|
716
|
+
// context.weight (1000) to the buyback hook, so the mint path returns 1000.
|
|
717
|
+
uint256 mintPathWeight = 1000;
|
|
714
718
|
JBPayHookSpecification[] memory emptyBuybackSpecs = new JBPayHookSpecification[](0);
|
|
715
719
|
vm.mockCall(
|
|
716
720
|
buybackHookAddr,
|
|
@@ -719,7 +723,8 @@ contract Tiered721HookComposition is Test {
|
|
|
719
723
|
);
|
|
720
724
|
JBBeforePayRecordedContext memory context = _makePayContext(projectId, block.timestamp);
|
|
721
725
|
(uint256 weight, JBPayHookSpecification[] memory specs) = deployer.beforePayRecordedWith(context);
|
|
722
|
-
|
|
726
|
+
// mulDiv(1000, 800, 1000) = 800: the 721 split ratio is correctly applied.
|
|
727
|
+
assertEq(weight, 800, "weight = buyback weight scaled by 721 split ratio (matches 721 hook weight)");
|
|
723
728
|
assertEq(specs.length, 1, "only 721 spec (buyback empty)");
|
|
724
729
|
assertEq(address(specs[0].hook), hookAddr, "spec = 721 hook");
|
|
725
730
|
assertEq(specs[0].amount, 0.2 ether, "721 split amount");
|
|
@@ -8,14 +8,11 @@ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
|
8
8
|
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
9
9
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
10
10
|
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
11
|
-
import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
12
11
|
import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforeCashOutRecordedContext.sol";
|
|
13
|
-
import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
|
|
14
12
|
import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
|
|
15
13
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
16
14
|
import {JBFundAccessLimitGroup} from "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
|
|
17
15
|
import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
|
|
18
|
-
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
19
16
|
import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
|
|
20
17
|
import {JBRulesetMetadata} from "@bananapus/core-v6/src/structs/JBRulesetMetadata.sol";
|
|
21
18
|
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
@@ -379,19 +376,13 @@ contract AuditFixesC2H6M14 is Test {
|
|
|
379
376
|
JBRulesetConfig[] memory configs = new JBRulesetConfig[](1);
|
|
380
377
|
configs[0] = _rulesetConfig();
|
|
381
378
|
|
|
382
|
-
// 721 hook
|
|
383
|
-
|
|
384
|
-
vm.mockCall(
|
|
385
|
-
hookAddr,
|
|
386
|
-
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
387
|
-
abi.encode(uint256(5000), uint256(1000), uint256(10_000), uint256(0), emptySpecs)
|
|
388
|
-
);
|
|
389
|
-
|
|
379
|
+
// Disable the 721 hook for cash-out so the deployer computes cross-chain surplus itself.
|
|
380
|
+
// These tests verify C-2 (surplus aggregation), not NFT cashout behavior.
|
|
390
381
|
deployer.launchProjectFor({
|
|
391
382
|
owner: projectOwner,
|
|
392
383
|
projectUri: "test",
|
|
393
384
|
deploy721Config: JBOmnichain721Config({
|
|
394
|
-
deployTiersHookConfig: _empty721HookConfig(), useDataHookForCashOut:
|
|
385
|
+
deployTiersHookConfig: _empty721HookConfig(), useDataHookForCashOut: false, salt: bytes32(0)
|
|
395
386
|
}),
|
|
396
387
|
rulesetConfigurations: configs,
|
|
397
388
|
terminalConfigurations: new JBTerminalConfig[](0),
|
|
@@ -420,19 +411,13 @@ contract AuditFixesC2H6M14 is Test {
|
|
|
420
411
|
configs[0].metadata.dataHook = extraHookAddr;
|
|
421
412
|
configs[0].metadata.useDataHookForCashOut = true;
|
|
422
413
|
|
|
423
|
-
// 721 hook
|
|
424
|
-
|
|
425
|
-
vm.mockCall(
|
|
426
|
-
hookAddr,
|
|
427
|
-
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
428
|
-
abi.encode(uint256(5000), uint256(1000), uint256(10_000), uint256(0), emptySpecs)
|
|
429
|
-
);
|
|
430
|
-
|
|
414
|
+
// Disable the 721 hook for cash-out so the deployer computes cross-chain surplus itself.
|
|
415
|
+
// These tests verify H-6 (extra hook forwarding), not NFT cashout behavior.
|
|
431
416
|
deployer.launchProjectFor({
|
|
432
417
|
owner: projectOwner,
|
|
433
418
|
projectUri: "test",
|
|
434
419
|
deploy721Config: JBOmnichain721Config({
|
|
435
|
-
deployTiersHookConfig: _empty721HookConfig(), useDataHookForCashOut:
|
|
420
|
+
deployTiersHookConfig: _empty721HookConfig(), useDataHookForCashOut: false, salt: bytes32(0)
|
|
436
421
|
}),
|
|
437
422
|
rulesetConfigurations: configs,
|
|
438
423
|
terminalConfigurations: new JBTerminalConfig[](0),
|
|
@@ -318,11 +318,11 @@ contract CarryForwardRejectedHookTest is TestBaseWorkflow {
|
|
|
318
318
|
.setPermissionsFor(
|
|
319
319
|
owner,
|
|
320
320
|
JBPermissionsData({
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
321
|
+
operator: address(deployer),
|
|
322
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
323
|
+
projectId: uint64(projectId),
|
|
324
|
+
permissionIds: permissionIds
|
|
325
|
+
})
|
|
326
326
|
);
|
|
327
327
|
}
|
|
328
328
|
}
|