@bananapus/omnichain-deployers-v6 0.0.38 → 0.0.39

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/README.md CHANGED
@@ -90,7 +90,7 @@ src/
90
90
  interfaces/
91
91
  structs/
92
92
  test/
93
- unit, attack, invariant, fork, audit, and regression coverage
93
+ unit, attack, invariant, fork, review, and regression coverage
94
94
  script/
95
95
  Deploy.s.sol
96
96
  helpers/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/omnichain-deployers-v6",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,10 +27,10 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@bananapus/721-hook-v6": "0.0.43",
30
- "@bananapus/core-v6": "0.0.39",
30
+ "@bananapus/core-v6": "0.0.44",
31
31
  "@bananapus/ownable-v6": "0.0.24",
32
32
  "@bananapus/permission-ids-v6": "0.0.22",
33
- "@bananapus/suckers-v6": "0.0.34",
33
+ "@bananapus/suckers-v6": "0.0.37",
34
34
  "@openzeppelin/contracts": "5.6.1"
35
35
  },
36
36
  "devDependencies": {
@@ -24,5 +24,5 @@
24
24
  ## Useful Proof Points
25
25
 
26
26
  - [`test/JBOmnichainDeployer.t.sol`](../test/JBOmnichainDeployer.t.sol) for baseline deploy and queue flows.
27
- - [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for pinned edge cases.
27
+ - [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) for pinned edge cases.
28
28
  - [`test/Tiered721HookComposition.t.sol`](../test/Tiered721HookComposition.t.sol) when cross-repo integration behavior matters more than isolated unit logic.
@@ -25,4 +25,4 @@
25
25
  - [`test/Tiered721HookComposition.t.sol`](../test/Tiered721HookComposition.t.sol) for hook-composition behavior.
26
26
  - [`test/JBOmnichainDeployerGuard.t.sol`](../test/JBOmnichainDeployerGuard.t.sol) and [`test/OmnichainDeployerAttacks.t.sol`](../test/OmnichainDeployerAttacks.t.sol) for safety properties.
27
27
  - [`test/OmnichainDeployerReentrancy.t.sol`](../test/OmnichainDeployerReentrancy.t.sol) for wrapper security assumptions.
28
- - [`test/OmnichainDeployerEdgeCases.t.sol`](../test/OmnichainDeployerEdgeCases.t.sol), [`test/JBOmnichainDeployer.t.sol`](../test/JBOmnichainDeployer.t.sol), and [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for edge behavior.
28
+ - [`test/OmnichainDeployerEdgeCases.t.sol`](../test/OmnichainDeployerEdgeCases.t.sol), [`test/JBOmnichainDeployer.t.sol`](../test/JBOmnichainDeployer.t.sol), and [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) for edge behavior.
@@ -55,24 +55,26 @@ contract JBOmnichainDeployer is
55
55
  //*********************************************************************//
56
56
 
57
57
  /// @notice Thrown when the provided controller does not match the project's controller in the directory.
58
- error JBOmnichainDeployer_ControllerMismatch();
58
+ error JBOmnichainDeployer_ControllerMismatch(
59
+ uint256 projectId, address expectedController, address actualController
60
+ );
59
61
 
60
- /// @notice Thrown when a data hook is set to this contract.
61
- error JBOmnichainDeployer_InvalidHook();
62
+ /// @notice Thrown when a data hook is invalid for the project ruleset being configured.
63
+ error JBOmnichainDeployer_InvalidHook(address hook, uint256 projectId, uint256 rulesetId);
62
64
 
63
65
  /// @notice Thrown when an empty `rulesetConfigurations` array is passed to a simplified overload that needs at
64
66
  /// least one ruleset to derive a default 721 config.
65
- error JBOmnichainDeployer_NoRulesetConfigurations();
66
-
67
- /// @notice Thrown when the project ID returned by the controller does not match the expected project ID.
68
- error JBOmnichainDeployer_ProjectIdMismatch();
67
+ error JBOmnichainDeployer_NoRulesetConfigurations(uint256 rulesetConfigurationCount);
69
68
 
70
69
  /// @notice Thrown when queueing rulesets for a project whose latest ruleset was already queued in the same block.
71
70
  /// @dev Ruleset IDs are predicted as `block.timestamp + i`. This prediction fails if
72
71
  /// `latestRulesetIdOf >= block.timestamp`, which can only happen if rulesets were already queued in the same block.
73
- error JBOmnichainDeployer_RulesetIdsUnpredictable();
72
+ error JBOmnichainDeployer_RulesetIdsUnpredictable(
73
+ uint256 projectId, uint256 latestRulesetId, uint256 currentTimestamp
74
+ );
74
75
 
75
- error JBOmnichainDeployer_UnexpectedNFTReceived();
76
+ /// @notice Thrown when this contract receives a project NFT from an unexpected sender or transfer.
77
+ error JBOmnichainDeployer_UnexpectedNFTReceived(address caller, address from, uint256 tokenId);
76
78
 
77
79
  //*********************************************************************//
78
80
  // --------------- public immutable stored properties ---------------- //
@@ -172,7 +174,6 @@ contract JBOmnichainDeployer is
172
174
  // Deploy the suckers.
173
175
  // Note: the salt includes `_msgSender()` for replay protection. Cross-chain deterministic
174
176
  // address matching requires using the same sender address on each chain.
175
- // slither-disable-next-line unused-return
176
177
  suckers = SUCKER_REGISTRY.deploySuckersFor({
177
178
  projectId: projectId,
178
179
  salt: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
@@ -326,10 +327,10 @@ contract JBOmnichainDeployer is
326
327
  }
327
328
 
328
329
  /// @dev Make sure this contract can only receive project NFTs minted from `JBProjects` (not transferred).
329
- function onERC721Received(address, address from, uint256, bytes calldata) external view returns (bytes4) {
330
+ function onERC721Received(address, address from, uint256 tokenId, bytes calldata) external view returns (bytes4) {
330
331
  // Only accept mints (from == address(0)) from the `JBProjects` contract, not arbitrary transfers.
331
332
  if (msg.sender != address(PROJECTS) || from != address(0)) {
332
- revert JBOmnichainDeployer_UnexpectedNFTReceived();
333
+ revert JBOmnichainDeployer_UnexpectedNFTReceived({caller: msg.sender, from: from, tokenId: tokenId});
333
334
  }
334
335
 
335
336
  return IERC721Receiver.onERC721Received.selector;
@@ -434,18 +435,19 @@ contract JBOmnichainDeployer is
434
435
  cashOutTaxRate = context.cashOutTaxRate;
435
436
  cashOutCount = context.cashOutCount;
436
437
 
437
- // Compute the cross-chain total supply: local supply + sum of known peer chain supplies.
438
- // This prevents the cash out tax from vanishing when a holder dominates the local supply.
439
- totalSupply = context.totalSupply + SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
438
+ // Start with local values.
439
+ totalSupply = context.totalSupply;
440
+ effectiveSurplusValue = context.surplus.value;
440
441
 
441
- // Compute the cross-chain surplus: local surplus + sum of known peer chain surpluses.
442
- // This prevents disproportionate reclaim when tokens bridge away but surplus stays.
443
- effectiveSurplusValue = context.surplus.value
444
- + SUCKER_REGISTRY.remoteSurplusOf({
442
+ // If the ruleset aggregates cross-chain state, add remote supply and surplus.
443
+ if (!context.scopeCashOutsToLocalBalances) {
444
+ totalSupply += SUCKER_REGISTRY.remoteTotalSupplyOf(context.projectId);
445
+ effectiveSurplusValue += SUCKER_REGISTRY.remoteSurplusOf({
445
446
  projectId: context.projectId,
446
447
  decimals: context.surplus.decimals,
447
448
  currency: uint256(context.surplus.currency)
448
449
  });
450
+ }
449
451
 
450
452
  // Will hold the 721 hook's cash out specifications (always 0 or 1 element).
451
453
  JBCashOutHookSpecification[] memory tiered721HookSpecifications;
@@ -458,7 +460,6 @@ contract JBOmnichainDeployer is
458
460
  // Forward to the 721 hook. It may change the tax rate, count, and return hook specs.
459
461
  // Capture the 721 hook's totalSupply and effectiveSurplusValue — NFT cash-outs should use
460
462
  // local-only denominators so holders reclaim against local surplus, not omnichain surplus.
461
- // slither-disable-next-line unused-return
462
463
  (cashOutTaxRate, cashOutCount, totalSupply, effectiveSurplusValue, tiered721HookSpecifications) =
463
464
  IJBRulesetDataHook(address(tiered721Config.hook)).beforeCashOutRecordedWith(context);
464
465
  }
@@ -485,10 +486,8 @@ contract JBOmnichainDeployer is
485
486
  // (sum of tier prices), not fungible token counts. Letting the extra hook override
486
487
  // cashOutCount would corrupt NFT pricing in the bonding curve.
487
488
  if (address(tiered721Config.hook) != address(0) && tiered721Config.useDataHookForCashOut) {
488
- // slither-disable-next-line unused-return
489
489
  (cashOutTaxRate,,,, extraHookSpecifications) = extraHook.dataHook.beforeCashOutRecordedWith(hookContext);
490
490
  } else {
491
- // slither-disable-next-line unused-return
492
491
  (cashOutTaxRate, cashOutCount,,, extraHookSpecifications) =
493
492
  extraHook.dataHook.beforeCashOutRecordedWith(hookContext);
494
493
  }
@@ -554,7 +553,6 @@ contract JBOmnichainDeployer is
554
553
  has721Hook = true;
555
554
  // Call the 721 hook directly — useDataHookForPay is always true for 721 hooks.
556
555
  JBPayHookSpecification[] memory tiered721HookSpecs;
557
- // slither-disable-next-line unused-return
558
556
  (tiered721Weight, tiered721HookSpecs) =
559
557
  IJBRulesetDataHook(address(tiered721Config.hook)).beforePayRecordedWith(context);
560
558
  // The 721 hook returns a single spec (itself) whose amount is the total split amount.
@@ -752,7 +750,6 @@ contract JBOmnichainDeployer is
752
750
 
753
751
  // Deploy a 721 hook and set up rulesets.
754
752
  hook = _deploy721Hook({projectId: projectId, config: deploy721Config});
755
- // slither-disable-next-line reentrancy-benign
756
753
  rulesetConfigurations = _setup721({
757
754
  projectId: projectId,
758
755
  rulesetConfigurations: rulesetConfigurations,
@@ -761,7 +758,6 @@ contract JBOmnichainDeployer is
761
758
  });
762
759
 
763
760
  // Launch the rulesets for the reserved project.
764
- // slither-disable-start unused-return
765
761
  controller.launchRulesetsFor({
766
762
  projectId: projectId,
767
763
  projectUri: projectUri,
@@ -769,14 +765,12 @@ contract JBOmnichainDeployer is
769
765
  terminalConfigurations: terminalConfigurations,
770
766
  memo: memo
771
767
  });
772
- // slither-disable-end unused-return
773
768
 
774
769
  // Transfer the hook's ownership to the project (now that the project NFT has been minted).
775
770
  JBOwnable(address(hook)).transferOwnershipToProject(projectId);
776
771
 
777
772
  // Deploy the suckers (if applicable).
778
773
  if (suckerDeploymentConfiguration.salt != bytes32(0)) {
779
- // slither-disable-next-line unused-return
780
774
  suckers = SUCKER_REGISTRY.deploySuckersFor({
781
775
  projectId: projectId,
782
776
  salt: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
@@ -822,7 +816,6 @@ contract JBOmnichainDeployer is
822
816
  // Deploy a 721 hook, transfer its ownership to the project, and set up rulesets.
823
817
  hook = _deploy721Hook({projectId: projectId, config: deploy721Config});
824
818
  JBOwnable(address(hook)).transferOwnershipToProject(projectId);
825
- // slither-disable-next-line reentrancy-benign
826
819
  rulesetConfigurations = _setup721({
827
820
  projectId: projectId,
828
821
  rulesetConfigurations: rulesetConfigurations,
@@ -863,8 +856,11 @@ contract JBOmnichainDeployer is
863
856
  // `block.timestamp + i` ruleset ID prediction incorrect.
864
857
  uint256 latestRulesetId = controller.RULESETS().latestRulesetIdOf(projectId);
865
858
  // forge-lint: disable-next-line(block-timestamp)
866
- if (latestRulesetId >= block.timestamp) {
867
- revert JBOmnichainDeployer_RulesetIdsUnpredictable();
859
+ uint256 currentTimestamp = block.timestamp;
860
+ if (latestRulesetId >= currentTimestamp) {
861
+ revert JBOmnichainDeployer_RulesetIdsUnpredictable({
862
+ projectId: projectId, latestRulesetId: latestRulesetId, currentTimestamp: currentTimestamp
863
+ });
868
864
  }
869
865
 
870
866
  // Deploy a new 721 hook if tiers are provided, otherwise carry forward the existing hook.
@@ -901,12 +897,15 @@ contract JBOmnichainDeployer is
901
897
  hook = previousConfig.hook;
902
898
  // Revert if no hook exists to carry forward — this means no tiers were provided and
903
899
  // no previous ruleset had a 721 hook deployed through this contract.
904
- if (address(hook) == address(0)) revert JBOmnichainDeployer_InvalidHook();
900
+ if (address(hook) == address(0)) {
901
+ revert JBOmnichainDeployer_InvalidHook({
902
+ hook: address(hook), projectId: projectId, rulesetId: sourceRulesetId
903
+ });
904
+ }
905
905
  // Preserve the previous ruleset's cash-out flag when carrying forward.
906
906
  use721ForCashOut = previousConfig.useDataHookForCashOut;
907
907
  }
908
908
 
909
- // slither-disable-next-line reentrancy-benign
910
909
  rulesetConfigurations = _setup721({
911
910
  projectId: projectId,
912
911
  rulesetConfigurations: rulesetConfigurations,
@@ -940,19 +939,23 @@ contract JBOmnichainDeployer is
940
939
  returns (JBRulesetConfig[] memory)
941
940
  {
942
941
  for (uint256 i; i < rulesetConfigurations.length; i++) {
942
+ // forge-lint: disable-next-line(block-timestamp)
943
+ uint256 rulesetId = block.timestamp + i;
944
+
943
945
  // Validate no self-reference.
944
- if (rulesetConfigurations[i].metadata.dataHook == address(this)) revert JBOmnichainDeployer_InvalidHook();
946
+ if (rulesetConfigurations[i].metadata.dataHook == address(this)) {
947
+ revert JBOmnichainDeployer_InvalidHook({
948
+ hook: rulesetConfigurations[i].metadata.dataHook, projectId: projectId, rulesetId: rulesetId
949
+ });
950
+ }
945
951
 
946
952
  // Store the 721 hook config per-ruleset.
947
- // slither-disable-next-line reentrancy-benign
948
- // forge-lint: disable-next-line(block-timestamp)
949
- _tiered721HookOf[projectId][block.timestamp + i] =
953
+ _tiered721HookOf[projectId][rulesetId] =
950
954
  JBTiered721HookConfig({hook: hook721, useDataHookForCashOut: use721ForCashOut});
951
955
 
952
956
  // Store custom hook from metadata (same as _setup).
953
957
  if (rulesetConfigurations[i].metadata.dataHook != address(0)) {
954
- // forge-lint: disable-next-line(block-timestamp)
955
- _extraDataHookOf[projectId][block.timestamp + i] = JBDeployerHookConfig({
958
+ _extraDataHookOf[projectId][rulesetId] = JBDeployerHookConfig({
956
959
  dataHook: IJBRulesetDataHook(rulesetConfigurations[i].metadata.dataHook),
957
960
  useDataHookForPay: rulesetConfigurations[i].metadata.useDataHookForPay,
958
961
  useDataHookForCashOut: rulesetConfigurations[i].metadata.useDataHookForCashOut
@@ -986,7 +989,11 @@ contract JBOmnichainDeployer is
986
989
  pure
987
990
  returns (JBOmnichain721Config memory config)
988
991
  {
989
- if (rulesetConfigurations.length == 0) revert JBOmnichainDeployer_NoRulesetConfigurations();
992
+ if (rulesetConfigurations.length == 0) {
993
+ revert JBOmnichainDeployer_NoRulesetConfigurations({
994
+ rulesetConfigurationCount: rulesetConfigurations.length
995
+ });
996
+ }
990
997
  config.deployTiersHookConfig.tiersConfig.currency = rulesetConfigurations[0].metadata.baseCurrency;
991
998
  config.deployTiersHookConfig.tiersConfig.decimals = 18;
992
999
  }
@@ -1012,7 +1019,9 @@ contract JBOmnichainDeployer is
1012
1019
  address current = address(DIRECTORY.controllerOf(projectId));
1013
1020
  // Allow address(0) for fresh projects that haven't launched rulesets yet.
1014
1021
  if (current != address(0) && current != address(controller)) {
1015
- revert JBOmnichainDeployer_ControllerMismatch();
1022
+ revert JBOmnichainDeployer_ControllerMismatch({
1023
+ projectId: projectId, expectedController: current, actualController: address(controller)
1024
+ });
1016
1025
  }
1017
1026
  }
1018
1027
  }