@bananapus/suckers-v6 0.0.25 → 0.0.27
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 +14 -199
- package/ARCHITECTURE.md +20 -46
- package/AUDIT_INSTRUCTIONS.md +15 -110
- package/README.md +29 -40
- package/RISKS.md +7 -2
- package/SKILLS.md +9 -26
- package/USER_JOURNEYS.md +30 -65
- package/foundry.toml +1 -0
- package/package.json +1 -1
- package/references/operations.md +7 -4
- package/references/runtime.md +7 -5
- package/src/JBCCIPSucker.sol +0 -1
- package/src/JBSwapCCIPSucker.sol +0 -1
- package/src/libraries/JBSwapPoolLib.sol +4 -6
- package/src/structs/JBClaim.sol +0 -1
- package/src/structs/JBDenominatedAmount.sol +0 -1
- package/src/structs/JBInboxTreeRoot.sol +0 -1
- package/src/structs/JBLeaf.sol +0 -1
- package/src/structs/JBMessageRoot.sol +0 -1
- package/src/structs/JBOutboxTree.sol +0 -1
- package/src/structs/JBRemoteToken.sol +0 -1
- package/src/structs/JBSuckerDeployerConfig.sol +0 -1
- package/src/structs/JBSuckersPair.sol +0 -1
- package/src/structs/JBTokenMapping.sol +0 -1
- package/test/unit/pool_discovery.t.sol +366 -0
package/ADMINISTRATION.md
CHANGED
|
@@ -1,211 +1,26 @@
|
|
|
1
1
|
# Administration
|
|
2
2
|
|
|
3
|
-
Admin privileges and their scope in nana-suckers-v6.
|
|
4
|
-
|
|
5
3
|
## At A Glance
|
|
6
4
|
|
|
7
5
|
| Item | Details |
|
|
8
|
-
|
|
9
|
-
| Scope | Cross-chain
|
|
10
|
-
|
|
|
11
|
-
| Highest-risk actions |
|
|
12
|
-
| Recovery posture |
|
|
13
|
-
|
|
14
|
-
## Routine Operations
|
|
15
|
-
|
|
16
|
-
- Allow only the sucker deployer implementations you are prepared to support operationally on the registry side.
|
|
17
|
-
- Map remote tokens carefully and early, before outbox activity makes mistakes harder to unwind.
|
|
18
|
-
- Use deprecation timestamps to create a safe runway for shutdown rather than trying to disable a bridge abruptly.
|
|
19
|
-
- Treat emergency hatch activation as a last-resort safety action when the normal bridge path is no longer trustworthy.
|
|
20
|
-
- Verify one-time deployer configuration (`configureSingleton`, `setChainSpecificConstants`) before using a deployer in production.
|
|
21
|
-
|
|
22
|
-
## One-Way Or High-Risk Actions
|
|
23
|
-
|
|
24
|
-
- `enableEmergencyHatchFor` is irreversible for the affected token mapping.
|
|
25
|
-
- Once a sucker reaches `SENDING_DISABLED` or `DEPRECATED`, the lifecycle cannot be reversed.
|
|
26
|
-
- Deployer singleton and chain-specific constant configuration are one-time setup operations.
|
|
27
|
-
- A bad token mapping can strand users or route liquidity to the wrong remote asset.
|
|
28
|
-
|
|
29
|
-
## Recovery Notes
|
|
30
|
-
|
|
31
|
-
- If a bridge path is unhealthy, prefer deprecating the affected sucker and standing up a replacement rather than forcing traffic through a degraded route.
|
|
32
|
-
- If a token is emergency-hatched, users recover through the documented exit flow; you cannot simply re-enable the old path afterward.
|
|
33
|
-
|
|
34
|
-
## Roles
|
|
35
|
-
|
|
36
|
-
### Registry Owner
|
|
37
|
-
|
|
38
|
-
- **How assigned:** Set at `JBSuckerRegistry` construction via the `initialOwner` parameter. Transferable via OpenZeppelin `Ownable`.
|
|
39
|
-
- **Scope:** Controls which sucker deployer contracts are approved for use by the registry, and sets the global `toRemoteFee` applied to all suckers. Initially expected to be JuiceboxDAO (project ID 1).
|
|
40
|
-
- **Permission ID:** None (uses `onlyOwner` modifier from OpenZeppelin `Ownable`).
|
|
41
|
-
|
|
42
|
-
### Project Owner
|
|
43
|
-
|
|
44
|
-
- **How assigned:** Owner of the ERC-721 project NFT in `JBProjects`. All project-scoped permissions are checked against `PROJECTS.ownerOf(projectId)` or `DIRECTORY.PROJECTS().ownerOf(projectId)`.
|
|
45
|
-
- **Scope:** Controls sucker deployment, token mapping, deprecation, and emergency hatch for their project. Can delegate any of these permissions to other addresses via `JBPermissions`.
|
|
46
|
-
- **Permission ID:** N/A (is the root authority that grants the permission IDs below).
|
|
47
|
-
|
|
48
|
-
### Sucker Deployer (delegated role)
|
|
49
|
-
|
|
50
|
-
- **How assigned:** Granted `DEPLOY_SUCKERS` permission by the project owner via `JBPermissions`.
|
|
51
|
-
- **Scope:** Can deploy new suckers for a specific project through the registry.
|
|
52
|
-
- **Permission ID:** `JBPermissionIds.DEPLOY_SUCKERS`
|
|
53
|
-
|
|
54
|
-
### Token Mapper (delegated role)
|
|
55
|
-
|
|
56
|
-
- **How assigned:** Granted `MAP_SUCKER_TOKEN` permission by the project owner via `JBPermissions`. Commonly granted to the `JBSuckerRegistry` address so it can call `mapTokens` during deployment.
|
|
57
|
-
- **Scope:** Can map or disable local-to-remote token pairs on a specific sucker.
|
|
58
|
-
- **Permission ID:** `JBPermissionIds.MAP_SUCKER_TOKEN`
|
|
59
|
-
|
|
60
|
-
For existing-project deployments, this permission is part of the same operational path as `DEPLOY_SUCKERS`: the registry deploys the sucker and then immediately calls `mapTokens` on it. If the project delegates deployment without arranging mapping authority for the registry, the single deployment transaction will revert during configuration.
|
|
61
|
-
|
|
62
|
-
### Safety Admin (delegated role)
|
|
63
|
-
|
|
64
|
-
- **How assigned:** Granted `SUCKER_SAFETY` permission by the project owner via `JBPermissions`.
|
|
65
|
-
- **Scope:** Can open the emergency hatch for specific tokens on a sucker. This is an irreversible action.
|
|
66
|
-
- **Permission ID:** `JBPermissionIds.SUCKER_SAFETY`
|
|
67
|
-
|
|
68
|
-
### Deprecation Admin (delegated role)
|
|
69
|
-
|
|
70
|
-
- **How assigned:** Granted `SET_SUCKER_DEPRECATION` permission by the project owner via `JBPermissions`.
|
|
71
|
-
- **Scope:** Can set or cancel the deprecation timestamp on a sucker.
|
|
72
|
-
- **Permission ID:** `JBPermissionIds.SET_SUCKER_DEPRECATION`
|
|
73
|
-
|
|
74
|
-
### Layer-Specific Configurator
|
|
75
|
-
|
|
76
|
-
- **How assigned:** Set at deployer construction via the `configurator` parameter. Stored as the immutable `LAYER_SPECIFIC_CONFIGURATOR` address on each `JBSuckerDeployer`.
|
|
77
|
-
- **Scope:** Can call `setChainSpecificConstants` and `configureSingleton` exactly once each on the deployer. These are one-time setup functions that configure bridge-specific addresses (messenger, bridge, router, inbox) and the singleton implementation used for cloning.
|
|
78
|
-
- **Permission ID:** None (uses direct `_msgSender()` check against `LAYER_SPECIFIC_CONFIGURATOR`).
|
|
79
|
-
|
|
80
|
-
## Privileged Functions
|
|
81
|
-
|
|
82
|
-
### JBSuckerRegistry
|
|
83
|
-
|
|
84
|
-
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
85
|
-
|----------|--------------|---------------|-------|--------------|
|
|
86
|
-
| `allowSuckerDeployer(deployer)` | Registry Owner | N/A (`onlyOwner`) | Global | Adds a deployer contract to the allowlist, enabling it to be used when deploying suckers. |
|
|
87
|
-
| `allowSuckerDeployers(deployers)` | Registry Owner | N/A (`onlyOwner`) | Global | Batch version: adds multiple deployer contracts to the allowlist. |
|
|
88
|
-
| `removeSuckerDeployer(deployer)` | Registry Owner | N/A (`onlyOwner`) | Global | Removes a deployer contract from the allowlist, preventing future sucker deployments through it. |
|
|
89
|
-
| `setToRemoteFee(fee)` | Registry Owner | N/A (`onlyOwner`) | Global | Sets the `toRemoteFee` applied to all suckers. The fee must be <= `MAX_TO_REMOTE_FEE` (0.001 ether). Emits `ToRemoteFeeChanged`. |
|
|
90
|
-
| `deploySuckersFor(projectId, salt, configs)` | Project owner or delegate | `DEPLOY_SUCKERS` | Per-project | Deploys one or more suckers for a project using allowlisted deployers. Hashes salt with `_msgSender()` (which equals `msg.sender` for direct calls, or the relayed sender for ERC-2771 meta-transactions) for deterministic cross-chain addresses. Also calls `mapTokens` on each newly created sucker, so the registry must be able to satisfy the corresponding `MAP_SUCKER_TOKEN` checks for the project. |
|
|
91
|
-
| `removeDeprecatedSucker(projectId, sucker)` | Anyone | None | Per-project | Removes a fully `DEPRECATED` sucker from the registry. Permissionless but only succeeds if the sucker is in the `DEPRECATED` state. |
|
|
92
|
-
|
|
93
|
-
### JBSucker
|
|
94
|
-
|
|
95
|
-
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
96
|
-
|----------|--------------|---------------|-------|--------------|
|
|
97
|
-
| `mapToken(map)` | Project owner or delegate | `MAP_SUCKER_TOKEN` | Per-sucker | Maps a local terminal token to a remote token, enabling bridging. Setting `remoteToken` to `bytes32(0)` disables bridging and sends a final root to flush remaining outbox entries. |
|
|
98
|
-
| `mapTokens(maps)` | Project owner or delegate | `MAP_SUCKER_TOKEN` | Per-sucker | Batch version: maps multiple local-to-remote token pairs. Each mapping requires the same permission. |
|
|
99
|
-
| `enableEmergencyHatchFor(tokens)` | Project owner or delegate | `SUCKER_SAFETY` | Per-sucker | Opens the emergency hatch for specified tokens (irreversible). Sets `emergencyHatch = true` and `enabled = false` on each token's remote mapping. Allows users to exit through the outbox on the chain they deposited on. |
|
|
100
|
-
| `setDeprecation(timestamp)` | Project owner or delegate | `SET_SUCKER_DEPRECATION` | Per-sucker | Sets the timestamp after which the sucker becomes fully deprecated. Must be at least `_maxMessagingDelay()` (14 days) in the future. Set to `0` to cancel a pending deprecation. Reverts if already in `SENDING_DISABLED` or `DEPRECATED` state. |
|
|
101
|
-
|
|
102
|
-
### JBSuckerDeployer (base and all subclasses)
|
|
103
|
-
|
|
104
|
-
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
105
|
-
|----------|--------------|---------------|-------|--------------|
|
|
106
|
-
| `configureSingleton(singleton)` | Layer-Specific Configurator | N/A (direct address check) | Per-deployer | Sets the singleton implementation contract used to clone suckers via `LibClone`. Can only be called once. |
|
|
107
|
-
| `setChainSpecificConstants(...)` | Layer-Specific Configurator | N/A (direct address check) | Per-deployer | Configures bridge-specific addresses (messenger, bridge, router, inbox, etc.) for the deployer. Can only be called once. Parameters vary by bridge type. |
|
|
108
|
-
|
|
109
|
-
## Deprecation Lifecycle
|
|
110
|
-
|
|
111
|
-
The sucker deprecation lifecycle progresses through four states, controlled by `setDeprecation(timestamp)`:
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
ENABLED --> DEPRECATION_PENDING --> SENDING_DISABLED --> DEPRECATED
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
| State | Condition | Behavior |
|
|
118
|
-
|-------|-----------|----------|
|
|
119
|
-
| `ENABLED` | `deprecatedAfter == 0` | Fully functional. No deprecation is set. |
|
|
120
|
-
| `DEPRECATION_PENDING` | `block.timestamp < deprecatedAfter - _maxMessagingDelay()` | Fully functional but a warning to users that deprecation is coming. |
|
|
121
|
-
| `SENDING_DISABLED` | `block.timestamp < deprecatedAfter` (but past the messaging delay window) | `prepare()` and `toRemote()` revert. No new outbox entries or root sends. Incoming roots from the remote peer are still accepted. Users can `claim()` incoming tokens and `exitThroughEmergencyHatch()`. |
|
|
122
|
-
| `DEPRECATED` | `block.timestamp >= deprecatedAfter` | Fully shut down. No new inbox roots are accepted (`fromRemote` skips the update). Users can still `claim()` against previously accepted inbox roots and `exitThroughEmergencyHatch()` against outbox entries that were never sent. |
|
|
123
|
-
|
|
124
|
-
**Who controls transitions:**
|
|
125
|
-
- The project owner (or holder of `SET_SUCKER_DEPRECATION` permission) sets the `deprecatedAfter` timestamp via `setDeprecation(timestamp)`.
|
|
126
|
-
- The timestamp must be at least `_maxMessagingDelay()` (14 days) in the future to allow in-flight messages to arrive.
|
|
127
|
-
- A pending deprecation can be cancelled by calling `setDeprecation(0)`, but only while in `ENABLED` or `DEPRECATION_PENDING` state.
|
|
128
|
-
- Once `SENDING_DISABLED` or `DEPRECATED`, the deprecation cannot be reversed.
|
|
129
|
-
|
|
130
|
-
**Removing from registry:** Once a sucker reaches `DEPRECATED`, anyone can call `JBSuckerRegistry.removeDeprecatedSucker(projectId, sucker)` to remove it from the project's sucker list.
|
|
131
|
-
|
|
132
|
-
## Emergency Hatch
|
|
133
|
-
|
|
134
|
-
The emergency hatch is a per-token escape mechanism for when a bridge becomes non-functional for specific tokens.
|
|
135
|
-
|
|
136
|
-
**Activation:** The project owner (or holder of `SUCKER_SAFETY` permission) calls `enableEmergencyHatchFor(tokens)` on the sucker. This is irreversible -- once opened for a token, that token can never be bridged by this sucker again.
|
|
137
|
-
|
|
138
|
-
**Effect:** Sets `emergencyHatch = true` and `enabled = false` on each specified token's `JBRemoteToken` mapping. This:
|
|
139
|
-
- Prevents new `prepare()` calls for the token (the token is no longer mapped/enabled).
|
|
140
|
-
- Prevents `toRemote()` calls for the token (reverts with `JBSucker_TokenHasInvalidEmergencyHatchState`).
|
|
141
|
-
- Prevents `mapToken()` from remapping or re-enabling the token (reverts with `JBSucker_TokenHasInvalidEmergencyHatchState`).
|
|
142
|
-
- Enables `exitThroughEmergencyHatch()` for users whose outbox entries were never sent to the remote peer (entries with `index >= numberOfClaimsSent`).
|
|
143
|
-
|
|
144
|
-
**Who can exit:** Any user with a valid outbox merkle proof for a leaf that was never sent over the bridge. The exit mints project tokens to the beneficiary and returns the terminal tokens to the project's terminal balance. The beneficiary can then cash out the minted project tokens through the normal Juicebox mechanism if desired.
|
|
145
|
-
|
|
146
|
-
**Safety constraint:** Only leaves that were never communicated to the remote peer (i.e., `index >= outbox.numberOfClaimsSent`) can be emergency-exited. Leaves already sent over the bridge (index < `numberOfClaimsSent`) cannot be emergency-exited because they may have already been claimed on the remote chain.
|
|
147
|
-
|
|
148
|
-
## Immutable Configuration
|
|
149
|
-
|
|
150
|
-
The following values are set at deploy time and cannot be changed:
|
|
151
|
-
|
|
152
|
-
| Property | Contract | Set By | Description |
|
|
153
|
-
|----------|----------|--------|-------------|
|
|
154
|
-
| `DIRECTORY` | `JBSucker`, `JBSuckerRegistry`, all deployers | Constructor | The Juicebox directory contract. |
|
|
155
|
-
| `FEE_PROJECT_ID` | `JBSucker` | Constructor | The project that receives `toRemoteFee` payments via `terminal.pay()` on each `toRemote()` call. Typically project ID 1 (the protocol project). Best-effort: fee is silently skipped if the fee project has no native token terminal or if `terminal.pay()` reverts. |
|
|
156
|
-
| `REGISTRY` | `JBSucker` | Constructor | The `JBSuckerRegistry` that this sucker reads the `toRemoteFee` from. |
|
|
157
|
-
| `MAX_TO_REMOTE_FEE` | `JBSuckerRegistry` | Constant | Hard cap on what `toRemoteFee` can be set to (0.001 ether). Prevents the registry owner from setting an excessively high fee. |
|
|
158
|
-
| `TOKENS` | `JBSucker`, all deployers | Constructor | The Juicebox token management contract. |
|
|
159
|
-
| `PROJECTS` | `JBSuckerRegistry` | Derived from `DIRECTORY.PROJECTS()` | The ERC-721 project ownership contract. |
|
|
160
|
-
| `OPBRIDGE` | `JBOptimismSucker`, `JBBaseSucker`, `JBCeloSucker` | Constructor (from deployer callback) | The OP Standard Bridge address. |
|
|
161
|
-
| `OPMESSENGER` | `JBOptimismSucker`, `JBBaseSucker`, `JBCeloSucker` | Constructor (from deployer callback) | The OP Cross-Domain Messenger address. |
|
|
162
|
-
| `ARBINBOX` | `JBArbitrumSucker` | Constructor (from deployer callback) | The Arbitrum Inbox address. |
|
|
163
|
-
| `GATEWAYROUTER` | `JBArbitrumSucker` | Constructor (from deployer callback) | The Arbitrum Gateway Router address. |
|
|
164
|
-
| `LAYER` | `JBArbitrumSucker` | Constructor (from deployer callback) | Whether this is an L1 or L2 sucker. |
|
|
165
|
-
| `CCIP_ROUTER` | `JBCCIPSucker` | Constructor (from deployer callback) | The Chainlink CCIP Router address. |
|
|
166
|
-
| `REMOTE_CHAIN_ID` | `JBCCIPSucker` | Constructor (from deployer callback) | The remote chain's chain ID. |
|
|
167
|
-
| `REMOTE_CHAIN_SELECTOR` | `JBCCIPSucker` | Constructor (from deployer callback) | The CCIP chain selector for the remote chain. |
|
|
168
|
-
| `WRAPPED_NATIVE` | `JBCeloSucker` | Constructor (from deployer callback) | The wrapped native token (WETH) on the local chain. |
|
|
169
|
-
| `LAYER_SPECIFIC_CONFIGURATOR` | All deployers | Constructor | The address authorized to call one-time setup functions. |
|
|
170
|
-
| `peer()` | `JBSucker` | Deterministic (returns `bytes32` via `_toBytes32(address(this))`) | The remote peer sucker address as `bytes32`. Defaults to the local address, relying on CREATE2 giving the same address on both chains. |
|
|
171
|
-
| `deployer` | `JBSucker` | Set once in `initialize()` by `msg.sender` | The address that deployed this sucker clone (the deployer contract). |
|
|
172
|
-
| `projectId()` | `JBSucker` | Set once in `initialize()` | The local project ID this sucker serves. |
|
|
173
|
-
| Bridge/messenger/router addresses | All deployers | `setChainSpecificConstants()` (one-time) | Bridge infrastructure addresses, set once by the configurator. |
|
|
174
|
-
| Singleton implementation | All deployers | `configureSingleton()` (one-time) | The singleton contract used as the clone template. |
|
|
175
|
-
|
|
176
|
-
## Admin Boundaries
|
|
177
|
-
|
|
178
|
-
What admins **cannot** do:
|
|
179
|
-
|
|
180
|
-
- **Cannot remap tokens after outbox activity.** Once a token has outbox tree entries (`_outboxOf[token].tree.count != 0`), it cannot be remapped to a different remote token. It can only be disabled (set to `bytes32(0)`) or re-enabled to the same remote address. This prevents double-spending across two different remote tokens.
|
|
181
|
-
|
|
182
|
-
- **Cannot reverse an emergency hatch.** Once `enableEmergencyHatchFor` is called for a token, `emergencyHatch` is permanently `true`. The token can never be re-enabled for bridging on this sucker. A new sucker must be deployed if bridging needs to resume.
|
|
183
|
-
|
|
184
|
-
- **Cannot reverse deprecation past `SENDING_DISABLED`.** Once the sucker enters `SENDING_DISABLED` or `DEPRECATED` state, `setDeprecation()` reverts. The deprecation cannot be cancelled.
|
|
185
|
-
|
|
186
|
-
- **Cannot bypass bridge security.** Cross-chain message authentication is enforced by each bridge implementation's `_isRemotePeer()` check. The project owner has no way to override this -- only messages from the legitimate remote peer (verified by the OP Messenger, Arbitrum Bridge/Outbox, or CCIP Router) are accepted by `fromRemote()`.
|
|
187
|
-
|
|
188
|
-
- **Cannot reconfigure deployers.** `setChainSpecificConstants()` and `configureSingleton()` can each only be called once per deployer. Bridge addresses and the singleton implementation are permanent after initial configuration.
|
|
189
|
-
|
|
190
|
-
- **Cannot steal user funds via emergency hatch.** Emergency exit only returns tokens to the original beneficiary specified in the outbox merkle tree, and only for leaves that were never sent over the bridge (index >= `numberOfClaimsSent`). Leaves already bridged cannot be double-claimed.
|
|
191
|
-
|
|
192
|
-
- **Cannot claim on behalf of others.** While `claim()` and `exitThroughEmergencyHatch()` are permissionless (anyone can call them), the project tokens are always minted to the beneficiary encoded in the merkle tree leaf, not to the caller.
|
|
193
|
-
|
|
194
|
-
- **Cannot change the peer address.** The peer is determined by deterministic deployment (CREATE2 with sender-specific salt). There is no admin function to change which remote address is trusted.
|
|
195
|
-
|
|
196
|
-
- **Cannot change the project ID.** The `projectId` is set once during `initialize()` and is immutable thereafter (enforced by OpenZeppelin `Initializable`).
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Scope | Cross-chain claim movement, token mapping, fees, and deprecation controls |
|
|
8
|
+
| Control posture | Mixed registry-owner, project-permission, and bridge-specific trust |
|
|
9
|
+
| Highest-risk actions | Wrong token mapping, wrong peer assumptions, and bad emergency or deprecation handling |
|
|
10
|
+
| Recovery posture | Often one-way; many recovery paths are intentionally irreversible |
|
|
197
11
|
|
|
198
|
-
|
|
12
|
+
## Purpose
|
|
199
13
|
|
|
200
|
-
|
|
14
|
+
This repo controls the shared lifecycle around bridging project positions, not just the transport call itself.
|
|
201
15
|
|
|
202
|
-
##
|
|
16
|
+
## Control Model
|
|
203
17
|
|
|
204
|
-
|
|
18
|
+
- registry owner controls shared fee settings and deployer allowlists
|
|
19
|
+
- project-level permissions control token mapping and safety paths
|
|
20
|
+
- bridge-specific implementations inherit external trust assumptions
|
|
205
21
|
|
|
206
|
-
|
|
22
|
+
## Recovery
|
|
207
23
|
|
|
208
|
-
-
|
|
209
|
-
-
|
|
24
|
+
- emergency hatch and deprecation are the main recovery tools
|
|
25
|
+
- both are intentionally conservative and often one-way
|
|
210
26
|
|
|
211
|
-
**Fee delivery is best-effort.** The `toRemoteFee` is collected by calling `terminal.pay()` to the fee project during `toRemote()`. If this call reverts (e.g., the fee project has no native token terminal or its terminal is misconfigured), the fee is silently skipped and `toRemote()` proceeds normally. This ensures bridging availability is never blocked by fee infrastructure issues, but it also means fee revenue can be lost without any on-chain signal.
|
package/ARCHITECTURE.md
CHANGED
|
@@ -2,59 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`nana-suckers-v6`
|
|
5
|
+
`nana-suckers-v6` bridges Juicebox project positions across chains by turning local burns into claimable remote mints.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## System Overview
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- Chain-specific subclasses own transport details for OP Stack, Arbitrum, CCIP, and Celo variants.
|
|
11
|
-
- The registry and deployers own pair creation and global policy.
|
|
12
|
-
- Core protocol accounting still happens through project terminals on each chain.
|
|
9
|
+
`JBSucker` handles prepare, relay, claim, token mapping, deprecation, and emergency exits. `JBSuckerRegistry` tracks deployments, deployer allowlists, and shared fee settings. Bridge-specific implementations handle transport details.
|
|
13
10
|
|
|
14
|
-
##
|
|
11
|
+
## Core Invariants
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| deployers | Clone and initialize deterministic sucker instances |
|
|
22
|
-
| `MerkleLib` and helper libraries | Incremental tree logic plus chain-specific constants |
|
|
13
|
+
- Merkle trees stay append-only
|
|
14
|
+
- nonce progression stays monotonic
|
|
15
|
+
- token mapping stays coherent across peers
|
|
16
|
+
- claims and emergency exits do not double-spend
|
|
17
|
+
- outbox balance accounting stays consistent through send and recovery flows
|
|
23
18
|
|
|
24
|
-
##
|
|
19
|
+
## Trust Boundaries
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-> sucker inserts a merkle leaf into the outbox tree
|
|
30
|
-
-> someone bridges funds and the latest root to the remote sucker
|
|
31
|
-
-> claimant proves inclusion against the remote inbox tree
|
|
32
|
-
-> remote sucker releases or remints the destination-side value
|
|
33
|
-
```
|
|
21
|
+
- shared logic lives in `JBSucker`
|
|
22
|
+
- transport security lives in the bridge-specific implementation and external bridge counterparties
|
|
23
|
+
- registry decisions can widen or constrain the allowed deployment surface
|
|
34
24
|
|
|
35
|
-
##
|
|
25
|
+
## Security Model
|
|
36
26
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- Deprecation and emergency controls must not compromise already-bridged claims.
|
|
40
|
-
- The chain-specific transport layers may differ, but they must preserve the same logical prepare-to-claim lifecycle.
|
|
27
|
+
- the biggest risks are non-atomic cross-chain state, bad token mapping, and broken peer assumptions
|
|
28
|
+
- bridge liveness and correct peer identity are real trust assumptions
|
|
41
29
|
|
|
42
|
-
##
|
|
30
|
+
## Source Map
|
|
43
31
|
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
## Dependencies
|
|
49
|
-
|
|
50
|
-
- `nana-core-v6` terminals and project-token semantics
|
|
51
|
-
- Native bridge infrastructure for OP Stack, Arbitrum, CCIP, and supported variants
|
|
52
|
-
- `nana-permission-ids-v6` for deploy, map, safety, and deprecation permissions
|
|
53
|
-
|
|
54
|
-
## Safe Change Guide
|
|
55
|
-
|
|
56
|
-
- Review every cross-chain change from both sides of the pair.
|
|
57
|
-
- Do not change merkle leaf encoding casually; proofs and remote compatibility depend on it.
|
|
58
|
-
- Keep registry policy, deployer configuration, and singleton logic aligned.
|
|
59
|
-
- Test chain-specific native-token wrapping paths separately from the abstract lifecycle.
|
|
60
|
-
- Assume bridge-edge behavior is the primary review target, not the happy path.
|
|
32
|
+
- `src/JBSucker.sol`
|
|
33
|
+
- `src/JBSuckerRegistry.sol`
|
|
34
|
+
- `src/utils/MerkleLib.sol`
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -1,128 +1,33 @@
|
|
|
1
1
|
# Audit Instructions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Audit this repo as cross-chain claim and recovery logic, not as a generic ERC-20 bridge.
|
|
4
4
|
|
|
5
|
-
## Objective
|
|
5
|
+
## Audit Objective
|
|
6
6
|
|
|
7
7
|
Find issues that:
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
8
|
+
|
|
9
|
+
- break Merkle-root or nonce progression
|
|
10
|
+
- allow bad token mapping or peer assumptions
|
|
11
|
+
- permit double-claim or bad emergency exit behavior
|
|
12
|
+
- make non-atomic bridge semantics unsafe
|
|
13
13
|
|
|
14
14
|
## Scope
|
|
15
15
|
|
|
16
16
|
In scope:
|
|
17
|
-
|
|
18
|
-
-
|
|
17
|
+
|
|
18
|
+
- `src/JBSucker.sol`
|
|
19
|
+
- `src/JBSuckerRegistry.sol`
|
|
20
|
+
- bridge-specific implementations and deployers
|
|
19
21
|
- `src/utils/MerkleLib.sol`
|
|
20
|
-
- libraries, enums, interfaces, and structs under `src/`
|
|
21
|
-
- deployment scripts in `script/`
|
|
22
22
|
|
|
23
23
|
## Start Here
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- token mapping and emergency-hatch logic
|
|
29
|
-
- one native bridge implementation
|
|
30
|
-
- `JBCCIPSucker`
|
|
31
|
-
- deployers and registry assumptions
|
|
32
|
-
|
|
33
|
-
That order gets you from the shared conservation model to the transport-specific deviations.
|
|
34
|
-
|
|
35
|
-
## System Model
|
|
36
|
-
|
|
37
|
-
The bridge flow is:
|
|
38
|
-
- burn or prepare project-token value on source chain
|
|
39
|
-
- record a leaf into an outbox tree
|
|
40
|
-
- send a merkle root and backing assets over a chain-specific transport
|
|
41
|
-
- receive the root on the remote chain
|
|
42
|
-
- claim by proving inclusion against the current inbox root
|
|
43
|
-
|
|
44
|
-
This repo supports multiple transport implementations:
|
|
45
|
-
- OP Stack variants
|
|
46
|
-
- Arbitrum
|
|
47
|
-
- CCIP
|
|
48
|
-
- related deployers and registries
|
|
49
|
-
|
|
50
|
-
One non-obvious property to audit explicitly:
|
|
51
|
-
- roots and assets do not always arrive in a perfectly ordered, synchronous way
|
|
52
|
-
- the system is intentionally designed to survive some transport mismatch without deadlocking
|
|
53
|
-
- those recovery choices are exactly where conservation bugs tend to hide
|
|
54
|
-
|
|
55
|
-
## Critical Invariants
|
|
56
|
-
|
|
57
|
-
1. Cross-chain conservation
|
|
58
|
-
For any prepared transfer, destination claimable value must not exceed what the source side actually prepared and backed.
|
|
59
|
-
|
|
60
|
-
2. Single execution
|
|
61
|
-
Each bridged leaf must be claimable at most once on the destination and at most once via emergency exit.
|
|
62
|
-
|
|
63
|
-
3. Peer authenticity
|
|
64
|
-
Only the intended remote peer and messenger path may update inbox roots.
|
|
65
|
-
|
|
66
|
-
4. Deprecation safety
|
|
67
|
-
Deprecation and emergency-hatch controls must not let callers bypass intended restrictions or steal in-flight funds.
|
|
68
|
-
|
|
69
|
-
5. Token mapping integrity
|
|
70
|
-
Remote token mappings must be immutable or mutable only exactly where the design allows.
|
|
25
|
+
1. `src/JBSucker.sol`
|
|
26
|
+
2. `src/JBSuckerRegistry.sol`
|
|
27
|
+
3. the relevant bridge-specific implementation
|
|
71
28
|
|
|
72
|
-
|
|
73
|
-
Later roots must not silently invalidate earlier user claims unless the protocol explicitly intends that recovery path.
|
|
29
|
+
## Verification
|
|
74
30
|
|
|
75
|
-
## Threat Model
|
|
76
|
-
|
|
77
|
-
Prioritize:
|
|
78
|
-
- out-of-order nonce arrival
|
|
79
|
-
- cross-sucker replay
|
|
80
|
-
- trusted-forwarder or messenger spoofing
|
|
81
|
-
- emergency-exit races
|
|
82
|
-
- fee fallback and bridge-payment edge cases
|
|
83
|
-
- deterministic deployer assumptions for peer pairing
|
|
84
|
-
|
|
85
|
-
The strongest attacker models here are:
|
|
86
|
-
- a caller trying to claim from the wrong root with a structurally valid proof
|
|
87
|
-
- a privileged actor abusing token mapping or emergency controls after users already prepared transfers
|
|
88
|
-
- a transport delivering messages out of order and exposing assumptions hidden in the happy path
|
|
89
|
-
|
|
90
|
-
## Hotspots
|
|
91
|
-
|
|
92
|
-
- `prepare`, `toRemote`, `fromRemote`, and `claim`
|
|
93
|
-
- bitmap execution tracking
|
|
94
|
-
- root and nonce handling
|
|
95
|
-
- token mapping and registry trust
|
|
96
|
-
- chain-specific messenger authentication
|
|
97
|
-
- deployer address derivation and clone setup
|
|
98
|
-
|
|
99
|
-
## Sequences Worth Replaying
|
|
100
|
-
|
|
101
|
-
1. Prepare multiple leaves -> send multiple roots -> receive them out of order -> attempt claims for each.
|
|
102
|
-
2. Prepare -> deprecate or enable emergency hatch -> claim and exit attempts racing each other.
|
|
103
|
-
3. Map token -> prepare transfer -> attempt remap or peer mismatch after value is already in flight.
|
|
104
|
-
4. Same logical transfer across different sucker implementations to check for replay or identity confusion.
|
|
105
|
-
|
|
106
|
-
## Finding Bar
|
|
107
|
-
|
|
108
|
-
The strongest findings here usually show one of these:
|
|
109
|
-
- a user can claim against value that was never actually prepared
|
|
110
|
-
- a valid prepare becomes permanently unclaimable without the recovery path the protocol expects
|
|
111
|
-
- transport-specific authentication is weaker than the shared model assumes
|
|
112
|
-
- a privileged mapping or safety control can rewrite the meaning of already in-flight value
|
|
113
|
-
|
|
114
|
-
## Build And Verification
|
|
115
|
-
|
|
116
|
-
Standard workflow:
|
|
117
31
|
- `npm install`
|
|
118
32
|
- `forge build`
|
|
119
33
|
- `forge test`
|
|
120
|
-
|
|
121
|
-
The current tests already target:
|
|
122
|
-
- deep attack and regression scenarios
|
|
123
|
-
- trusted-forwarder spoofing
|
|
124
|
-
- fee fallback behavior
|
|
125
|
-
- deterministic deployment
|
|
126
|
-
- chain-specific fork flows
|
|
127
|
-
|
|
128
|
-
High-value findings here show a break in conservation, replay resistance, or trusted-peer boundaries.
|
package/README.md
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
`@bananapus/suckers-v6` provides cross-chain bridging for Juicebox project tokens and the terminal assets that back them. A pair of suckers lets users burn on one chain, move value across a bridge, and mint the same project token representation on another chain.
|
|
4
4
|
|
|
5
5
|
Docs: <https://docs.juicebox.money>
|
|
6
|
-
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
6
|
+
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
7
|
+
User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
|
|
8
|
+
Skills: [SKILLS.md](./SKILLS.md)
|
|
9
|
+
Risks: [RISKS.md](./RISKS.md)
|
|
10
|
+
Administration: [ADMINISTRATION.md](./ADMINISTRATION.md)
|
|
11
|
+
Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
|
|
7
12
|
|
|
8
13
|
The codebase includes multiple bridge variants, but the canonical deployment and discovery tooling in this repo is narrower than the full runtime surface. Treat the deployment scripts and helper libraries as the source of truth for what is operationally supported today.
|
|
9
14
|
|
|
@@ -19,7 +24,7 @@ The base implementation is extended for multiple bridge families so the same pro
|
|
|
19
24
|
|
|
20
25
|
Use this repo when the requirement is canonical project-token movement across chains. Do not use it if the project is single-chain or if the bridge assumptions for the target networks are unacceptable.
|
|
21
26
|
|
|
22
|
-
The main idea is not "bridge the token contract." The main idea is "bridge a Juicebox
|
|
27
|
+
The main idea is not "bridge the token contract." The main idea is "bridge a Juicebox claim plus enough information to recreate the project-token position on the remote chain."
|
|
23
28
|
|
|
24
29
|
## Key Contracts
|
|
25
30
|
|
|
@@ -27,11 +32,7 @@ The main idea is not "bridge the token contract." The main idea is "bridge a Jui
|
|
|
27
32
|
| --- | --- |
|
|
28
33
|
| `JBSucker` | Base bridge logic for prepare, relay, claim, token mapping, and lifecycle controls. |
|
|
29
34
|
| `JBSuckerRegistry` | Registry for per-project sucker deployments, deployer allowlists, and shared bridge fee settings. |
|
|
30
|
-
|
|
|
31
|
-
| `JBBaseSucker` | Base-flavored OP Stack implementation. |
|
|
32
|
-
| `JBCeloSucker` | OP Stack implementation adapted for Celo's native asset behavior. |
|
|
33
|
-
| `JBArbitrumSucker` | Arbitrum bridge implementation. |
|
|
34
|
-
| `JBCCIPSucker` | Chainlink CCIP-based implementation for CCIP-connected chains. |
|
|
35
|
+
| Chain-specific suckers | Transport-specific implementations for OP Stack, Arbitrum, CCIP, and related environments. |
|
|
35
36
|
|
|
36
37
|
## Mental Model
|
|
37
38
|
|
|
@@ -45,28 +46,6 @@ That means every bridge path has two trust surfaces:
|
|
|
45
46
|
- the shared sucker accounting and Merkle logic
|
|
46
47
|
- the bridge-specific transport implementation
|
|
47
48
|
|
|
48
|
-
The shortest useful reading order is:
|
|
49
|
-
|
|
50
|
-
| Contract | Description |
|
|
51
|
-
|----------|-------------|
|
|
52
|
-
| [`JBSucker`](src/JBSucker.sol) | Abstract base. Manages outbox/inbox merkle trees, `prepare`/`toRemote`/`claim` lifecycle, token mapping, deprecation, and emergency hatch. Deployed as clones via `Initializable`. Uses `ERC2771Context` for meta-transactions. Has immutable `FEE_PROJECT_ID` (typically project ID 1) and immutable `REGISTRY` reference. Reads the `toRemoteFee` from the registry via `REGISTRY.toRemoteFee()` on each `toRemote()` call. |
|
|
53
|
-
| [`JBCCIPSucker`](src/JBCCIPSucker.sol) | Extends `JBSucker`. Bridges via Chainlink CCIP (`ccipSend`/`ccipReceive`). Supports any CCIP-connected chain pair. Wraps native ETH to WETH before bridging (CCIP only transports ERC-20s) and unwraps on the receiving end. Can map `NATIVE_TOKEN` to ERC-20 addresses on the remote chain (unlike OP/Arbitrum suckers). |
|
|
54
|
-
| [`JBOptimismSucker`](src/JBOptimismSucker.sol) | Extends `JBSucker`. Bridges via OP Standard Bridge + OP Messenger. No `msg.value` required for transport. |
|
|
55
|
-
| [`JBBaseSucker`](src/JBBaseSucker.sol) | Thin wrapper around `JBOptimismSucker` with Base chain IDs (Ethereum 1 <-> Base 8453, Sepolia 11155111 <-> Base Sepolia 84532). |
|
|
56
|
-
| [`JBCeloSucker`](src/JBCeloSucker.sol) | Extends `JBOptimismSucker` for Celo (OP Stack, custom gas token CELO). Wraps native ETH → WETH before bridging as ERC-20. Unwraps received WETH → native ETH via `_addToBalance` override. Removes `NATIVE_TOKEN → NATIVE_TOKEN` restriction. Sends messenger messages with `nativeValue = 0` (Celo's native token is CELO, not ETH). |
|
|
57
|
-
| [`JBArbitrumSucker`](src/JBArbitrumSucker.sol) | Extends `JBSucker`. Bridges via Arbitrum Inbox + Gateway Router. Uses `unsafeCreateRetryableTicket` for L1->L2 (to avoid address aliasing of refund address) and `ArbSys.sendTxToL1` for L2->L1. Requires `msg.value` for L1->L2 transport payment. |
|
|
58
|
-
| [`JBSuckerRegistry`](src/JBSuckerRegistry.sol) | Tracks all suckers per project. Manages deployer allowlist (owner-only). Entry point for `deploySuckersFor`. Can remove deprecated suckers via `removeDeprecatedSucker`. Owns the global `toRemoteFee` (ETH fee in wei, capped at `MAX_TO_REMOTE_FEE` = 0.001 ether), adjustable by the registry owner via `setToRemoteFee()`. All sucker clones read this fee from the registry. Existing-project deployments are deploy-and-map operations, so the registry also needs to be arranged as an authorized `MAP_SUCKER_TOKEN` operator for those projects. |
|
|
59
|
-
| [`JBSuckerDeployer`](src/JBSuckerDeployer.sol) | Abstract base deployer. Clones a singleton sucker via `LibClone.cloneDeterministic` and initializes it. Two-phase setup: `setChainSpecificConstants` then `configureSingleton`. |
|
|
60
|
-
| [`JBCCIPSuckerDeployer`](src/deployers/JBCCIPSuckerDeployer.sol) | Deployer for `JBCCIPSucker`. Stores CCIP router, remote chain ID, and CCIP chain selector. |
|
|
61
|
-
| [`JBOptimismSuckerDeployer`](src/deployers/JBOptimismSuckerDeployer.sol) | Deployer for `JBOptimismSucker`. Stores OP Messenger and OP Bridge addresses. |
|
|
62
|
-
| [`JBBaseSuckerDeployer`](src/deployers/JBBaseSuckerDeployer.sol) | Thin wrapper around `JBOptimismSuckerDeployer` for Base. |
|
|
63
|
-
| [`JBCeloSuckerDeployer`](src/deployers/JBCeloSuckerDeployer.sol) | Deployer for `JBCeloSucker`. Extends `JBOptimismSuckerDeployer` with `wrappedNative` (`IWrappedNativeToken`) storage for the local chain's WETH address. |
|
|
64
|
-
| [`JBArbitrumSuckerDeployer`](src/deployers/JBArbitrumSuckerDeployer.sol) | Deployer for `JBArbitrumSucker`. Stores Arbitrum Inbox, Gateway Router, and layer (`JBLayer.L1` or `JBLayer.L2`). |
|
|
65
|
-
| [`MerkleLib`](src/utils/MerkleLib.sol) | Incremental merkle tree (depth 32, max ~4 billion leaves, modeled on eth2 deposit contract). Used for outbox/inbox trees. `insert` and `root` operate directly on `Tree storage` (not memory copies) to avoid redundant SLOAD/SSTORE round-trips. Gas-optimized with inline assembly for `root()` and `branchRoot()`. |
|
|
66
|
-
| [`CCIPHelper`](src/libraries/CCIPHelper.sol) | CCIP router addresses, chain selectors, and WETH addresses per chain. Covers Ethereum, Optimism, Arbitrum, Base, Polygon, Avalanche, and BNB Chain (mainnet and testnets). |
|
|
67
|
-
| [`ARBAddresses`](src/libraries/ARBAddresses.sol) | Arbitrum bridge contract addresses (Inbox, Gateway Router) for mainnet and Sepolia. |
|
|
68
|
-
| [`ARBChains`](src/libraries/ARBChains.sol) | Arbitrum chain ID constants. |
|
|
69
|
-
|
|
70
49
|
## Read These Files First
|
|
71
50
|
|
|
72
51
|
1. `src/JBSucker.sol`
|
|
@@ -77,18 +56,24 @@ The shortest useful reading order is:
|
|
|
77
56
|
|
|
78
57
|
## Integration Traps
|
|
79
58
|
|
|
80
|
-
- do not reason about suckers as if they were generic ERC-20 bridges
|
|
81
|
-
- root ordering and message delivery semantics matter as much as
|
|
82
|
-
- token mapping is part of the economic invariant
|
|
83
|
-
- emergency and deprecation paths are
|
|
59
|
+
- do not reason about suckers as if they were generic ERC-20 bridges
|
|
60
|
+
- root ordering and message delivery semantics matter as much as proof format
|
|
61
|
+
- token mapping is part of the economic invariant
|
|
62
|
+
- emergency and deprecation paths are part of normal operational safety
|
|
84
63
|
|
|
85
64
|
## Where State Lives
|
|
86
65
|
|
|
87
|
-
- per-claim and tree progression state
|
|
88
|
-
- deployment inventory and shared operational config
|
|
89
|
-
- bridge transport assumptions
|
|
66
|
+
- per-claim and tree progression state: the sucker pair
|
|
67
|
+
- deployment inventory and shared operational config: `JBSuckerRegistry`
|
|
68
|
+
- bridge transport assumptions: the chain-specific implementation and its external counterparties
|
|
69
|
+
|
|
70
|
+
## High-Signal Tests
|
|
90
71
|
|
|
91
|
-
|
|
72
|
+
1. `test/unit/registry.t.sol`
|
|
73
|
+
2. `test/unit/multi_chain_evolution.t.sol`
|
|
74
|
+
3. `test/ForkClaimMainnet.t.sol`
|
|
75
|
+
4. `test/audit/codex-PeerSnapshotDesync.t.sol`
|
|
76
|
+
5. `test/audit/codex-ToRemoteFeeIrrecoverable.t.sol`
|
|
92
77
|
|
|
93
78
|
## Install
|
|
94
79
|
|
|
@@ -112,7 +97,7 @@ Useful scripts:
|
|
|
112
97
|
|
|
113
98
|
## Deployment Notes
|
|
114
99
|
|
|
115
|
-
This package supports multiple bridge families and is intentionally split into bridge-specific deployers.
|
|
100
|
+
This package supports multiple bridge families and is intentionally split into bridge-specific deployers. Operational support is narrower than "all theoretically bridgeable chains" and should be taken from the configured deployers, helper libraries, and deployment scripts in this repo.
|
|
116
101
|
|
|
117
102
|
## Repository Layout
|
|
118
103
|
|
|
@@ -136,9 +121,13 @@ script/
|
|
|
136
121
|
|
|
137
122
|
## Risks And Notes
|
|
138
123
|
|
|
139
|
-
- out-of-order root delivery can make some claims
|
|
124
|
+
- out-of-order root delivery can make some claims unavailable until an operator uses an emergency path
|
|
140
125
|
- bridge-specific transport assumptions matter as much as the shared sucker logic
|
|
141
126
|
- token mapping and deprecation controls are governance-sensitive surfaces
|
|
142
127
|
- a bridge that stays live operationally still may not be economically safe for every asset or chain pair
|
|
143
128
|
|
|
144
|
-
|
|
129
|
+
## For AI Agents
|
|
130
|
+
|
|
131
|
+
- Do not summarize this repo as a generic token bridge.
|
|
132
|
+
- Always separate shared sucker logic from bridge-specific transport behavior.
|
|
133
|
+
- Use the chain-specific implementation and matching deployer together when answering operational questions.
|
package/RISKS.md
CHANGED
|
@@ -62,6 +62,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
|
|
|
62
62
|
- **Renounced registry ownership risk.** If the registry owner calls `renounceOwnership()`, `setToRemoteFee()` becomes permanently uncallable and the fee is frozen at its current value across all suckers. This is a deliberate trade-off: it allows the registry owner to credibly commit to a fee level, but eliminates the ability to respond to future ETH price changes. The fee is still capped at `MAX_TO_REMOTE_FEE`, so the maximum downside is bounded.
|
|
63
63
|
- **Immutable fee project.** `FEE_PROJECT_ID` is set at construction and cannot be changed. If the fee project is abandoned or its terminal removed, there is no way to redirect fees without deploying new suckers.
|
|
64
64
|
- **Cross-reference: sucker registration path.** Suckers are deployed via `JBSuckerRegistry.deploySuckersFor`, which requires `DEPLOY_SUCKERS` permission from the project owner. The registry's `deploy` function uses `CREATE2` with a deployer-specific salt. The sucker's `peer()` address is deterministic — a misconfigured peer means the sucker accepts messages from the wrong remote address. See [nana-omnichain-deployers-v6 RISKS.md](../nana-omnichain-deployers-v6/RISKS.md) for deployer-level risks.
|
|
65
|
+
- **Registry aggregate views fail open.** `JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` sum values across active suckers but silently skip any sucker that reverts. This preserves liveness for dashboards and cross-chain estimates, but it means the returned aggregate can understate remote state whenever one peer is broken, censored, deprecated incorrectly, or simply expensive to query.
|
|
65
66
|
|
|
66
67
|
## 6. Deprecation Lifecycle
|
|
67
68
|
|
|
@@ -127,10 +128,14 @@ The registry owner can adjust `toRemoteFee` via `JBSuckerRegistry.setToRemoteFee
|
|
|
127
128
|
|
|
128
129
|
The fee is paid to `FEE_PROJECT_ID` (the protocol project), not to the sucker's own `projectId()`. This centralizes fee collection, but it is still only best-effort: if the fee project's native terminal is missing or its `pay` call reverts, the fee ETH stays in the sucker contract and is later recoverable through the normal claim path. The sucker's project does not directly benefit from the anti-spam fee.
|
|
129
130
|
|
|
130
|
-
### 10.5
|
|
131
|
+
### 10.5 Registry aggregate views prioritize liveness over completeness
|
|
132
|
+
|
|
133
|
+
`JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` intentionally use `try/catch` around each sucker and silently ignore peers that revert. This is accepted because a single bad peer should not brick every cross-chain dashboard or estimator. The trade-off is that these read surfaces are best-effort only: consumers must treat them as lower bounds, not exact reconciled totals, unless they independently verify that every active sucker responded successfully.
|
|
134
|
+
|
|
135
|
+
### 10.6 Hookless V4 spot pricing is sandwich-vulnerable by design
|
|
131
136
|
|
|
132
137
|
When the only available Uniswap pool for a cross-denomination swap is a hookless V4 pool (no V3 pool exists), `_getV4Quote` falls back to the instantaneous spot tick from `POOL_MANAGER.getSlot0()` instead of a TWAP oracle. This tick is manipulable via sandwich attacks, allowing an attacker to skew the `minAmountOut` and extract value from the swap. The sigmoid slippage model limits the damage but operates on a corrupted baseline. This is an accepted tradeoff: reverting when no TWAP is available would cause the CCIP message to fail, leaving bridged tokens stuck in the CCIP router until manual retry. Getting some value (even at a worse rate) is preferred over a stuck bridge message, especially since this path only triggers when no V3 pool with built-in TWAP exists at all. The hooked V4 path also falls back to spot if the hook's `observe()` reverts, with the same tradeoff.
|
|
133
138
|
|
|
134
|
-
### 10.
|
|
139
|
+
### 10.7 `mapTokens` refunds ETH on enable-only batches
|
|
135
140
|
|
|
136
141
|
`mapTokens()` only uses `msg.value` when one or more mappings are being disabled and need transport payment for the final root flush. If every mapping in the batch is enable-only (`numberToDisable == 0`), the full `msg.value` is refunded to `_msgSender()`. If the refund transfer fails (e.g., the caller is a non-payable contract), the call reverts with `JBSucker_RefundFailed`. When disables are present, any dust remainder from integer division (`msg.value % numberToDisable`) is also refunded on a best-effort basis.
|