@bananapus/suckers-v6 0.0.26 → 0.0.28
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 +10 -69
- package/ARCHITECTURE.md +12 -68
- package/AUDIT_INSTRUCTIONS.md +13 -93
- package/README.md +13 -43
- package/RISKS.md +14 -2
- package/SKILLS.md +9 -34
- package/USER_JOURNEYS.md +24 -116
- package/package.json +1 -1
- package/src/JBArbitrumSucker.sol +21 -6
- package/src/JBCCIPSucker.sol +4 -2
- package/src/JBSucker.sol +4 -1
- package/src/JBSuckerRegistry.sol +132 -12
- package/src/JBSwapCCIPSucker.sol +29 -5
- package/src/interfaces/IL1ArbitrumGateway.sol +23 -0
- package/src/libraries/JBCCIPLib.sol +19 -6
- package/src/libraries/JBSwapPoolLib.sol +4 -6
- package/test/ForkClaimMainnet.t.sol +5 -2
- package/test/ForkMainnet.t.sol +9 -8
- package/test/ForkSwapMainnet.t.sol +10 -8
- package/test/SuckerAttacks.t.sol +1 -1
- package/test/SuckerRegressions.t.sol +1 -1
- package/test/audit/CertikAIScan.t.sol +352 -0
- package/test/audit/DeprecatedSuckerAggregateViews.t.sol +247 -0
- package/test/audit/ZeroOutputSwapPending.t.sol +218 -0
- package/test/audit/codex-nemesis-DeprecatedRemovalUndercount.t.sol +141 -0
- package/test/audit/codex-nemesis-SwapZeroLocalTotalUnbackedClaim.t.sol +179 -0
- package/test/unit/deployer.t.sol +9 -8
- package/test/unit/emergency.t.sol +2 -2
- package/test/unit/multi_chain_evolution.t.sol +2 -2
- package/test/unit/pool_discovery.t.sol +366 -0
package/ADMINISTRATION.md
CHANGED
|
@@ -4,82 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
| Item | Details |
|
|
6
6
|
| --- | --- |
|
|
7
|
-
| Scope |
|
|
8
|
-
| Control posture | Mixed registry-owner, project-
|
|
9
|
-
| Highest-risk actions | Wrong token mapping,
|
|
10
|
-
| Recovery posture |
|
|
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 |
|
|
11
11
|
|
|
12
12
|
## Purpose
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
This repo controls the shared lifecycle around bridging project positions, not just the transport call itself.
|
|
15
15
|
|
|
16
16
|
## Control Model
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- Bridge deployers have a one-time configurator role for singleton and chain constants.
|
|
22
|
-
|
|
23
|
-
## Roles
|
|
24
|
-
|
|
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 |
|
|
31
|
-
|
|
32
|
-
## Privileged Surfaces
|
|
33
|
-
|
|
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 |
|
|
42
|
-
|
|
43
|
-
## Immutable And One-Way
|
|
44
|
-
|
|
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.
|
|
49
|
-
|
|
50
|
-
## Operational Notes
|
|
51
|
-
|
|
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.
|
|
57
|
-
|
|
58
|
-
## Machine Notes
|
|
59
|
-
|
|
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.
|
|
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
|
|
64
21
|
|
|
65
22
|
## Recovery
|
|
66
23
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
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.
|
|
71
|
-
|
|
72
|
-
## Admin Boundaries
|
|
73
|
-
|
|
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.
|
|
78
|
-
|
|
79
|
-
## Source Map
|
|
24
|
+
- emergency hatch and deprecation are the main recovery tools
|
|
25
|
+
- both are intentionally conservative and often one-way
|
|
80
26
|
|
|
81
|
-
- `src/JBSucker.sol`
|
|
82
|
-
- `src/JBSuckerRegistry.sol`
|
|
83
|
-
- `src/deployers/`
|
|
84
|
-
- `src/utils/MerkleLib.sol`
|
|
85
|
-
- `test/`
|
package/ARCHITECTURE.md
CHANGED
|
@@ -2,89 +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
|
-
`JBSucker`
|
|
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.
|
|
10
10
|
|
|
11
11
|
## Core Invariants
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- Transport-specific implementations may differ operationally, but they must preserve the same logical prepare-to-claim lifecycle.
|
|
19
|
-
|
|
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 |
|
|
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
|
|
29
18
|
|
|
30
19
|
## Trust Boundaries
|
|
31
20
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
## Critical Flows
|
|
37
|
-
|
|
38
|
-
### Prepare, Relay, Claim
|
|
39
|
-
|
|
40
|
-
```text
|
|
41
|
-
holder prepares a bridge
|
|
42
|
-
-> sucker cashes out or consumes the local project-token position
|
|
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
|
|
46
|
-
-> claimant proves inclusion against the remote inbox tree
|
|
47
|
-
-> remote sucker releases or remints destination-side value
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Accounting Model
|
|
51
|
-
|
|
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.
|
|
53
|
-
|
|
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.
|
|
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
|
|
55
24
|
|
|
56
25
|
## Security Model
|
|
57
26
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
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.
|
|
63
|
-
|
|
64
|
-
## Safe Change Guide
|
|
65
|
-
|
|
66
|
-
- Review every cross-chain change from both sides of the pair.
|
|
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`
|
|
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
|
|
81
29
|
|
|
82
30
|
## Source Map
|
|
83
31
|
|
|
84
32
|
- `src/JBSucker.sol`
|
|
85
33
|
- `src/JBSuckerRegistry.sol`
|
|
86
|
-
- `src/deployers/`
|
|
87
34
|
- `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
|
@@ -1,110 +1,30 @@
|
|
|
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
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
|
-
## Security 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
|
-
## 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
|
-
|
|
70
|
-
## Critical Invariants
|
|
71
|
-
|
|
72
|
-
1. Cross-chain conservation
|
|
73
|
-
For any prepared transfer, destination claimable value must not exceed what the source side actually prepared and backed.
|
|
74
|
-
|
|
75
|
-
2. Single execution
|
|
76
|
-
Each bridged leaf must be claimable at most once on the destination and at most once via emergency exit.
|
|
77
|
-
|
|
78
|
-
3. Peer authenticity
|
|
79
|
-
Only the intended remote peer and messenger path may update inbox roots.
|
|
80
|
-
|
|
81
|
-
4. Deprecation safety
|
|
82
|
-
Deprecation and emergency-hatch controls must not let callers bypass intended restrictions or steal in-flight funds.
|
|
83
|
-
|
|
84
|
-
5. Token mapping integrity
|
|
85
|
-
Remote token mappings must be immutable or mutable only exactly where the design allows.
|
|
86
|
-
|
|
87
|
-
6. Nonce progression is monotonic in the way each transport expects
|
|
88
|
-
Later roots must not silently invalidate earlier user claims unless the protocol explicitly intends that recovery path.
|
|
89
|
-
|
|
90
|
-
## Attack Surfaces
|
|
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
|
-
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
|
|
104
|
-
|
|
105
|
-
## Accepted Risks Or Behaviors
|
|
106
|
-
|
|
107
|
-
- Out-of-order arrival is part of the intended model, not an edge case.
|
|
25
|
+
1. `src/JBSucker.sol`
|
|
26
|
+
2. `src/JBSuckerRegistry.sol`
|
|
27
|
+
3. the relevant bridge-specific implementation
|
|
108
28
|
|
|
109
29
|
## Verification
|
|
110
30
|
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ The base implementation is extended for multiple bridge families so the same pro
|
|
|
24
24
|
|
|
25
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.
|
|
26
26
|
|
|
27
|
-
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."
|
|
28
28
|
|
|
29
29
|
## Key Contracts
|
|
30
30
|
|
|
@@ -32,11 +32,7 @@ The main idea is not "bridge the token contract." The main idea is "bridge a Jui
|
|
|
32
32
|
| --- | --- |
|
|
33
33
|
| `JBSucker` | Base bridge logic for prepare, relay, claim, token mapping, and lifecycle controls. |
|
|
34
34
|
| `JBSuckerRegistry` | Registry for per-project sucker deployments, deployer allowlists, and shared bridge fee settings. |
|
|
35
|
-
|
|
|
36
|
-
| `JBBaseSucker` | Base-flavored OP Stack implementation. |
|
|
37
|
-
| `JBCeloSucker` | OP Stack implementation adapted for Celo's native asset behavior. |
|
|
38
|
-
| `JBArbitrumSucker` | Arbitrum bridge implementation. |
|
|
39
|
-
| `JBCCIPSucker` | Chainlink CCIP-based implementation for CCIP-connected chains. |
|
|
35
|
+
| Chain-specific suckers | Transport-specific implementations for OP Stack, Arbitrum, CCIP, and related environments. |
|
|
40
36
|
|
|
41
37
|
## Mental Model
|
|
42
38
|
|
|
@@ -50,28 +46,6 @@ That means every bridge path has two trust surfaces:
|
|
|
50
46
|
- the shared sucker accounting and Merkle logic
|
|
51
47
|
- the bridge-specific transport implementation
|
|
52
48
|
|
|
53
|
-
The shortest useful reading order is:
|
|
54
|
-
|
|
55
|
-
| Contract | Description |
|
|
56
|
-
|----------|-------------|
|
|
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. |
|
|
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). |
|
|
59
|
-
| [`JBOptimismSucker`](src/JBOptimismSucker.sol) | Extends `JBSucker`. Bridges via OP Standard Bridge + OP Messenger. No `msg.value` required for transport. |
|
|
60
|
-
| [`JBBaseSucker`](src/JBBaseSucker.sol) | Thin wrapper around `JBOptimismSucker` with Base chain IDs (Ethereum 1 <-> Base 8453, Sepolia 11155111 <-> Base Sepolia 84532). |
|
|
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). |
|
|
62
|
-
| [`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. |
|
|
63
|
-
| [`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. |
|
|
64
|
-
| [`JBSuckerDeployer`](src/JBSuckerDeployer.sol) | Abstract base deployer. Clones a singleton sucker via `LibClone.cloneDeterministic` and initializes it. Two-phase setup: `setChainSpecificConstants` then `configureSingleton`. |
|
|
65
|
-
| [`JBCCIPSuckerDeployer`](src/deployers/JBCCIPSuckerDeployer.sol) | Deployer for `JBCCIPSucker`. Stores CCIP router, remote chain ID, and CCIP chain selector. |
|
|
66
|
-
| [`JBOptimismSuckerDeployer`](src/deployers/JBOptimismSuckerDeployer.sol) | Deployer for `JBOptimismSucker`. Stores OP Messenger and OP Bridge addresses. |
|
|
67
|
-
| [`JBBaseSuckerDeployer`](src/deployers/JBBaseSuckerDeployer.sol) | Thin wrapper around `JBOptimismSuckerDeployer` for Base. |
|
|
68
|
-
| [`JBCeloSuckerDeployer`](src/deployers/JBCeloSuckerDeployer.sol) | Deployer for `JBCeloSucker`. Extends `JBOptimismSuckerDeployer` with `wrappedNative` (`IWrappedNativeToken`) storage for the local chain's WETH address. |
|
|
69
|
-
| [`JBArbitrumSuckerDeployer`](src/deployers/JBArbitrumSuckerDeployer.sol) | Deployer for `JBArbitrumSucker`. Stores Arbitrum Inbox, Gateway Router, and layer (`JBLayer.L1` or `JBLayer.L2`). |
|
|
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()`. |
|
|
71
|
-
| [`CCIPHelper`](src/libraries/CCIPHelper.sol) | CCIP router addresses, chain selectors, and WETH addresses for the chain set currently encoded in this repo. |
|
|
72
|
-
| [`ARBAddresses`](src/libraries/ARBAddresses.sol) | Arbitrum bridge contract addresses (Inbox, Gateway Router) for mainnet and Sepolia. |
|
|
73
|
-
| [`ARBChains`](src/libraries/ARBChains.sol) | Arbitrum chain ID constants. |
|
|
74
|
-
|
|
75
49
|
## Read These Files First
|
|
76
50
|
|
|
77
51
|
1. `src/JBSucker.sol`
|
|
@@ -82,18 +56,16 @@ The shortest useful reading order is:
|
|
|
82
56
|
|
|
83
57
|
## Integration Traps
|
|
84
58
|
|
|
85
|
-
- do not reason about suckers as if they were generic ERC-20 bridges
|
|
86
|
-
- root ordering and message delivery semantics matter as much as
|
|
87
|
-
- token mapping is part of the economic invariant
|
|
88
|
-
- 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
|
|
89
63
|
|
|
90
64
|
## Where State Lives
|
|
91
65
|
|
|
92
|
-
- per-claim and tree progression state
|
|
93
|
-
- deployment inventory and shared operational config
|
|
94
|
-
- bridge transport assumptions
|
|
95
|
-
|
|
96
|
-
When reviewing a bridge incident, check local state transition correctness before blaming the transport layer.
|
|
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
|
|
97
69
|
|
|
98
70
|
## High-Signal Tests
|
|
99
71
|
|
|
@@ -149,15 +121,13 @@ script/
|
|
|
149
121
|
|
|
150
122
|
## Risks And Notes
|
|
151
123
|
|
|
152
|
-
- 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
|
|
153
125
|
- bridge-specific transport assumptions matter as much as the shared sucker logic
|
|
154
126
|
- token mapping and deprecation controls are governance-sensitive surfaces
|
|
155
127
|
- a bridge that stays live operationally still may not be economically safe for every asset or chain pair
|
|
156
128
|
|
|
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
129
|
## For AI Agents
|
|
160
130
|
|
|
161
|
-
- Do not summarize this repo as a generic token bridge
|
|
162
|
-
- Always separate shared sucker logic from bridge-specific transport behavior
|
|
163
|
-
- Use the chain-specific implementation and
|
|
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
|
@@ -24,6 +24,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
|
|
|
24
24
|
- **CCIP:** trusts `CCIP_ROUTER` identity plus `any2EvmMessage.sender` and `sourceChainSelector`. The router address is immutable at deploy time -- if Chainlink rotates routers, the sucker is bricked (no upgrade path).
|
|
25
25
|
- **CREATE2 peer assumption.** `peer()` defaults to `address(this)`, assuming deterministic cross-chain deployment. Breaks if deployer address, init code, or factory nonce differs across chains. Incorrect peer = permanent fund loss (messages accepted from nobody, or routed to wrong address).
|
|
26
26
|
- **Controller/terminal must exist on destination chain.** `_handleClaim` calls `controllerOf(projectId).mintTokensOf()`. If the project does not exist or has no controller on the remote chain, all claims permanently revert -- funds are stuck.
|
|
27
|
+
- **Registry allowlisting does not verify deployer singleton provenance.** `JBSuckerRegistry.deploySuckersFor()` only checks the deployer allowlist, not the singleton implementation behind the deployer. `configureSingleton()` can point an approved deployer at any JBSucker singleton. This is a privileged-only concern (both deployer configuration and registry allowlisting require governance). Defense: validate deployer configuration during registry allowlist reviews.
|
|
27
28
|
- **No reentrancy guard.** The contract relies on state ordering (mark-executed-before-external-call) rather than explicit ReentrancyGuard. Correct today, but fragile to future refactors.
|
|
28
29
|
|
|
29
30
|
## 2. Merkle Tree Risks
|
|
@@ -41,6 +42,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
|
|
|
41
42
|
- **CCIP: no guaranteed delivery order.** CCIP does not guarantee in-order delivery. The contract handles this by accepting any nonce > current, but concurrent `toRemote` calls for the same token could result in a later root arriving first, skipping an intermediate root.
|
|
42
43
|
- **OP Stack: generally ordered** but the L1-to-L2 message must be relayed by an off-chain actor. A delayed or dropped relay permanently blocks claims until someone retries the relay.
|
|
43
44
|
- **Message loss.** If an AMB silently fails (accepts the message on L1 but never delivers to L2), `numberOfClaimsSent` is incremented but the remote peer never receives the root. Those leaves are blocked from both remote claim and local emergency exit (conservative: locked, not double-spent). Recovery requires enabling the emergency hatch.
|
|
45
|
+
- **Aggregate balance accounting.** `amountToAddToBalanceOf(token)` computes `balanceOf(this) - outbox.balance`, making all contract-held tokens fungible claim backing. This is intentional: all funds serve the same project, so refunded ETH (from failed Arbitrum retryable tickets) and stale native deliveries correctly become project-claimable. The tradeoff is that later roots can consume liquidity from earlier failed batches, but the project is the ultimate beneficiary in all cases.
|
|
44
46
|
- **`fromRemote` does not revert on stale nonce.** By design, stale/duplicate messages are silently ignored (emitting `StaleRootRejected`). This prevents fund loss on native token transfers where reverting would lose the ETH, but means monitoring must watch for this event to detect bridge issues.
|
|
45
47
|
|
|
46
48
|
## 4. Token Mapping Risks
|
|
@@ -62,7 +64,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
|
|
|
62
64
|
- **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
65
|
- **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
66
|
- **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`
|
|
67
|
+
- **Registry aggregate views fail open.** `JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` include both active and deprecated suckers with per-chain deduplication (max per chain, not sum). When multiple suckers target the same peer chain (e.g., during migration), the highest reported value is used. The views 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, or simply expensive to query.
|
|
66
68
|
|
|
67
69
|
## 6. Deprecation Lifecycle
|
|
68
70
|
|
|
@@ -83,6 +85,8 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
|
|
|
83
85
|
- **Claim vs emergency exit use separate bitmap slots.** Emergency exit uses `_executedFor[keccak256(abi.encode(terminalToken))]` while regular claims use `_executedFor[terminalToken]`. This means a leaf that was emergency-exited locally could theoretically also be claimed remotely if the root was already sent -- double-spend is prevented only by the `numberOfClaimsSent` check.
|
|
84
86
|
- **`numberOfClaimsSent` is the critical guard.** Emergency exit reverts if `outbox.numberOfClaimsSent != 0 && outbox.numberOfClaimsSent - 1 >= index`. The `numberOfClaimsSent != 0` precondition prevents underflow when no root has ever been sent — in that case, all leaves are available for emergency exit. This means leaves at indices below `numberOfClaimsSent` cannot be emergency-exited (they may have been sent to the remote peer). If `_sendRootOverAMB` silently fails, these leaves are permanently locked.
|
|
85
87
|
- **Emergency exit decrements `outbox.balance`.** If emergency exits drain the outbox balance below the amount that was already sent to the bridge, the accounting becomes inconsistent. The contract guards against this by only allowing exit for unsent leaves.
|
|
88
|
+
- **Emergency exit recipient is the leaf beneficiary.** `exitThroughEmergencyHatch()` refunds to `claimData.leaf.beneficiary`, not the original `prepare()` caller. The depositor chose this beneficiary when preparing the bridge; the leaf structure does not store the depositor address. If Alice prepares for Bob and the bridge fails, Bob gets the emergency refund, not Alice. This is the intended behavior: the depositor delegated their claim to the beneficiary.
|
|
89
|
+
- **`numberOfClaimsSent` advancement timing.** `_sendRoot()` sets `numberOfClaimsSent` before `_sendRootOverAMB()` completes. If the L1 transaction succeeds but L2 delivery fails, those leaves are blocked from emergency exit. Mitigations: Arbitrum retryable tickets can be manually re-executed on L2; Optimism messages can be re-relayed by anyone. If delivery permanently fails, `enableEmergencyHatchFor()` combined with project owner intervention can recover. Adding a rollback mechanism would introduce double-spend risk (leaf claimable on both chains). Current design is conservative: locked funds are preferable to double-spent funds.
|
|
86
90
|
- **Emergency hatch + minting.** Emergency exit calls `_handleClaim`, which mints project tokens via the controller. If the controller or token contract is broken/missing, emergency exits also revert -- there is no "raw withdrawal" of terminal tokens without minting.
|
|
87
91
|
|
|
88
92
|
## 8. DoS Vectors
|
|
@@ -130,7 +134,11 @@ The fee is paid to `FEE_PROJECT_ID` (the protocol project), not to the sucker's
|
|
|
130
134
|
|
|
131
135
|
### 10.5 Registry aggregate views prioritize liveness over completeness
|
|
132
136
|
|
|
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.
|
|
137
|
+
`JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` intentionally use `try/catch` around each sucker and silently ignore peers that revert. Both active and deprecated suckers are included, with per-chain deduplication: when multiple suckers target the same peer chain (e.g., during a migration window), the highest reported value is used to avoid double-counting. 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.
|
|
138
|
+
|
|
139
|
+
### 10.8 Zero-output swap batches route to pendingSwapOf
|
|
140
|
+
|
|
141
|
+
When `JBSwapCCIPSucker.ccipReceive` receives bridge tokens and the swap succeeds but returns zero local tokens (e.g., due to extreme price impact or dust amounts), the batch is routed to `pendingSwapOf` for later retry via `retrySwap`. Without this, claims would proceed with zero terminal backing, minting unbacked project tokens. The trade-off is that these batches require a manual `retrySwap` call once pool conditions improve. Anyone can call `retrySwap` — it is permissionless.
|
|
134
142
|
|
|
135
143
|
### 10.6 Hookless V4 spot pricing is sandwich-vulnerable by design
|
|
136
144
|
|
|
@@ -139,3 +147,7 @@ When the only available Uniswap pool for a cross-denomination swap is a hookless
|
|
|
139
147
|
### 10.7 `mapTokens` refunds ETH on enable-only batches
|
|
140
148
|
|
|
141
149
|
`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.
|
|
150
|
+
|
|
151
|
+
### 10.9 Zero-value `prepare()` is allowed
|
|
152
|
+
|
|
153
|
+
`prepare()` does not reject `projectTokenCount == 0`. A zero-value check would be trivially bypassed by passing `1` instead, so it provides no real protection against remap-window consumption. The cost to create a leaf with `projectTokenCount = 1` is negligible (1 wei of project tokens). The one-time remap window is protected by the token mapping's `enabled` flag and the outbox tree count, not by minimum deposit requirements.
|
package/SKILLS.md
CHANGED
|
@@ -2,49 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Use This File For
|
|
4
4
|
|
|
5
|
-
- Use this file when the task involves cross-chain project-token
|
|
6
|
-
- Start here, then decide whether the issue is in shared
|
|
5
|
+
- Use this file when the task involves cross-chain project-token movement, Merkle-root progression, token mapping, or emergency and deprecation flows.
|
|
6
|
+
- Start here, then decide whether the issue is in shared sucker logic or in a bridge-specific transport implementation.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
10
10
|
| If you need... | Open this next |
|
|
11
11
|
|---|---|
|
|
12
|
-
| Repo overview and
|
|
12
|
+
| Repo overview and architecture | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
|
|
13
13
|
| Shared bridge logic | [`src/JBSucker.sol`](./src/JBSucker.sol), [`src/JBSuckerRegistry.sol`](./src/JBSuckerRegistry.sol) |
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
| Merkle and helper logic | [`src/utils/`](./src/utils/), [`src/libraries/`](./src/libraries/) |
|
|
17
|
-
| Interop and chain-specific fork coverage | [`test/ForkMainnet.t.sol`](./test/ForkMainnet.t.sol), [`test/ForkArbitrum.t.sol`](./test/ForkArbitrum.t.sol), [`test/ForkCelo.t.sol`](./test/ForkCelo.t.sol), [`test/ForkOPStack.t.sol`](./test/ForkOPStack.t.sol), [`test/InteropCompat.t.sol`](./test/InteropCompat.t.sol) |
|
|
18
|
-
| Swap, claim, attack, and regression coverage | [`test/ForkSwap.t.sol`](./test/ForkSwap.t.sol), [`test/ForkClaimMainnet.t.sol`](./test/ForkClaimMainnet.t.sol), [`test/SuckerAttacks.t.sol`](./test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](./test/SuckerDeepAttacks.t.sol), [`test/SuckerRegressions.t.sol`](./test/SuckerRegressions.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol) |
|
|
19
|
-
|
|
20
|
-
## Repo Map
|
|
21
|
-
|
|
22
|
-
| Area | Where to look |
|
|
23
|
-
|---|---|
|
|
24
|
-
| Base contracts | [`src/JBSucker.sol`](./src/JBSucker.sol), [`src/JBSuckerRegistry.sol`](./src/JBSuckerRegistry.sol) |
|
|
25
|
-
| Chain-specific implementations and deployers | [`src/`](./src/), [`src/deployers/`](./src/deployers/) |
|
|
26
|
-
| Libraries, utils, and types | [`src/libraries/`](./src/libraries/), [`src/utils/`](./src/utils/), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/), [`src/enums/`](./src/enums/) |
|
|
27
|
-
| Scripts | [`script/`](./script/) |
|
|
28
|
-
| Tests | [`test/`](./test/) |
|
|
14
|
+
| Merkle logic | [`src/utils/MerkleLib.sol`](./src/utils/MerkleLib.sol) |
|
|
15
|
+
| Bridge-specific behavior | the matching implementation and deployer under [`src/`](./src/) and [`src/deployers/`](./src/deployers/) |
|
|
29
16
|
|
|
30
17
|
## Purpose
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
## Reference Files
|
|
35
|
-
|
|
36
|
-
- Open [`references/runtime.md`](./references/runtime.md) when you need the base claim flow, registry role, token mapping model, or the main bridge invariants.
|
|
37
|
-
- Open [`references/operations.md`](./references/operations.md) when you need deployer and transport-selection guidance, deprecation and emergency behavior, or the common stale-data traps around bridge configuration.
|
|
19
|
+
Canonical cross-chain movement layer for Juicebox project positions.
|
|
38
20
|
|
|
39
21
|
## Working Rules
|
|
40
22
|
|
|
41
|
-
- Start in
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
- Token mapping is intentionally one-way after real activity starts. Disabling a mapping is allowed; remapping to a different remote asset is not.
|
|
45
|
-
- Peer symmetry depends on deployer and salt assumptions as well as runtime code. A bridge bug can start in deployment shape before it appears in message flow.
|
|
46
|
-
- Treat token mapping, root progression, and emergency/deprecation controls as first-class runtime behavior, not admin-only side tooling.
|
|
47
|
-
- When debugging a bridge incident, separate accounting correctness from transport correctness before patching.
|
|
48
|
-
- Message authentication is delegated to bridge-specific subclasses. When reviewing a new transport, `_isRemotePeer` is one of the first things to inspect.
|
|
49
|
-
- Emergency exit and deprecation behavior are intentionally conservative. Some failure modes lock funds rather than risking double-spend.
|
|
50
|
-
- If a task touches project deployment shape, check whether the real source is `nana-omnichain-deployers-v6` or `revnet-core-v6` instead of the sucker implementation itself.
|
|
23
|
+
- Start in `JBSucker` for shared lifecycle logic.
|
|
24
|
+
- Separate Merkle bookkeeping from bridge-specific transport assumptions.
|
|
25
|
+
- Treat token mapping, deprecation, and emergency hatch behavior as core safety surfaces.
|