@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 +1 -1
- package/package.json +3 -3
- package/references/operations.md +1 -1
- package/references/runtime.md +1 -1
- package/src/JBOmnichainDeployer.sol +49 -40
package/README.md
CHANGED
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.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.
|
|
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.
|
|
33
|
+
"@bananapus/suckers-v6": "0.0.37",
|
|
34
34
|
"@openzeppelin/contracts": "5.6.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
package/references/operations.md
CHANGED
|
@@ -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/
|
|
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.
|
package/references/runtime.md
CHANGED
|
@@ -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/
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
438
|
+
// Start with local values.
|
|
439
|
+
totalSupply = context.totalSupply;
|
|
440
|
+
effectiveSurplusValue = context.surplus.value;
|
|
440
441
|
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
867
|
-
|
|
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))
|
|
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))
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
}
|