@bananapus/omnichain-deployers-v6 0.0.19 → 0.0.21
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/ADMINISTRATION.md +30 -2
- package/ARCHITECTURE.md +48 -85
- package/AUDIT_INSTRUCTIONS.md +52 -366
- package/CHANGELOG.md +61 -0
- package/README.md +45 -189
- package/RISKS.md +23 -6
- package/SKILLS.md +27 -240
- package/STYLE_GUIDE.md +71 -19
- package/USER_JOURNEYS.md +45 -417
- package/package.json +4 -5
- package/references/operations.md +28 -0
- package/references/runtime.md +28 -0
- package/src/JBOmnichainDeployer.sol +7 -2
- package/src/interfaces/IJBOmnichainDeployer.sol +3 -0
- package/test/JBOmnichainDeployer.t.sol +23 -0
- package/test/OmnichainDeployerEdgeCases.t.sol +10 -1
- package/test/TestAuditGaps.sol +23 -0
- package/test/audit/CarryForwardRejectedHook.t.sol +305 -0
- package/test/audit/JBOmnichainDeployer.t.sol +10 -0
- package/test/fork/OmnichainForkTestBase.sol +10 -7
- package/test/fork/TestOmnichain721QueueAndAdjust.t.sol +28 -21
- package/CHANGE_LOG.md +0 -341
- package/assets/findings/nana-omnichain-deployers-v6-pashov-ai-audit-report-20260330-103536.md +0 -39
package/USER_JOURNEYS.md
CHANGED
|
@@ -1,441 +1,69 @@
|
|
|
1
|
-
#
|
|
1
|
+
# User Journeys
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Who This Repo Serves
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- teams launching Juicebox projects that should be cross-chain from day one
|
|
6
|
+
- deployers composing a tiered 721 hook with sucker support and optional extra hooks
|
|
7
|
+
- operators evolving rulesets without breaking sucker-safe wrapper behavior
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Journey 1: Launch A Project Across Chains In One Flow
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
**Starting state:** the project wants an initial Juicebox launch plus 721 hook plus sucker package instead of separate setup steps.
|
|
10
12
|
|
|
11
|
-
**
|
|
12
|
-
```solidity
|
|
13
|
-
JBOmnichainDeployer.launchProjectFor(
|
|
14
|
-
address owner,
|
|
15
|
-
string calldata projectUri,
|
|
16
|
-
JBOmnichain721Config calldata deploy721Config,
|
|
17
|
-
JBRulesetConfig[] memory rulesetConfigurations,
|
|
18
|
-
JBTerminalConfig[] calldata terminalConfigurations,
|
|
19
|
-
string calldata memo,
|
|
20
|
-
JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
|
|
21
|
-
IJBController controller
|
|
22
|
-
) external returns (uint256 projectId, IJB721TiersHook hook, address[] memory suckers)
|
|
23
|
-
```
|
|
13
|
+
**Success:** the project launches with its omnichain-capable shape already installed.
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
**Flow**
|
|
16
|
+
1. Call `JBOmnichainDeployer` with the project launch config, 721 config, sucker deployment config, and any extra hook composition.
|
|
17
|
+
2. The deployer launches the base Juicebox project and either deploys or wires the tiered 721 hook.
|
|
18
|
+
3. It wraps the hook composition so future rulesets can keep sucker-safe behavior while still preserving project-specific hook logic.
|
|
19
|
+
4. Deterministic salts are used so sibling-chain deployments can be coordinated with confidence.
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
## Journey 2: Coordinate Deterministic Deployment Inputs Across Chains
|
|
28
22
|
|
|
29
|
-
**
|
|
23
|
+
**Starting state:** multiple chain deployments need to line up so the same project shape exists everywhere.
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|-----------|-------|
|
|
33
|
-
| `owner` | Final recipient of the project NFT. Not the caller. |
|
|
34
|
-
| `projectUri` | Metadata URI (IPFS hash, etc). |
|
|
35
|
-
| `deploy721Config.deployTiersHookConfig` | Tier definitions, currency, name, symbol. Empty tiers array is valid -- a 721 hook is still deployed. |
|
|
36
|
-
| `deploy721Config.useDataHookForCashOut` | Whether the 721 hook handles cash-outs. Set `false` for fungible-only projects (721 hook reverts on fungible cash-outs). |
|
|
37
|
-
| `deploy721Config.salt` | For CREATE2 deterministic hook deployment. `bytes32(0)` disables determinism. Combined with `_msgSender()` internally. |
|
|
38
|
-
| `rulesetConfigurations` | Array of ruleset configs. Each can specify a custom `metadata.dataHook` (e.g. buyback hook). The deployer strips this and replaces it with itself. |
|
|
39
|
-
| `rulesetConfigurations[i].metadata.dataHook` | Custom hook address (buyback, etc). Stored in `_extraDataHookOf`. Set `address(0)` for none. Must NOT be `address(deployer)`. |
|
|
40
|
-
| `rulesetConfigurations[i].metadata.useDataHookForPay` | Stored alongside the custom hook. Determines if custom hook is called on payments. |
|
|
41
|
-
| `rulesetConfigurations[i].metadata.useDataHookForCashOut` | Stored alongside the custom hook. Determines if custom hook is called on cash-outs. |
|
|
42
|
-
| `terminalConfigurations` | Terminal + accounting contexts. At least one terminal with native token context for ETH projects. |
|
|
43
|
-
| `suckerDeploymentConfiguration.salt` | `bytes32(0)` to skip sucker deployment. Non-zero triggers `SUCKER_REGISTRY.deploySuckersFor`. |
|
|
44
|
-
| `suckerDeploymentConfiguration.deployerConfigurations` | Array of `JBSuckerDeployerConfig` -- bridge-specific configs for each target chain. |
|
|
45
|
-
| `controller` | The JBController to use. Not validated (project doesn't exist yet). Must be a legitimate controller that is `isAllowedToSetFirstController` in the directory. |
|
|
25
|
+
**Success:** teams can predict addresses, salts, and wrapper behavior before doing the live rollout.
|
|
46
26
|
|
|
47
|
-
**
|
|
27
|
+
**Flow**
|
|
28
|
+
1. Fix the deployer inputs that drive deterministic addresses for suckers and hook packaging.
|
|
29
|
+
2. Reuse those inputs consistently on each chain.
|
|
30
|
+
3. Validate that the controller, hook ownership, and sucker expectations still line up across the resulting deployments.
|
|
48
31
|
|
|
49
|
-
|
|
50
|
-
2. For each ruleset `i`:
|
|
51
|
-
- `_tiered721HookOf[projectId][block.timestamp + i]` = 721 hook + cashout flag.
|
|
52
|
-
- `_extraDataHookOf[projectId][block.timestamp + i]` = custom hook + flags (if `metadata.dataHook != address(0)`).
|
|
53
|
-
- `rulesetConfigurations[i].metadata.dataHook` rewritten to `address(deployer)`, `useDataHookForPay` and `useDataHookForCashOut` forced to `true`.
|
|
54
|
-
3. `controller.launchProjectFor(address(this), projectUri, rulesetConfigurations, terminalConfigurations, memo)` -- project NFT minted to deployer; rulesets queued; terminals set.
|
|
55
|
-
4. `JBOwnable(hook).transferOwnershipToProject(projectId)` -- hook now owned by project.
|
|
56
|
-
5. `SUCKER_REGISTRY.deploySuckersFor(projectId, salt, configurations)` -- suckers deployed (if `suckerDeploymentConfiguration.salt != bytes32(0)`).
|
|
57
|
-
6. `PROJECTS.transferFrom(address(this), owner, projectId)` -- project NFT transferred to intended owner.
|
|
32
|
+
## Journey 3: Carry Forward An Existing 721 Hook While Queueing New Omnichain Rulesets
|
|
58
33
|
|
|
59
|
-
**
|
|
34
|
+
**Starting state:** the project already has a 721 hook and wants future omnichain-aware rulesets without replacing that collection.
|
|
60
35
|
|
|
61
|
-
|
|
62
|
-
- `IJBProjects.Create(projectId, owner, caller)` -- from step 3 (project NFT mint)
|
|
63
|
-
- `IJBDirectory.SetController(projectId, controller, caller)` -- from step 3
|
|
64
|
-
- `IJBDirectory.SetTerminals(projectId, terminals, caller)` -- from step 3
|
|
65
|
-
- `IJBRulesets.RulesetQueued(rulesetId, projectId, duration, weight, weightCutPercent, approvalHook, metadata, mustStartAtOrAfter, caller)` -- one per ruleset, from step 3
|
|
66
|
-
- `IJBController.LaunchProject(rulesetId, projectId, projectUri, memo, caller)` -- from step 3
|
|
67
|
-
- `IJBOwnable.OwnershipTransferred(previousOwner, newOwner, caller)` -- from step 4
|
|
68
|
-
- `IJBSuckerRegistry.SuckerDeployedFor(projectId, sucker, configuration, caller)` -- one per sucker, from step 5 (if salt non-zero)
|
|
69
|
-
- `IERC721.Transfer(from, to, tokenId)` -- from step 6 (project NFT transfer to owner)
|
|
36
|
+
**Success:** the deployer carries the existing hook forward, stores it against the queued ruleset, and preserves the ownership and wrapper assumptions bridge flows need.
|
|
70
37
|
|
|
71
|
-
**
|
|
38
|
+
**Flow**
|
|
39
|
+
1. Queue the next ruleset through the deployer using the path that reuses the existing 721 hook.
|
|
40
|
+
2. Validate the controller and queued ruleset inputs before relying on the result.
|
|
41
|
+
3. Let the deployer transfer or confirm hook ownership as needed so future project-controlled behavior still resolves correctly.
|
|
42
|
+
4. Confirm the queued ruleset now points at the carried-forward hook rather than accidentally dropping the 721 layer.
|
|
72
43
|
|
|
73
|
-
|
|
74
|
-
- **Custom hook set to `address(this)`**: Reverts with `JBOmnichainDeployer_InvalidHook`.
|
|
75
|
-
- **Controller returns wrong project ID**: Reverts with `JBOmnichainDeployer_ProjectIdMismatch`. This can happen if another project is created in the same transaction before this call.
|
|
76
|
-
- **721 hook deployment with `salt == bytes32(0)`**: Deploys non-deterministically. Cross-chain address matching will fail.
|
|
77
|
-
- **Multiple rulesets**: Ruleset IDs are `block.timestamp`, `block.timestamp + 1`, etc. Each gets its own hook mapping entry. If the block timestamp is very large, `block.timestamp + i` could theoretically overflow (practically impossible with Solidity 0.8 checks).
|
|
44
|
+
## Journey 4: Compose A Tiered 721 Hook With A Custom Extra Hook
|
|
78
45
|
|
|
79
|
-
|
|
46
|
+
**Starting state:** the project wants standard tiered NFTs plus some extra product logic.
|
|
80
47
|
|
|
81
|
-
|
|
48
|
+
**Success:** the extra logic composes with the 721 hook and sucker wrapper instead of overriding them unsafely.
|
|
82
49
|
|
|
83
|
-
**
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
JBOmnichain721Config memory deploy721Config,
|
|
88
|
-
JBRulesetConfig[] memory rulesetConfigurations,
|
|
89
|
-
JBTerminalConfig[] calldata terminalConfigurations,
|
|
90
|
-
string calldata memo,
|
|
91
|
-
IJBController controller
|
|
92
|
-
) external returns (uint256 rulesetId, IJB721TiersHook hook)
|
|
93
|
-
```
|
|
50
|
+
**Flow**
|
|
51
|
+
1. Provide the extra-hook config when launching through `JBOmnichainDeployer`.
|
|
52
|
+
2. Let the deployer remember which composition belongs to each ruleset.
|
|
53
|
+
3. Make sure bridge flows still bypass or special-case the right tax and data-hook behavior.
|
|
94
54
|
|
|
95
|
-
|
|
55
|
+
## Journey 5: Evolve The Project After Launch Without Breaking Bridge Paths
|
|
96
56
|
|
|
97
|
-
**
|
|
57
|
+
**Starting state:** the project is live and future rulesets need new metadata, hook composition, or payout behavior.
|
|
98
58
|
|
|
99
|
-
**
|
|
59
|
+
**Success:** ruleset changes preserve the special wrapper assumptions that let suckers bridge cleanly.
|
|
100
60
|
|
|
101
|
-
|
|
61
|
+
**Flow**
|
|
62
|
+
1. Queue the next ruleset through the omnichain-aware deployer surface.
|
|
63
|
+
2. Keep track of which hook stack should apply for that future ruleset.
|
|
64
|
+
3. Confirm that sucker flows still land on the mint-safe and tax-safe path the wrapper was designed to preserve.
|
|
102
65
|
|
|
103
|
-
|
|
66
|
+
## Hand-Offs
|
|
104
67
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
3. `_validateController(projectId, controller)` -- confirms `controller.DIRECTORY().controllerOf(projectId) == controller`.
|
|
108
|
-
4. `HOOK_DEPLOYER.deployHookFor(projectId, config, salt)` -- new 721 hook deployed.
|
|
109
|
-
5. `JBOwnable(hook).transferOwnershipToProject(projectId)` -- hook ownership transferred immediately (project already exists).
|
|
110
|
-
6. For each ruleset `i`:
|
|
111
|
-
- `_tiered721HookOf[projectId][block.timestamp + i]` = 721 hook + cashout flag.
|
|
112
|
-
- `_extraDataHookOf[projectId][block.timestamp + i]` = custom hook + flags (if specified).
|
|
113
|
-
- `rulesetConfigurations[i].metadata.dataHook` rewritten to `address(deployer)`.
|
|
114
|
-
7. `controller.launchRulesetsFor(projectId, rulesetConfigurations, terminalConfigurations, memo)` -- rulesets launched; terminals set.
|
|
115
|
-
|
|
116
|
-
**Events** (emitted by downstream contracts):
|
|
117
|
-
|
|
118
|
-
- `IJB721TiersHookDeployer.HookDeployed(projectId, hook, caller)` -- from step 4
|
|
119
|
-
- `IJBOwnable.OwnershipTransferred(previousOwner, newOwner, caller)` -- from step 5
|
|
120
|
-
- `IJBDirectory.SetTerminals(projectId, terminals, caller)` -- from step 7
|
|
121
|
-
- `IJBRulesets.RulesetQueued(rulesetId, projectId, duration, weight, weightCutPercent, approvalHook, metadata, mustStartAtOrAfter, caller)` -- one per ruleset, from step 7
|
|
122
|
-
- `IJBController.LaunchRulesets(rulesetId, projectId, memo, caller)` -- from step 7
|
|
123
|
-
|
|
124
|
-
**Edge cases**:
|
|
125
|
-
|
|
126
|
-
- **No ruleset ID prediction guard**: Unlike `queueRulesetsOf`, there is no check for `latestRulesetId >= block.timestamp`. If another ruleset operation happened in the same block, the predicted IDs may be wrong and the stored hooks will be keyed incorrectly.
|
|
127
|
-
- **Controller mismatch**: Reverts with `JBOmnichainDeployer_ControllerMismatch` immediately before any state changes.
|
|
128
|
-
- **Insufficient permissions**: Reverts on the first failed permission check. Note: both `LAUNCH_RULESETS` AND `SET_TERMINALS` are required -- having only one is not enough.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Journey 3: Queue Rulesets for an Existing Project
|
|
133
|
-
|
|
134
|
-
**Entry point**:
|
|
135
|
-
```solidity
|
|
136
|
-
JBOmnichainDeployer.queueRulesetsOf(
|
|
137
|
-
uint256 projectId,
|
|
138
|
-
JBOmnichain721Config memory deploy721Config,
|
|
139
|
-
JBRulesetConfig[] memory rulesetConfigurations,
|
|
140
|
-
string calldata memo,
|
|
141
|
-
IJBController controller
|
|
142
|
-
) external returns (uint256 rulesetId, IJB721TiersHook hook)
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
There is a simplified overload that omits `deploy721Config` and derives a default.
|
|
146
|
-
|
|
147
|
-
**Who can call**: Project owner or address with `QUEUE_RULESETS` (permission ID 2) for the project.
|
|
148
|
-
|
|
149
|
-
**Parameters**:
|
|
150
|
-
|
|
151
|
-
Same as `launchRulesetsFor` but without `terminalConfigurations`. Only requires `QUEUE_RULESETS` (not `SET_TERMINALS`).
|
|
152
|
-
|
|
153
|
-
**State changes**:
|
|
154
|
-
|
|
155
|
-
1. `_requirePermissionFrom(owner, projectId, QUEUE_RULESETS)` -- permission check.
|
|
156
|
-
2. `_validateController(projectId, controller)` -- confirms controller matches directory.
|
|
157
|
-
3. Ruleset ID prediction guard:
|
|
158
|
-
```solidity
|
|
159
|
-
uint256 latestRulesetId = controller.RULESETS().latestRulesetIdOf(projectId);
|
|
160
|
-
if (latestRulesetId >= block.timestamp) revert JBOmnichainDeployer_RulesetIdsUnpredictable();
|
|
161
|
-
```
|
|
162
|
-
4. If `deploy721Config.deployTiersHookConfig.tiersConfig.tiers.length > 0`:
|
|
163
|
-
- `HOOK_DEPLOYER.deployHookFor(projectId, config, salt)` -- new hook deployed.
|
|
164
|
-
- `JBOwnable(hook).transferOwnershipToProject(projectId)` -- hook ownership transferred.
|
|
165
|
-
Otherwise:
|
|
166
|
-
- `hook = _tiered721HookOf[projectId][latestRulesetId].hook` -- carry forward existing hook.
|
|
167
|
-
- Reverts with `JBOmnichainDeployer_InvalidHook` if `address(hook) == address(0)`.
|
|
168
|
-
5. For each ruleset `i`:
|
|
169
|
-
- `_tiered721HookOf[projectId][block.timestamp + i]` = hook + cashout flag.
|
|
170
|
-
- `_extraDataHookOf[projectId][block.timestamp + i]` = custom hook + flags (if specified).
|
|
171
|
-
- `rulesetConfigurations[i].metadata.dataHook` rewritten to `address(deployer)`.
|
|
172
|
-
6. `controller.queueRulesetsOf(projectId, rulesetConfigurations, memo)` -- rulesets queued.
|
|
173
|
-
|
|
174
|
-
**Events** (emitted by downstream contracts):
|
|
175
|
-
|
|
176
|
-
- `IJB721TiersHookDeployer.HookDeployed(projectId, hook, caller)` -- from step 4 (only if new tiers deployed)
|
|
177
|
-
- `IJBOwnable.OwnershipTransferred(previousOwner, newOwner, caller)` -- from step 4 (only if new tiers deployed)
|
|
178
|
-
- `IJBRulesets.RulesetQueued(rulesetId, projectId, duration, weight, weightCutPercent, approvalHook, metadata, mustStartAtOrAfter, caller)` -- one per ruleset, from step 6
|
|
179
|
-
- `IJBController.QueueRulesets(rulesetId, projectId, memo, caller)` -- from step 6
|
|
180
|
-
|
|
181
|
-
**Edge cases**:
|
|
182
|
-
|
|
183
|
-
- **Same-block queue**: Reverts with `JBOmnichainDeployer_RulesetIdsUnpredictable`. This happens if:
|
|
184
|
-
- The project was launched in the same block.
|
|
185
|
-
- Another `queueRulesetsOf` was called in the same block (via deployer or directly on controller).
|
|
186
|
-
- Multiple rulesets were launched causing `latestRulesetId = block.timestamp + N` where `N >= 0`.
|
|
187
|
-
- **Carry-forward with no previous hook**: If `_tiered721HookOf[projectId][latestRulesetId]` was never set (project created outside the deployer), the call reverts with `JBOmnichainDeployer_InvalidHook`. This prevents storing a zero-address 721 hook.
|
|
188
|
-
- **Carry-forward with stale hook**: If the project owner previously queued rulesets with a different 721 hook via the deployer, the carry-forward uses the hook from the LATEST ruleset, not the currently active one. Verify `latestRulesetIdOf` returns the most recently queued (not necessarily active) ruleset.
|
|
189
|
-
- **Simplified overload**: The default config has 0 tiers, so the hook is always carried forward.
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Journey 4: Deploy Suckers for an Existing Project
|
|
194
|
-
|
|
195
|
-
**Entry point**:
|
|
196
|
-
```solidity
|
|
197
|
-
JBOmnichainDeployer.deploySuckersFor(
|
|
198
|
-
uint256 projectId,
|
|
199
|
-
JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration
|
|
200
|
-
) external returns (address[] memory suckers)
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**Who can call**: Project owner or address with `DEPLOY_SUCKERS` (permission ID 31) for the project.
|
|
204
|
-
|
|
205
|
-
**Parameters**:
|
|
206
|
-
|
|
207
|
-
| Parameter | Notes |
|
|
208
|
-
|-----------|-------|
|
|
209
|
-
| `projectId` | Must be an existing project. |
|
|
210
|
-
| `suckerDeploymentConfiguration.salt` | Combined with `_msgSender()` for deterministic deployment. |
|
|
211
|
-
| `suckerDeploymentConfiguration.deployerConfigurations` | Array of `JBSuckerDeployerConfig` -- bridge-specific sucker configs. |
|
|
212
|
-
|
|
213
|
-
**State changes**:
|
|
214
|
-
|
|
215
|
-
1. `_requirePermissionFrom(owner, projectId, DEPLOY_SUCKERS)` -- permission check.
|
|
216
|
-
2. `SUCKER_REGISTRY.deploySuckersFor(projectId, keccak256(abi.encode(salt, _msgSender())), configurations)` -- suckers deployed.
|
|
217
|
-
|
|
218
|
-
**Events** (emitted by downstream contracts):
|
|
219
|
-
|
|
220
|
-
- `IJBSuckerRegistry.SuckerDeployedFor(projectId, sucker, configuration, caller)` -- one per sucker, from step 2
|
|
221
|
-
|
|
222
|
-
**Edge cases**:
|
|
223
|
-
|
|
224
|
-
- **Salt includes `_msgSender()`**: The same salt from different senders produces different sucker addresses. For cross-chain deterministic addresses, the same sender must deploy on each chain.
|
|
225
|
-
- **Empty deployerConfigurations**: Behavior depends on the sucker registry implementation.
|
|
226
|
-
- **Re-deployment with same salt**: Will revert at the CREATE2 level (address collision).
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## Journey 5: Payment Through the Data Hook Proxy
|
|
231
|
-
|
|
232
|
-
**Entry point** (called by `JBMultiTerminal` during `pay()`, not directly by users):
|
|
233
|
-
```solidity
|
|
234
|
-
JBOmnichainDeployer.beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
|
|
235
|
-
external view returns (uint256 weight, JBPayHookSpecification[] memory hookSpecifications)
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
**Who can call**: Any address. This is a `view` function called by the terminal during payment processing. It does not modify state.
|
|
239
|
-
|
|
240
|
-
**Parameters**:
|
|
241
|
-
- `context` -- Standard `JBBeforePayRecordedContext` struct populated by the terminal. Includes `projectId`, `rulesetId`, `amount`, `weight`, and payment metadata.
|
|
242
|
-
|
|
243
|
-
**Flow**:
|
|
244
|
-
|
|
245
|
-
1. **721 hook check**: Load `_tiered721HookOf[context.projectId][context.rulesetId]`. If non-zero, call its `beforePayRecordedWith(context)`. Extract the first spec as the 721 split amount.
|
|
246
|
-
2. **Compute project amount**: `projectAmount = context.amount.value - totalSplitAmount` (floored at 0).
|
|
247
|
-
3. **Custom hook check**: Load `_extraDataHookOf[context.projectId][context.rulesetId]`. If non-zero and `useDataHookForPay == true`, call its `beforePayRecordedWith` with `amount.value = projectAmount`.
|
|
248
|
-
4. **Weight resolution**:
|
|
249
|
-
- Custom hook called: `weight` from custom hook, then scaled by `projectAmount / context.amount.value`.
|
|
250
|
-
- Custom hook not called: `weight = context.weight`, then scaled.
|
|
251
|
-
- If `projectAmount == 0`: `weight = 0` regardless.
|
|
252
|
-
5. **Spec merging**: 721 spec (if any) first, then custom hook specs.
|
|
253
|
-
|
|
254
|
-
**Events**: None. This is a `view` function.
|
|
255
|
-
|
|
256
|
-
**Edge cases**:
|
|
257
|
-
|
|
258
|
-
- **No hooks stored for this ruleset**: Returns `(context.weight, [])`. This happens when the project was not created through the deployer, or the ruleset ID does not match any stored mapping.
|
|
259
|
-
- **721 hook returns empty specs**: `hasTiered721Spec = false`, `totalSplitAmount = 0`. Custom hook sees full amount.
|
|
260
|
-
- **721 hook returns multiple specs**: Only `tiered721HookSpecs[0]` is used. The 721 hook contract always returns exactly one spec (itself), so this is not a practical concern.
|
|
261
|
-
- **Custom hook returns weight=0**: After scaling, `weight = 0`. This is the buyback hook's "swap path" -- it returns `weight = 0` when routing through the AMM.
|
|
262
|
-
- **Full split (721 takes entire payment)**: `projectAmount = 0`, custom hook sees `amount.value = 0`, `weight = 0`. All funds go to the 721 hook.
|
|
263
|
-
- **Overflow in mulDiv**: PRB math's `mulDiv` handles `type(uint256).max` correctly. Tested explicitly.
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## Journey 6: Cash-Out Through the Data Hook Proxy
|
|
268
|
-
|
|
269
|
-
**Entry point** (called by `JBMultiTerminal` during `cashOutTokensOf()`, not directly by users):
|
|
270
|
-
```solidity
|
|
271
|
-
JBOmnichainDeployer.beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
|
|
272
|
-
external view returns (uint256 cashOutTaxRate, uint256 cashOutCount, uint256 totalSupply, JBCashOutHookSpecification[] memory)
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
**Who can call**: Any address. This is a `view` function called by the terminal during cash-out processing. It does not modify state.
|
|
276
|
-
|
|
277
|
-
**Parameters**:
|
|
278
|
-
- `context` -- Standard `JBBeforeCashOutRecordedContext` struct populated by the terminal. Includes `projectId`, `rulesetId`, `holder`, `cashOutCount`, `totalSupply`, `cashOutTaxRate`, and metadata.
|
|
279
|
-
|
|
280
|
-
**Flow (sequential composition)**:
|
|
281
|
-
|
|
282
|
-
1. **Sucker check**: `SUCKER_REGISTRY.isSuckerOf(context.projectId, context.holder)`. If true: return `(0, context.cashOutCount, context.totalSupply, [])`. Sucker gets full pro-rata reclaim, 0% tax.
|
|
283
|
-
2. **Initialize values** from `context`: `cashOutTaxRate`, `cashOutCount`, `totalSupply`.
|
|
284
|
-
3. **721 hook**: If stored and `useDataHookForCashOut == true`, call `tiered721Config.hook.beforeCashOutRecordedWith(context)`. Updates `cashOutTaxRate`, `cashOutCount`, `totalSupply`, and stores any returned hook specifications (always 0 or 1).
|
|
285
|
-
4. **Custom hook**: If stored and `useDataHookForCashOut == true`, call `extraHook.dataHook.beforeCashOutRecordedWith(context)` with the already-updated values from the 721 hook. Further updates `cashOutTaxRate`, `cashOutCount`, `totalSupply`, and stores any returned hook specifications.
|
|
286
|
-
5. **Merge specs**: If either hook returned specifications, merge them (721 specs first, then custom hook specs) and return.
|
|
287
|
-
6. **Fallback**: If neither hook returned specs, return the adjusted values with no hook specs.
|
|
288
|
-
|
|
289
|
-
**Events**: None. This is a `view` function.
|
|
290
|
-
|
|
291
|
-
**Edge cases**:
|
|
292
|
-
|
|
293
|
-
- **Sucker with reverting 721 hook**: The sucker check is first, so the 721 hook is never called. The sucker always gets 0% tax even if the 721 hook would revert.
|
|
294
|
-
- **Non-sucker with 721 `useDataHookForCashOut = true`**: The 721 hook is called. For fungible cash-outs, the 721 hook typically reverts with `JB721Hook_UnexpectedTokenCashedOut()`. This revert propagates -- the non-sucker cannot cash out fungible tokens. Set `useDataHookForCashOut = false` in the `deploy721Config` to avoid this.
|
|
295
|
-
- **Both 721 and custom hooks have `useDataHookForCashOut = true`**: Both hooks are called sequentially. The 721 hook is called first, and the custom hook receives the already-updated values. Their specifications are merged.
|
|
296
|
-
- **Neither hook has `useDataHookForCashOut = true`**: Falls through to the original values. The ruleset's `cashOutTaxRate` applies as normal.
|
|
297
|
-
|
|
298
|
-
---
|
|
299
|
-
|
|
300
|
-
## Journey 7: Mint Permission Check
|
|
301
|
-
|
|
302
|
-
**Entry point** (called by `JBController` during token minting):
|
|
303
|
-
```solidity
|
|
304
|
-
JBOmnichainDeployer.hasMintPermissionFor(uint256 projectId, JBRuleset memory ruleset, address addr)
|
|
305
|
-
external view returns (bool)
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
**Who can call**: Any address. This is a `view` function called by the controller to check if an address has mint permission.
|
|
309
|
-
|
|
310
|
-
**Parameters**:
|
|
311
|
-
- `projectId` -- The project whose token minting permission is being checked.
|
|
312
|
-
- `ruleset` -- The current ruleset (used to look up stored hook configs by `ruleset.id`).
|
|
313
|
-
- `addr` -- The address being checked for mint permission.
|
|
314
|
-
|
|
315
|
-
**Flow**:
|
|
316
|
-
|
|
317
|
-
1. **Sucker check**: If `SUCKER_REGISTRY.isSuckerOf(projectId, addr)` returns true, return `true`.
|
|
318
|
-
2. **Custom hook check**: If `_extraDataHookOf[projectId][ruleset.id]` exists, delegate to its `hasMintPermissionFor`. If it returns true, return `true`.
|
|
319
|
-
3. **721 hook is NOT checked**: The 721 hook does not grant mint permission through this path.
|
|
320
|
-
4. **Default**: Return `false`.
|
|
321
|
-
|
|
322
|
-
**Events**: None. This is a `view` function.
|
|
323
|
-
|
|
324
|
-
**Edge cases**:
|
|
325
|
-
|
|
326
|
-
- **No stored hooks for this ruleset**: Only the sucker check applies. Non-suckers cannot mint.
|
|
327
|
-
- **Custom hook reverts**: The revert propagates. Mint permission check fails.
|
|
328
|
-
- **Sucker registered after project launch**: If the sucker registry is updated to include a new sucker, that address immediately gets mint permission for all omnichain projects (no per-project opt-in beyond the registry).
|
|
329
|
-
|
|
330
|
-
---
|
|
331
|
-
|
|
332
|
-
## Journey 8: Adversarial -- Reverting Custom Hook
|
|
333
|
-
|
|
334
|
-
### Scenario
|
|
335
|
-
A project is launched with a custom data hook that always reverts.
|
|
336
|
-
|
|
337
|
-
### Impact on Payments
|
|
338
|
-
`beforePayRecordedWith` calls the custom hook. If the custom hook reverts, the entire payment transaction reverts. The project cannot receive payments.
|
|
339
|
-
|
|
340
|
-
**Mitigation**: The project owner can queue new rulesets without the reverting hook (set `metadata.dataHook = address(0)` in new rulesets).
|
|
341
|
-
|
|
342
|
-
### Impact on Cash-Outs
|
|
343
|
-
If the custom hook has `useDataHookForCashOut = true` and the 721 hook does not handle cash-outs:
|
|
344
|
-
- Suckers: Unaffected. The sucker check returns before the hook is consulted.
|
|
345
|
-
- Non-suckers: Cash-outs revert. Tokens are locked until new rulesets are queued.
|
|
346
|
-
|
|
347
|
-
If the 721 hook has `useDataHookForCashOut = true`: The 721 hook is called first. If IT reverts, cash-outs fail regardless of the custom hook (the revert propagates before the custom hook is reached).
|
|
348
|
-
|
|
349
|
-
### Impact on Mint Permission
|
|
350
|
-
If the custom hook reverts on `hasMintPermissionFor`, the call propagates the revert. Suckers are unaffected (checked first). Non-suckers cannot get mint permission.
|
|
351
|
-
|
|
352
|
-
---
|
|
353
|
-
|
|
354
|
-
## Journey 9: Adversarial -- Rapid Ruleset Queueing
|
|
355
|
-
|
|
356
|
-
### Scenario
|
|
357
|
-
An attacker or automated system attempts to queue rulesets multiple times in the same block.
|
|
358
|
-
|
|
359
|
-
### Via the Deployer
|
|
360
|
-
The first call to `queueRulesetsOf` succeeds. The second call in the same block finds `latestRulesetId >= block.timestamp` and reverts with `JBOmnichainDeployer_RulesetIdsUnpredictable`.
|
|
361
|
-
|
|
362
|
-
### Via Controller Then Deployer (same block)
|
|
363
|
-
If rulesets are queued directly on the controller first (bypassing the deployer), then the deployer's `queueRulesetsOf` is called in the same block: the deployer detects the conflict and reverts.
|
|
364
|
-
|
|
365
|
-
### Via Deployer Then Controller
|
|
366
|
-
The deployer queues rulesets (storing hooks at `block.timestamp + i`). Then the controller queues more rulesets in the same tx. The controller's new rulesets get IDs starting from where the deployer left off. These new rulesets have `dataHook = address(deployer)` (from the deployer's `_setup721`), but the deployer has no hook mappings for these IDs (they were queued by the controller, not the deployer). Payments/cash-outs through these rulesets will hit the deployer's data hook proxy but find no stored hooks, returning default values (original weight, no 721 integration, no sucker bypass for cash-outs).
|
|
367
|
-
|
|
368
|
-
### Cross-Chain Timing
|
|
369
|
-
If the same `queueRulesetsOf` call is executed on two chains in different blocks, the stored hook mappings will be keyed to different `block.timestamp` values. This is expected -- ruleset IDs are chain-specific.
|
|
370
|
-
|
|
371
|
-
---
|
|
372
|
-
|
|
373
|
-
## Journey 10: Adversarial -- Fake Controller
|
|
374
|
-
|
|
375
|
-
### Scenario
|
|
376
|
-
An attacker provides a malicious controller address to `queueRulesetsOf` or `launchRulesetsFor`.
|
|
377
|
-
|
|
378
|
-
### Outcome
|
|
379
|
-
The `_validateController` function checks:
|
|
380
|
-
```solidity
|
|
381
|
-
address(controller.DIRECTORY().controllerOf(projectId)) != address(controller)
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
If the attacker's controller returns a different directory, or the directory returns a different controller, the check fails and the call reverts with `JBOmnichainDeployer_ControllerMismatch`.
|
|
385
|
-
|
|
386
|
-
A sophisticated attacker could deploy a controller that returns a directory where `controllerOf(projectId)` returns the attacker's controller. However, this would require the project to actually be using the attacker's controller in the canonical directory, which would mean the project is already compromised.
|
|
387
|
-
|
|
388
|
-
For `launchProjectFor`: There is no controller validation because the project doesn't exist yet. A malicious controller could:
|
|
389
|
-
1. Return a fake project ID -- caught by `JBOmnichainDeployer_ProjectIdMismatch`.
|
|
390
|
-
2. Mint the project NFT but configure it maliciously -- the deployer transfers ownership to the intended `owner`, who can reconfigure.
|
|
391
|
-
3. Not mint the project NFT at all -- the `PROJECTS.transferFrom` at the end would revert.
|
|
392
|
-
|
|
393
|
-
---
|
|
394
|
-
|
|
395
|
-
## Journey 11: Hook Ownership Lifecycle
|
|
396
|
-
|
|
397
|
-
### During `launchProjectFor`
|
|
398
|
-
1. `HOOK_DEPLOYER.deployHookFor()` -- hook is owned by `JBOmnichainDeployer`.
|
|
399
|
-
2. `controller.launchProjectFor(address(this), ...)` -- project NFT minted to deployer.
|
|
400
|
-
3. `JBOwnable(hook).transferOwnershipToProject(projectId)` -- hook now owned by project.
|
|
401
|
-
4. `PROJECTS.transferFrom(deployer, owner, projectId)` -- project (and transitively hook ownership) transferred to intended owner.
|
|
402
|
-
|
|
403
|
-
**Failure scenario**: If `controller.launchProjectFor` reverts, the 721 hook exists but the entire transaction reverts atomically. The hook bytecode is deployed but the tx effects are rolled back, so the hook is effectively orphaned at the EVM level (no state changes persist).
|
|
404
|
-
|
|
405
|
-
### During `queueRulesetsOf` (new tiers)
|
|
406
|
-
1. `_deploy721Hook()` -- hook owned by deployer.
|
|
407
|
-
2. `JBOwnable(hook).transferOwnershipToProject(projectId)` -- immediately transferred.
|
|
408
|
-
|
|
409
|
-
### During `queueRulesetsOf` (carry-forward)
|
|
410
|
-
No new hook deployment. The existing hook's ownership is unchanged.
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
## Journey 12: Default 721 Config Derivation
|
|
415
|
-
|
|
416
|
-
**Entry point**: Any simplified overload that omits `JBOmnichain721Config`.
|
|
417
|
-
|
|
418
|
-
**Who can call**: Same as the parent function being called (see Journeys 1, 2, 3).
|
|
419
|
-
|
|
420
|
-
**Logic**:
|
|
421
|
-
```solidity
|
|
422
|
-
function _default721Config(JBRulesetConfig[] memory rulesetConfigurations)
|
|
423
|
-
internal pure returns (JBOmnichain721Config memory config)
|
|
424
|
-
{
|
|
425
|
-
config.deployTiersHookConfig.tiersConfig.currency = rulesetConfigurations[0].metadata.baseCurrency;
|
|
426
|
-
config.deployTiersHookConfig.tiersConfig.decimals = 18;
|
|
427
|
-
}
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
- Currency from first ruleset's `baseCurrency`.
|
|
431
|
-
- 18 decimals hardcoded.
|
|
432
|
-
- Empty tiers array (0 tiers).
|
|
433
|
-
- `useDataHookForCashOut = false` (default).
|
|
434
|
-
- `salt = bytes32(0)` (non-deterministic deployment).
|
|
435
|
-
|
|
436
|
-
**Events**: None. This is an `internal pure` function.
|
|
437
|
-
|
|
438
|
-
**Edge cases**:
|
|
439
|
-
|
|
440
|
-
- **Empty rulesets array**: Reverts with `JBOmnichainDeployer_NoRulesetConfigurations`.
|
|
441
|
-
- **Non-18-decimal token**: The 721 hook will use 18 decimals regardless. This affects tier pricing if the project's accounting context uses a different precision.
|
|
68
|
+
- Use [nana-suckers-v6](../nana-suckers-v6/USER_JOURNEYS.md) for the bridge mechanics after the project has been launched with suckers.
|
|
69
|
+
- Use [nana-721-hook-v6](../nana-721-hook-v6/USER_JOURNEYS.md) for the standard tier and resolver behavior that this repo packages into the omnichain launch.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/omnichain-deployers-v6",
|
|
3
|
-
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,10 +17,9 @@
|
|
|
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/
|
|
22
|
-
"@bananapus/
|
|
23
|
-
"@bananapus/core-v6": "^0.0.30",
|
|
20
|
+
"@bananapus/721-hook-v6": "^0.0.30",
|
|
21
|
+
"@bananapus/buyback-hook-v6": "^0.0.25",
|
|
22
|
+
"@bananapus/core-v6": "^0.0.31",
|
|
24
23
|
"@bananapus/ownable-v6": "^0.0.16",
|
|
25
24
|
"@bananapus/permission-ids-v6": "^0.0.15",
|
|
26
25
|
"@bananapus/suckers-v6": "^0.0.20",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Omnichain Deployer Operations
|
|
2
|
+
|
|
3
|
+
## Deployment Surface
|
|
4
|
+
|
|
5
|
+
- [`src/JBOmnichainDeployer.sol`](../src/JBOmnichainDeployer.sol) is the first stop for launch, queue, and sucker-deployment behavior.
|
|
6
|
+
- [`script/Deploy.s.sol`](../script/Deploy.s.sol) is the deployment entry point when the question is about current wiring rather than wrapper semantics.
|
|
7
|
+
- [`src/structs/`](../src/structs/) defines the deploy and queue config types that often drift from memory.
|
|
8
|
+
|
|
9
|
+
## Change Checklist
|
|
10
|
+
|
|
11
|
+
- If you edit launch or queue behavior, verify ruleset IDs, carry-forward behavior, and stored hook config keys together.
|
|
12
|
+
- If you edit salt handling, confirm deterministic-address assumptions for both suckers and 721 hooks.
|
|
13
|
+
- If you edit wrapper behavior, check both pay and cash-out paths, not just one.
|
|
14
|
+
- If you touch mint-permission logic, confirm whether the permission should come from suckers, the extra hook, or neither.
|
|
15
|
+
|
|
16
|
+
## Common Failure Modes
|
|
17
|
+
|
|
18
|
+
- Wrapper behavior is blamed on the composed hook, but the deployer stored the wrong hook config for the ruleset.
|
|
19
|
+
- A same-block queue assumption breaks predicted ruleset IDs and silently strands stored config.
|
|
20
|
+
- A project expects custom-hook behavior on cash-out, but the wrapper flags disable it or the sucker exemption bypasses it.
|
|
21
|
+
- Deterministic deployment assumptions fail because sender or salt composition changed.
|
|
22
|
+
- Existing-project sucker deployment is treated as a pure `DEPLOY_SUCKERS` flow even though the registry also performs token mapping, so missing `MAP_SUCKER_TOKEN` authority for the registry shows up only when the deploy path reaches `mapTokens`.
|
|
23
|
+
|
|
24
|
+
## Useful Proof Points
|
|
25
|
+
|
|
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.
|
|
28
|
+
- [`test/fork/`](../test/fork/) when cross-repo integration behavior matters more than isolated unit logic.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Omnichain Deployer Runtime
|
|
2
|
+
|
|
3
|
+
## Contract Role
|
|
4
|
+
|
|
5
|
+
- [`src/JBOmnichainDeployer.sol`](../src/JBOmnichainDeployer.sol) launches projects, deploys suckers, installs a 721 hook, wraps extra hooks, and serves as the live ruleset data-hook wrapper for the projects it creates.
|
|
6
|
+
|
|
7
|
+
## Runtime Path
|
|
8
|
+
|
|
9
|
+
1. Project launch or ruleset queue stores the relevant 721 hook and optional extra hook per ruleset.
|
|
10
|
+
2. The deployer installs itself as the data-hook wrapper on the ruleset.
|
|
11
|
+
3. On pay, it calls the 721 hook first, then the optional extra hook with the adjusted amount context.
|
|
12
|
+
4. On cash out, it can short-circuit for suckers, otherwise it forwards into the configured hook stack in order.
|
|
13
|
+
5. Mint permission queries can be granted by suckers or by the configured extra hook.
|
|
14
|
+
|
|
15
|
+
## High-Risk Areas
|
|
16
|
+
|
|
17
|
+
- Ruleset ID prediction: if the predicted ID is wrong, hook config can be stored under the wrong key.
|
|
18
|
+
- Hook composition order: 721 logic runs before any extra hook, which affects both specs and accounting.
|
|
19
|
+
- Sucker exemptions: early-return cash-out behavior is intentional and should not be removed casually.
|
|
20
|
+
- Carry-forward logic: queueing rulesets without new tiers intentionally reuses the latest 721 hook.
|
|
21
|
+
- Meta-transaction sender handling: salt derivation uses `_msgSender()`, not raw `msg.sender`.
|
|
22
|
+
|
|
23
|
+
## Tests To Trust First
|
|
24
|
+
|
|
25
|
+
- [`test/Tiered721HookComposition.t.sol`](../test/Tiered721HookComposition.t.sol) for hook-composition behavior.
|
|
26
|
+
- [`test/JBOmnichainDeployerGuard.t.sol`](../test/JBOmnichainDeployerGuard.t.sol) and [`test/OmnichainDeployerAttacks.t.sol`](../test/OmnichainDeployerAttacks.t.sol) for safety properties.
|
|
27
|
+
- [`test/OmnichainDeployerReentrancy.t.sol`](../test/OmnichainDeployerReentrancy.t.sol) for wrapper security assumptions.
|
|
28
|
+
- [`test/OmnichainDeployerEdgeCases.t.sol`](../test/OmnichainDeployerEdgeCases.t.sol), [`test/invariants/`](../test/invariants/), and [`test/regression/`](../test/regression/) for edge behavior.
|
|
@@ -378,7 +378,9 @@ contract JBOmnichainDeployer is
|
|
|
378
378
|
//*********************************************************************//
|
|
379
379
|
|
|
380
380
|
/// @notice Deploy new suckers for an existing project.
|
|
381
|
-
/// @dev Only the juicebox's owner can
|
|
381
|
+
/// @dev Only the juicebox's owner or an operator with `JBPermissionIds.DEPLOY_SUCKERS` can call this entrypoint.
|
|
382
|
+
/// The downstream registry call also maps the configured tokens on each newly created sucker, so the same
|
|
383
|
+
/// end-to-end operation depends on the project's token-mapping authority being arranged for the registry.
|
|
382
384
|
/// @param projectId The ID of the project to deploy suckers for.
|
|
383
385
|
/// @param suckerDeploymentConfiguration The suckers to set up for the project.
|
|
384
386
|
function deploySuckersFor(
|
|
@@ -806,7 +808,10 @@ contract JBOmnichainDeployer is
|
|
|
806
808
|
// Use the caller-provided flag when deploying a new hook.
|
|
807
809
|
use721ForCashOut = deploy721Config.useDataHookForCashOut;
|
|
808
810
|
} else {
|
|
809
|
-
|
|
811
|
+
// Read the *current* (approved) ruleset — not `latestRulesetId` — because a queued-but-unapproved
|
|
812
|
+
// ruleset may have been rejected by the approval hook, so its hook config should not be carried forward.
|
|
813
|
+
uint256 currentRulesetId = controller.RULESETS().currentOf(projectId).id;
|
|
814
|
+
JBTiered721HookConfig memory previousConfig = _tiered721HookOf[projectId][currentRulesetId];
|
|
810
815
|
hook = previousConfig.hook;
|
|
811
816
|
// Revert if no hook exists to carry forward — this means no tiers were provided and
|
|
812
817
|
// no previous ruleset had a 721 hook deployed through this contract.
|
|
@@ -37,6 +37,9 @@ interface IJBOmnichainDeployer {
|
|
|
37
37
|
returns (IJB721TiersHook hook, bool useDataHookForCashOut);
|
|
38
38
|
|
|
39
39
|
/// @notice Deploy new suckers for an existing project.
|
|
40
|
+
/// @dev This flow forwards to `JBSuckerRegistry.deploySuckersFor`, which also applies the supplied token mappings
|
|
41
|
+
/// on each newly deployed sucker. Existing projects therefore need both deployment authority and a project-level
|
|
42
|
+
/// arrangement that lets the registry perform `MAP_SUCKER_TOKEN` for the same operation.
|
|
40
43
|
/// @param projectId The ID of the project to deploy suckers for.
|
|
41
44
|
/// @param suckerDeploymentConfiguration The suckers to set up for the project.
|
|
42
45
|
/// @return suckers The addresses of the deployed suckers.
|
|
@@ -392,6 +392,19 @@ contract TestJBOmnichainDeployer is Test {
|
|
|
392
392
|
abi.encodeWithSelector(IJBRulesets.latestRulesetIdOf.selector, projectId),
|
|
393
393
|
abi.encode(launchTimestamp)
|
|
394
394
|
);
|
|
395
|
+
|
|
396
|
+
// Mock currentOf to return a ruleset whose id matches the launch so carry-forward can look up the stored 721
|
|
397
|
+
// hook.
|
|
398
|
+
{
|
|
399
|
+
JBRuleset memory currentRuleset;
|
|
400
|
+
currentRuleset.id = uint48(launchTimestamp);
|
|
401
|
+
vm.mockCall(
|
|
402
|
+
address(rulesets),
|
|
403
|
+
abi.encodeWithSelector(IJBRulesets.currentOf.selector, projectId),
|
|
404
|
+
abi.encode(currentRuleset)
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
395
408
|
// Warp forward so latestRulesetId < block.timestamp.
|
|
396
409
|
vm.warp(block.timestamp + 1);
|
|
397
410
|
|
|
@@ -472,6 +485,16 @@ contract TestJBOmnichainDeployer is Test {
|
|
|
472
485
|
abi.encode(launchTimestamp)
|
|
473
486
|
);
|
|
474
487
|
|
|
488
|
+
// Mock currentOf to return a ruleset whose id matches the launch so carry-forward can look up the stored 721
|
|
489
|
+
// hook.
|
|
490
|
+
JBRuleset memory currentRuleset;
|
|
491
|
+
currentRuleset.id = uint48(launchTimestamp);
|
|
492
|
+
vm.mockCall(
|
|
493
|
+
address(rulesets),
|
|
494
|
+
abi.encodeWithSelector(IJBRulesets.currentOf.selector, projectId),
|
|
495
|
+
abi.encode(currentRuleset)
|
|
496
|
+
);
|
|
497
|
+
|
|
475
498
|
// Warp forward so latestRulesetId < block.timestamp.
|
|
476
499
|
vm.warp(block.timestamp + 1);
|
|
477
500
|
|