@bananapus/suckers-v6 0.0.24 → 0.0.26
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 +58 -184
- package/ARCHITECTURE.md +66 -36
- package/AUDIT_INSTRUCTIONS.md +26 -41
- package/README.md +23 -4
- package/RISKS.md +7 -2
- package/SKILLS.md +11 -3
- package/USER_JOURNEYS.md +83 -26
- 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/JBSuckerRegistry.sol +90 -0
- package/src/JBSwapCCIPSucker.sol +0 -1
- package/src/interfaces/IJBSuckerRegistry.sol +36 -0
- package/src/libraries/JBRelayBeneficiary.sol +56 -0
- 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/deployer.t.sol +145 -0
- package/test/unit/relay_beneficiary.t.sol +141 -0
package/ADMINISTRATION.md
CHANGED
|
@@ -1,211 +1,85 @@
|
|
|
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 |
|
|
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.
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Scope | Registry-managed sucker deployment plus project-local bridge mapping, deprecation, and safety control |
|
|
8
|
+
| Control posture | Mixed registry-owner, project-owner, and one-time deployer-configurator control |
|
|
9
|
+
| Highest-risk actions | Wrong token mapping, emergency hatch activation, unsafe deprecation handling, and misconfigured bridge constants |
|
|
10
|
+
| Recovery posture | Recovery usually means replacement sucker paths or deployers rather than in-place reversal |
|
|
21
11
|
|
|
22
|
-
##
|
|
12
|
+
## Purpose
|
|
23
13
|
|
|
24
|
-
-
|
|
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.
|
|
14
|
+
`nana-suckers-v6` has a layered control plane: registry ownership, project-local permissioned actions, and one-time deployer configuration for each bridge family. The most dangerous admin actions are token mapping, deprecation, emergency hatch activation, and deployer bridge-constant setup.
|
|
28
15
|
|
|
29
|
-
##
|
|
16
|
+
## Control Model
|
|
30
17
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
18
|
+
- `JBSuckerRegistry` is globally `Ownable`.
|
|
19
|
+
- Project-local authority flows through `JBPermissions`.
|
|
20
|
+
- `MAP_SUCKER_TOKEN`, `DEPLOY_SUCKERS`, `SUCKER_SAFETY`, and `SET_SUCKER_DEPRECATION` are the critical project-level permissions.
|
|
21
|
+
- Bridge deployers have a one-time configurator role for singleton and chain constants.
|
|
33
22
|
|
|
34
23
|
## Roles
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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. |
|
|
25
|
+
| Role | How Assigned | Scope | Notes |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| Registry owner | `Ownable(initialOwner)` | Global | Controls approved deployers and global `toRemoteFee` |
|
|
28
|
+
| Project owner | `JBProjects.ownerOf(projectId)` | Per project | May delegate project-local sucker permissions |
|
|
29
|
+
| Project operator | `JBPermissions` grant | Per project | Typically `DEPLOY_SUCKERS`, `MAP_SUCKER_TOKEN`, `SUCKER_SAFETY`, `SET_SUCKER_DEPRECATION` |
|
|
30
|
+
| Deployer configurator | Constructor `configurator` | Per deployer | One-time setup role for chain constants and singleton |
|
|
101
31
|
|
|
102
|
-
|
|
32
|
+
## Privileged Surfaces
|
|
103
33
|
|
|
104
|
-
|
|
|
105
|
-
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
34
|
+
| Contract | Function | Who Can Call | Effect |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `JBSuckerRegistry` | `allowSuckerDeployer(...)`, `removeSuckerDeployer(...)`, `setToRemoteFee(...)` | Registry owner | Controls global deployer allowlist and fee |
|
|
37
|
+
| `JBSuckerRegistry` | `deploySuckersFor(...)` | Project owner or `DEPLOY_SUCKERS` delegate | Deploys sucker pairs for a project |
|
|
38
|
+
| `JBSucker` | `mapToken(...)`, `mapTokens(...)` | Project owner or `MAP_SUCKER_TOKEN` delegate | Sets or disables token mappings |
|
|
39
|
+
| `JBSucker` | `enableEmergencyHatchFor(...)` | Project owner or `SUCKER_SAFETY` delegate | Irreversibly opens emergency exit for tokens |
|
|
40
|
+
| `JBSucker` | `setDeprecation(...)` | Project owner or `SET_SUCKER_DEPRECATION` delegate | Starts or cancels deprecation while allowed |
|
|
41
|
+
| `JBSuckerDeployer` variants | `configureSingleton(...)`, `setChainSpecificConstants(...)` | Configurator | One-time deployer setup |
|
|
108
42
|
|
|
109
|
-
##
|
|
43
|
+
## Immutable And One-Way
|
|
110
44
|
|
|
111
|
-
|
|
45
|
+
- Emergency hatch is irreversible for the affected token mapping.
|
|
46
|
+
- Deployer singleton and chain-constant setup are one-time.
|
|
47
|
+
- Deprecation becomes irreversible once the sucker reaches the disabled phase.
|
|
48
|
+
- Token mapping is constrained once outbox activity exists for that token.
|
|
112
49
|
|
|
113
|
-
|
|
114
|
-
ENABLED --> DEPRECATION_PENDING --> SENDING_DISABLED --> DEPRECATED
|
|
115
|
-
```
|
|
50
|
+
## Operational Notes
|
|
116
51
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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. |
|
|
52
|
+
- Map remote tokens carefully before meaningful bridge traffic accumulates.
|
|
53
|
+
- Use deprecation to create a controlled shutdown window instead of abrupt disablement.
|
|
54
|
+
- Treat emergency hatch as a last resort.
|
|
55
|
+
- Verify deployer singleton and chain constants before approving or using a deployer operationally.
|
|
56
|
+
- Treat fee-payment and bridge-send paths as best-effort in some variants; certain failures degrade into retained funds or local fallback claims rather than clean global rollback.
|
|
123
57
|
|
|
124
|
-
|
|
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.
|
|
58
|
+
## Machine Notes
|
|
129
59
|
|
|
130
|
-
|
|
60
|
+
- Do not assume registry ownership implies control over project-local mapping or emergency actions.
|
|
61
|
+
- Treat `src/JBSucker.sol`, `src/JBSuckerRegistry.sol`, and `src/deployers/` as the minimum admin source set.
|
|
62
|
+
- If live leaves, token mappings, or deprecation phase disagree with the planned action, stop and re-evaluate the recovery path.
|
|
63
|
+
- If a sucker variant uses try/catch around fee payment or inbound swaps, inspect the variant-specific recovery behavior before assuming failed bridge-side actions fully reverted.
|
|
131
64
|
|
|
132
|
-
##
|
|
65
|
+
## Recovery
|
|
133
66
|
|
|
134
|
-
The
|
|
135
|
-
|
|
136
|
-
|
|
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. |
|
|
67
|
+
- The normal recovery path is a new sucker path or a new deployer, not trying to re-enable an unsafe one.
|
|
68
|
+
- Emergency-hatched tokens recover through the defined local exit flow.
|
|
69
|
+
- Bad bridge-constant configuration generally means replacement deployers or replacement sucker instances.
|
|
70
|
+
- Some failure modes intentionally preserve liveness over strict rollback, so recovery may mean reconciling retained funds or retryable local claims rather than undoing the original send.
|
|
175
71
|
|
|
176
72
|
## Admin Boundaries
|
|
177
73
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
-
|
|
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`).
|
|
197
|
-
|
|
198
|
-
- **Cannot change the fee project.** `FEE_PROJECT_ID` is set at construction and is immutable. If the fee project's terminal changes or is removed, fee payments silently stop (best-effort design), but `toRemote()` still works.
|
|
199
|
-
|
|
200
|
-
- **Can adjust the fee amount, within bounds.** The registry owner can call `setToRemoteFee()` on `JBSuckerRegistry` to adjust the fee globally for all suckers, but it is capped at `MAX_TO_REMOTE_FEE` (0.001 ether).
|
|
201
|
-
|
|
202
|
-
## Bridge Infrastructure Risks
|
|
203
|
-
|
|
204
|
-
Sucker security ultimately depends on the underlying bridge infrastructure (OP Cross-Domain Messenger, Arbitrum Inbox/Outbox, CCIP Router). Each sucker's `_isRemotePeer()` check trusts these bridges to faithfully report the sender of cross-chain messages. If a bridge itself is compromised, an attacker could forge messages that pass the peer check, potentially minting unbacked project tokens on the destination chain.
|
|
205
|
-
|
|
206
|
-
**Mitigations available to project owners:**
|
|
74
|
+
- Registry owners cannot override project-local mapping or safety decisions directly.
|
|
75
|
+
- Project operators cannot reverse an emergency hatch.
|
|
76
|
+
- Project operators cannot force already sent leaves through the emergency hatch path.
|
|
77
|
+
- Nobody can mutate constructor immutables on live suckers or deployers.
|
|
207
78
|
|
|
208
|
-
|
|
209
|
-
- **Deprecation.** If the bridge is broadly compromised, the project owner (or `SET_SUCKER_DEPRECATION` delegate) can deprecate the entire sucker via `setDeprecation()`, shutting down all new outbound and eventually inbound activity.
|
|
79
|
+
## Source Map
|
|
210
80
|
|
|
211
|
-
|
|
81
|
+
- `src/JBSucker.sol`
|
|
82
|
+
- `src/JBSuckerRegistry.sol`
|
|
83
|
+
- `src/deployers/`
|
|
84
|
+
- `src/utils/MerkleLib.sol`
|
|
85
|
+
- `test/`
|
package/ARCHITECTURE.md
CHANGED
|
@@ -2,59 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`nana-suckers-v6` moves Juicebox project
|
|
5
|
+
`nana-suckers-v6` moves Juicebox project-token value across chains. A sucker pair lets a holder destroy or consume a local project-token position, bridge the corresponding terminal-side value plus a Merkle root, and later claim equivalent value on the remote chain.
|
|
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` defines the chain-agnostic prepare, relay, and claim lifecycle. Chain-specific implementations such as `JBOptimismSucker`, `JBArbitrumSucker`, `JBCCIPSucker`, `JBSwapCCIPSucker`, `JBBaseSucker`, and `JBCeloSucker` handle transport-specific details. `JBSuckerRegistry` governs deployment inventory and shared policy, while deployers create deterministic clones for each supported transport family.
|
|
13
10
|
|
|
14
|
-
##
|
|
11
|
+
## Core Invariants
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| `MerkleLib` and helper libraries | Incremental tree logic plus chain-specific constants |
|
|
13
|
+
- Inbox and outbox trees must remain append-only and proof-compatible across chains.
|
|
14
|
+
- Token mapping is part of economic correctness; a wrong mapping is a value-loss bug.
|
|
15
|
+
- Deprecation or emergency controls must not break already-bridged claims.
|
|
16
|
+
- Roots may arrive out of order. Newer nonces replace older inbox roots, so claims must stay provable against the latest append-only tree.
|
|
17
|
+
- Root reception and token mapping are intentionally decoupled. Accepting a root for an unmapped token is valid if later mapping is what makes the claim redeemable.
|
|
18
|
+
- Transport-specific implementations may differ operationally, but they must preserve the same logical prepare-to-claim lifecycle.
|
|
23
19
|
|
|
24
|
-
##
|
|
20
|
+
## Modules
|
|
21
|
+
|
|
22
|
+
| Module | Responsibility | Notes |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `JBSucker` | Prepare, root management, claim verification, token mapping, deprecation | Chain-agnostic base |
|
|
25
|
+
| chain-specific suckers | Transport details for OP Stack, Arbitrum, CCIP, Base, and Celo | Bridge-specific subclasses |
|
|
26
|
+
| `JBSuckerRegistry` | Deployer allowlist, inventory, and global bridge-fee policy | Shared policy surface |
|
|
27
|
+
| deployers | Deterministic clone deployment and initialization | One per transport family |
|
|
28
|
+
| `MerkleLib` and helper libraries | Incremental tree logic and chain constants | Proof-critical |
|
|
29
|
+
|
|
30
|
+
## Trust Boundaries
|
|
31
|
+
|
|
32
|
+
- Project-token semantics and local terminal accounting remain rooted in `nana-core-v6`.
|
|
33
|
+
- Transport assumptions come from native bridge infrastructure for each chain family.
|
|
34
|
+
- Permission IDs come from `nana-permission-ids-v6`.
|
|
35
|
+
|
|
36
|
+
## Critical Flows
|
|
37
|
+
|
|
38
|
+
### Prepare, Relay, Claim
|
|
25
39
|
|
|
26
40
|
```text
|
|
27
41
|
holder prepares a bridge
|
|
28
42
|
-> sucker cashes out or consumes the local project-token position
|
|
29
|
-
-> sucker inserts a
|
|
30
|
-
-> someone
|
|
43
|
+
-> sucker inserts a Merkle leaf into the outbox tree
|
|
44
|
+
-> someone relays funds and the latest root to the remote sucker
|
|
45
|
+
-> remote sucker may accept a later nonce before an earlier one, updating shared cross-chain snapshots to the freshest project-wide message
|
|
31
46
|
-> claimant proves inclusion against the remote inbox tree
|
|
32
|
-
-> remote sucker releases or remints
|
|
47
|
+
-> remote sucker releases or remints destination-side value
|
|
33
48
|
```
|
|
34
49
|
|
|
35
|
-
##
|
|
36
|
-
|
|
37
|
-
- Inbox and outbox tree updates must remain append-only and proof-compatible across chains.
|
|
38
|
-
- Token mapping is part of bridge correctness; a wrong remote token mapping is a value-loss bug.
|
|
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.
|
|
50
|
+
## Accounting Model
|
|
41
51
|
|
|
42
|
-
|
|
52
|
+
The repo does not replace local treasury accounting. It owns bridge-specific claim accounting: outbox leaves, inbox roots, token mappings, replay protection, and the transition from local destruction to remote claimability.
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
- Tree state, token mapping, and claim replay protection all carry cross-chain blast radius.
|
|
46
|
-
- Registry policy matters because deployment mistakes are hard to repair after pairs exist on multiple chains.
|
|
54
|
+
`JBSwapCCIPSucker` adds another accounting layer on top of the base lifecycle: nonce-indexed conversion rates. A claim can be temporarily blocked while a failed swap is pending retry, and successful claims are scaled against the conversion rate recorded for that batch's nonce.
|
|
47
55
|
|
|
48
|
-
##
|
|
56
|
+
## Security Model
|
|
49
57
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
58
|
+
- Tree state, token mapping, and replay protection have cross-chain blast radius.
|
|
59
|
+
- Each transport backend has distinct failure modes and native-token quirks.
|
|
60
|
+
- Out-of-order message delivery is part of the trust model, not an exception path. Proof generation and monitoring must tolerate stale-root rejection and regenerated proofs against the newest root.
|
|
61
|
+
- Emergency hatch and deprecation flows are designed to preserve already-bridged exits. Post-deprecation root acceptance is intentional so in-flight messages do not strand users.
|
|
62
|
+
- Registry policy matters because bad deployments are hard to repair once pairs exist on multiple chains.
|
|
53
63
|
|
|
54
64
|
## Safe Change Guide
|
|
55
65
|
|
|
56
66
|
- Review every cross-chain change from both sides of the pair.
|
|
57
|
-
- Do not change
|
|
58
|
-
- Keep registry policy, deployer configuration, and singleton
|
|
59
|
-
-
|
|
60
|
-
-
|
|
67
|
+
- Do not change Merkle leaf encoding casually.
|
|
68
|
+
- Keep registry policy, deployer configuration, and singleton initialization aligned.
|
|
69
|
+
- If you change root or snapshot nonce handling, re-check out-of-order delivery behavior and whether older claims remain provable against the newest root.
|
|
70
|
+
- If you change CCIP swap handling, re-check pending-swap claim blocking and per-batch conversion-rate lookups together.
|
|
71
|
+
- Test chain-specific wrapping and native-token handling separately from the abstract lifecycle.
|
|
72
|
+
|
|
73
|
+
## Canonical Checks
|
|
74
|
+
|
|
75
|
+
- peer snapshot and remote-state synchronization:
|
|
76
|
+
`test/audit/codex-PeerSnapshotDesync.t.sol`
|
|
77
|
+
- deprecation and stranded-destination handling:
|
|
78
|
+
`test/audit/DeprecatedSuckerDestination.t.sol`
|
|
79
|
+
- peer-chain state accounting:
|
|
80
|
+
`test/unit/peer_chain_state.t.sol`
|
|
81
|
+
|
|
82
|
+
## Source Map
|
|
83
|
+
|
|
84
|
+
- `src/JBSucker.sol`
|
|
85
|
+
- `src/JBSuckerRegistry.sol`
|
|
86
|
+
- `src/deployers/`
|
|
87
|
+
- `src/utils/MerkleLib.sol`
|
|
88
|
+
- `test/audit/codex-PeerSnapshotDesync.t.sol`
|
|
89
|
+
- `test/audit/DeprecatedSuckerDestination.t.sol`
|
|
90
|
+
- `test/unit/peer_chain_state.t.sol`
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This repo bridges Juicebox project tokens and associated terminal assets across chains. Audit it as a conservation and replay-prevention system.
|
|
4
4
|
|
|
5
|
-
## Objective
|
|
5
|
+
## Audit Objective
|
|
6
6
|
|
|
7
7
|
Find issues that:
|
|
8
8
|
- allow double claim, replay, or claim on the wrong destination
|
|
@@ -32,7 +32,7 @@ Read in this order:
|
|
|
32
32
|
|
|
33
33
|
That order gets you from the shared conservation model to the transport-specific deviations.
|
|
34
34
|
|
|
35
|
-
##
|
|
35
|
+
## Security Model
|
|
36
36
|
|
|
37
37
|
The bridge flow is:
|
|
38
38
|
- burn or prepare project-token value on source chain
|
|
@@ -52,6 +52,21 @@ One non-obvious property to audit explicitly:
|
|
|
52
52
|
- the system is intentionally designed to survive some transport mismatch without deadlocking
|
|
53
53
|
- those recovery choices are exactly where conservation bugs tend to hide
|
|
54
54
|
|
|
55
|
+
## Roles And Privileges
|
|
56
|
+
|
|
57
|
+
| Role | Powers | How constrained |
|
|
58
|
+
|------|--------|-----------------|
|
|
59
|
+
| Source-side caller | Prepare and bridge value to a remote chain | Must not create more claimable value than was prepared |
|
|
60
|
+
| Remote peer and messenger | Install new roots and deliver assets | Must be authenticated per transport |
|
|
61
|
+
| Emergency authority | Deprecate paths or enable recovery exits | Must not be able to steal in-flight funds |
|
|
62
|
+
|
|
63
|
+
## Integration Assumptions
|
|
64
|
+
|
|
65
|
+
| Dependency | Assumption | What breaks if wrong |
|
|
66
|
+
|------------|------------|----------------------|
|
|
67
|
+
| Bridge transport | Delivers only authenticated peer messages | Anyone can spoof remote state |
|
|
68
|
+
| Token mapping and registry state | Remote asset identity stays stable | Users claim the wrong asset or wrong meaning |
|
|
69
|
+
|
|
55
70
|
## Critical Invariants
|
|
56
71
|
|
|
57
72
|
1. Cross-chain conservation
|
|
@@ -72,22 +87,7 @@ Remote token mappings must be immutable or mutable only exactly where the design
|
|
|
72
87
|
6. Nonce progression is monotonic in the way each transport expects
|
|
73
88
|
Later roots must not silently invalidate earlier user claims unless the protocol explicitly intends that recovery path.
|
|
74
89
|
|
|
75
|
-
##
|
|
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
|
|
90
|
+
## Attack Surfaces
|
|
91
91
|
|
|
92
92
|
- `prepare`, `toRemote`, `fromRemote`, and `claim`
|
|
93
93
|
- bitmap execution tracking
|
|
@@ -96,33 +96,18 @@ The strongest attacker models here are:
|
|
|
96
96
|
- chain-specific messenger authentication
|
|
97
97
|
- deployer address derivation and clone setup
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
4. Same logical transfer across different sucker implementations to check for replay or identity confusion.
|
|
99
|
+
Replay these sequences:
|
|
100
|
+
1. prepare multiple leaves, send multiple roots, receive them out of order, and attempt each claim
|
|
101
|
+
2. prepare, deprecate or enable emergency hatch, then race claim and exit paths
|
|
102
|
+
3. map a token, prepare a transfer, then attempt remap or peer mismatch after value is in flight
|
|
103
|
+
4. replay the same logical transfer across different sucker implementations
|
|
105
104
|
|
|
106
|
-
##
|
|
105
|
+
## Accepted Risks Or Behaviors
|
|
107
106
|
|
|
108
|
-
|
|
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
|
|
107
|
+
- Out-of-order arrival is part of the intended model, not an edge case.
|
|
113
108
|
|
|
114
|
-
##
|
|
109
|
+
## Verification
|
|
115
110
|
|
|
116
|
-
Standard workflow:
|
|
117
111
|
- `npm install`
|
|
118
112
|
- `forge build`
|
|
119
113
|
- `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
|
|
|
@@ -50,7 +55,7 @@ The shortest useful reading order is:
|
|
|
50
55
|
| Contract | Description |
|
|
51
56
|
|----------|-------------|
|
|
52
57
|
| [`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`)
|
|
58
|
+
| [`JBCCIPSucker`](src/JBCCIPSucker.sol) | Extends `JBSucker`. Bridges via Chainlink CCIP (`ccipSend`/`ccipReceive`) for chain pairs whose router, selector, and token mapping are configured. 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
59
|
| [`JBOptimismSucker`](src/JBOptimismSucker.sol) | Extends `JBSucker`. Bridges via OP Standard Bridge + OP Messenger. No `msg.value` required for transport. |
|
|
55
60
|
| [`JBBaseSucker`](src/JBBaseSucker.sol) | Thin wrapper around `JBOptimismSucker` with Base chain IDs (Ethereum 1 <-> Base 8453, Sepolia 11155111 <-> Base Sepolia 84532). |
|
|
56
61
|
| [`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). |
|
|
@@ -63,7 +68,7 @@ The shortest useful reading order is:
|
|
|
63
68
|
| [`JBCeloSuckerDeployer`](src/deployers/JBCeloSuckerDeployer.sol) | Deployer for `JBCeloSucker`. Extends `JBOptimismSuckerDeployer` with `wrappedNative` (`IWrappedNativeToken`) storage for the local chain's WETH address. |
|
|
64
69
|
| [`JBArbitrumSuckerDeployer`](src/deployers/JBArbitrumSuckerDeployer.sol) | Deployer for `JBArbitrumSucker`. Stores Arbitrum Inbox, Gateway Router, and layer (`JBLayer.L1` or `JBLayer.L2`). |
|
|
65
70
|
| [`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
|
|
71
|
+
| [`CCIPHelper`](src/libraries/CCIPHelper.sol) | CCIP router addresses, chain selectors, and WETH addresses for the chain set currently encoded in this repo. |
|
|
67
72
|
| [`ARBAddresses`](src/libraries/ARBAddresses.sol) | Arbitrum bridge contract addresses (Inbox, Gateway Router) for mainnet and Sepolia. |
|
|
68
73
|
| [`ARBChains`](src/libraries/ARBChains.sol) | Arbitrum chain ID constants. |
|
|
69
74
|
|
|
@@ -90,6 +95,14 @@ The shortest useful reading order is:
|
|
|
90
95
|
|
|
91
96
|
When reviewing a bridge incident, check local state transition correctness before blaming the transport layer.
|
|
92
97
|
|
|
98
|
+
## High-Signal Tests
|
|
99
|
+
|
|
100
|
+
1. `test/unit/registry.t.sol`
|
|
101
|
+
2. `test/unit/multi_chain_evolution.t.sol`
|
|
102
|
+
3. `test/ForkClaimMainnet.t.sol`
|
|
103
|
+
4. `test/audit/codex-PeerSnapshotDesync.t.sol`
|
|
104
|
+
5. `test/audit/codex-ToRemoteFeeIrrecoverable.t.sol`
|
|
105
|
+
|
|
93
106
|
## Install
|
|
94
107
|
|
|
95
108
|
```bash
|
|
@@ -112,7 +125,7 @@ Useful scripts:
|
|
|
112
125
|
|
|
113
126
|
## Deployment Notes
|
|
114
127
|
|
|
115
|
-
This package supports multiple bridge families and is intentionally split into bridge-specific deployers.
|
|
128
|
+
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
129
|
|
|
117
130
|
## Repository Layout
|
|
118
131
|
|
|
@@ -142,3 +155,9 @@ script/
|
|
|
142
155
|
- a bridge that stays live operationally still may not be economically safe for every asset or chain pair
|
|
143
156
|
|
|
144
157
|
When debugging a bad cross-chain outcome, first decide whether the failure is in claim construction, message transport, inbox/outbox root progression, or remote settlement. Those are different bug classes.
|
|
158
|
+
|
|
159
|
+
## For AI Agents
|
|
160
|
+
|
|
161
|
+
- Do not summarize this repo as a generic token bridge; it bridges Juicebox project positions plus transported value.
|
|
162
|
+
- Always separate shared sucker logic from bridge-specific transport behavior in your explanation.
|
|
163
|
+
- Use the chain-specific implementation and the 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.
|