@bananapus/suckers-v6 0.0.20 → 0.0.22
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 +36 -5
- package/ARCHITECTURE.md +42 -103
- package/AUDIT_INSTRUCTIONS.md +116 -386
- package/CHANGELOG.md +72 -0
- package/README.md +88 -416
- package/RISKS.md +22 -5
- package/SKILLS.md +29 -250
- package/STYLE_GUIDE.md +58 -21
- package/USER_JOURNEYS.md +47 -311
- package/package.json +2 -3
- package/references/operations.md +25 -0
- package/references/runtime.md +28 -0
- package/script/Deploy.s.sol +7 -7
- package/src/JBArbitrumSucker.sol +5 -3
- package/src/JBCCIPSucker.sol +5 -0
- package/src/JBCeloSucker.sol +32 -40
- package/src/JBOptimismSucker.sol +5 -3
- package/src/JBSucker.sol +59 -34
- package/src/JBSuckerRegistry.sol +26 -7
- package/src/interfaces/IJBSuckerRegistry.sol +2 -0
- package/src/libraries/ARBAddresses.sol +1 -1
- package/src/libraries/ARBChains.sol +1 -1
- package/src/structs/JBOutboxTree.sol +3 -3
- package/src/utils/MerkleLib.sol +13 -6
- package/test/SuckerDeepAttacks.t.sol +1 -1
- package/test/TestAuditGaps.sol +1 -1
- package/test/audit/codex-MapTokensEnableOnlyValueStuck.t.sol +84 -0
- package/test/audit/codex-ToRemoteFeeIrrecoverable.t.sol +238 -0
- package/test/unit/emergency.t.sol +1 -1
- package/test/unit/invariants.t.sol +1 -1
- package/test/unit/merkle.t.sol +1 -1
- package/CHANGE_LOG.md +0 -484
package/ADMINISTRATION.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
Admin privileges and their scope in nana-suckers-v6.
|
|
4
4
|
|
|
5
|
+
## At A Glance
|
|
6
|
+
|
|
7
|
+
| Item | Details |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| Scope | Cross-chain bridge deployment, token mapping, deprecation, emergency hatch controls, and layer-specific bridge configurator setup. |
|
|
10
|
+
| Operators | Registry owner, project owners and delegates, sucker deployers, token mappers, safety admins, deprecation admins, and one-time bridge configurators. |
|
|
11
|
+
| Highest-risk actions | Mapping the wrong remote token, enabling an emergency hatch, setting deprecation, or misconfiguring bridge singletons and chain constants. |
|
|
12
|
+
| Recovery posture | The normal recovery path is to deploy or registry-enable a new sucker/deployer and migrate traffic; several safety actions are intentionally irreversible. |
|
|
13
|
+
|
|
14
|
+
## Routine Operations
|
|
15
|
+
|
|
16
|
+
- Allow only the sucker deployer implementations you are prepared to support operationally on the registry side.
|
|
17
|
+
- Map remote tokens carefully and early, before outbox activity makes mistakes harder to unwind.
|
|
18
|
+
- Use deprecation timestamps to create a safe runway for shutdown rather than trying to disable a bridge abruptly.
|
|
19
|
+
- Treat emergency hatch activation as a last-resort safety action when the normal bridge path is no longer trustworthy.
|
|
20
|
+
- Verify one-time deployer configuration (`configureSingleton`, `setChainSpecificConstants`) before using a deployer in production.
|
|
21
|
+
|
|
22
|
+
## One-Way Or High-Risk Actions
|
|
23
|
+
|
|
24
|
+
- `enableEmergencyHatchFor` is irreversible for the affected token mapping.
|
|
25
|
+
- Once a sucker reaches `SENDING_DISABLED` or `DEPRECATED`, the lifecycle cannot be reversed.
|
|
26
|
+
- Deployer singleton and chain-specific constant configuration are one-time setup operations.
|
|
27
|
+
- A bad token mapping can strand users or route liquidity to the wrong remote asset.
|
|
28
|
+
|
|
29
|
+
## Recovery Notes
|
|
30
|
+
|
|
31
|
+
- If a bridge path is unhealthy, prefer deprecating the affected sucker and standing up a replacement rather than forcing traffic through a degraded route.
|
|
32
|
+
- If a token is emergency-hatched, users recover through the documented exit flow; you cannot simply re-enable the old path afterward.
|
|
33
|
+
|
|
5
34
|
## Roles
|
|
6
35
|
|
|
7
36
|
### Registry Owner
|
|
@@ -28,6 +57,8 @@ Admin privileges and their scope in nana-suckers-v6.
|
|
|
28
57
|
- **Scope:** Can map or disable local-to-remote token pairs on a specific sucker.
|
|
29
58
|
- **Permission ID:** `JBPermissionIds.MAP_SUCKER_TOKEN`
|
|
30
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
|
+
|
|
31
62
|
### Safety Admin (delegated role)
|
|
32
63
|
|
|
33
64
|
- **How assigned:** Granted `SUCKER_SAFETY` permission by the project owner via `JBPermissions`.
|
|
@@ -56,17 +87,17 @@ Admin privileges and their scope in nana-suckers-v6.
|
|
|
56
87
|
| `allowSuckerDeployers(deployers)` | Registry Owner | N/A (`onlyOwner`) | Global | Batch version: adds multiple deployer contracts to the allowlist. |
|
|
57
88
|
| `removeSuckerDeployer(deployer)` | Registry Owner | N/A (`onlyOwner`) | Global | Removes a deployer contract from the allowlist, preventing future sucker deployments through it. |
|
|
58
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`. |
|
|
59
|
-
| `deploySuckersFor(projectId, salt, configs)` | Project
|
|
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. |
|
|
60
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. |
|
|
61
92
|
|
|
62
93
|
### JBSucker
|
|
63
94
|
|
|
64
95
|
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
65
96
|
|----------|--------------|---------------|-------|--------------|
|
|
66
|
-
| `mapToken(map)` | Project
|
|
67
|
-
| `mapTokens(maps)` | Project
|
|
68
|
-
| `enableEmergencyHatchFor(tokens)` | Project
|
|
69
|
-
| `setDeprecation(timestamp)` | Project
|
|
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. |
|
|
70
101
|
|
|
71
102
|
### JBSuckerDeployer (base and all subclasses)
|
|
72
103
|
|
package/ARCHITECTURE.md
CHANGED
|
@@ -1,121 +1,60 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Architecture
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`nana-suckers-v6` moves Juicebox project token value across chains. A sucker pair lets a holder burn or cash out locally, bridge the resulting terminal token plus the latest merkle root, and then claim equivalent project-token value on the remote chain.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Boundaries
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
├── JBBaseSucker.sol — Base chain sucker (extends JBOptimismSucker, overrides peerChainId for Base ↔ Ethereum)
|
|
14
|
-
├── JBCeloSucker.sol — Celo sucker (extends JBOptimismSucker, wraps ETH → WETH for bridging, custom gas token handling)
|
|
15
|
-
├── JBArbitrumSucker.sol — Arbitrum bridge implementation
|
|
16
|
-
├── JBCCIPSucker.sol — Chainlink CCIP bridge implementation
|
|
17
|
-
├── JBSuckerRegistry.sol — Registry of suckers per project, deployment permissions, centralized toRemoteFee (owner-controlled, applies to all suckers)
|
|
18
|
-
├── deployers/
|
|
19
|
-
│ ├── JBSuckerDeployer.sol — Abstract base deployer (LibClone, singleton pattern)
|
|
20
|
-
│ ├── JBOptimismSuckerDeployer.sol
|
|
21
|
-
│ ├── JBBaseSuckerDeployer.sol
|
|
22
|
-
│ ├── JBCeloSuckerDeployer.sol
|
|
23
|
-
│ ├── JBArbitrumSuckerDeployer.sol
|
|
24
|
-
│ └── JBCCIPSuckerDeployer.sol
|
|
25
|
-
├── enums/
|
|
26
|
-
│ ├── JBSuckerState.sol — ENABLED → DEPRECATION_PENDING → SENDING_DISABLED → DEPRECATED
|
|
27
|
-
│ └── JBLayer.sol — L1 / L2 indicator (used by JBArbitrumSucker)
|
|
28
|
-
├── interfaces/ — IJBSucker, IJBSuckerRegistry, IJBSuckerDeployer, bridge-specific interfaces (OP, Arb, CCIP)
|
|
29
|
-
├── libraries/
|
|
30
|
-
│ ├── ARBAddresses.sol — Arbitrum precompile/contract addresses
|
|
31
|
-
│ ├── ARBChains.sol — Arbitrum chain ID constants
|
|
32
|
-
│ └── CCIPHelper.sol — CCIP chain selector lookups
|
|
33
|
-
├── structs/ — JBClaim, JBLeaf, JBTokenMapping, JBRemoteToken, JBOutboxTree, JBMessageRoot, etc.
|
|
34
|
-
└── utils/
|
|
35
|
-
└── MerkleLib.sol — Append-only merkle tree operations
|
|
36
|
-
```
|
|
9
|
+
- `JBSucker` owns the chain-agnostic lifecycle.
|
|
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.
|
|
37
13
|
|
|
38
|
-
##
|
|
14
|
+
## Main Components
|
|
39
15
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
→ Pay toRemoteFee (ETH) to fee project via terminal.pay() — caller gets fee project tokens
|
|
48
|
-
→ Remaining msg.value forwarded as transport payment to the bridge
|
|
49
|
-
→ Send merkle root + bridged tokens to remote sucker via OP/Arb/CCIP messenger
|
|
50
|
-
```
|
|
16
|
+
| Component | Responsibility |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `JBSucker` | Prepare, root management, claim verification, token mapping, and deprecation controls |
|
|
19
|
+
| chain-specific suckers | Bridge transport and token-wrapping details for each supported network family |
|
|
20
|
+
| `JBSuckerRegistry` | Tracks deployed suckers, allowlists deployers, and stores global bridge-fee policy |
|
|
21
|
+
| deployers | Clone and initialize deterministic sucker instances |
|
|
22
|
+
| `MerkleLib` and helper libraries | Incremental tree logic plus chain-specific constants |
|
|
51
23
|
|
|
52
|
-
|
|
24
|
+
## Runtime Model
|
|
53
25
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
→ Mint project tokens to beneficiary (via controller.mintTokensOf)
|
|
26
|
+
```text
|
|
27
|
+
holder prepares a bridge
|
|
28
|
+
-> sucker cashes out or consumes the local project-token position
|
|
29
|
+
-> sucker inserts a merkle leaf into the outbox tree
|
|
30
|
+
-> someone bridges funds and the latest root to the remote sucker
|
|
31
|
+
-> claimant proves inclusion against the remote inbox tree
|
|
32
|
+
-> remote sucker releases or remints the destination-side value
|
|
62
33
|
```
|
|
63
34
|
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
ENABLED → DEPRECATION_PENDING → SENDING_DISABLED → DEPRECATED
|
|
67
|
-
(owner) (pending period) (no new outbox) (fully disabled)
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Token Mapping
|
|
71
|
-
|
|
72
|
-
Token mappings link a local terminal token to a remote token address, enabling bridging for that pair. Mappings are managed via `mapToken()` / `mapTokens()` (requires `MAP_SUCKER_TOKEN` permission).
|
|
73
|
-
|
|
74
|
-
**Immutability constraint:** Once a token has outbox tree entries (`_outboxOf[token].tree.count != 0`), it cannot be remapped to a *different* remote token — it can only be disabled by mapping to `address(0)`. This prevents double-spending: if remapping were allowed after outbox activity, the same local funds could be claimed against two different remote tokens on the remote chain.
|
|
75
|
-
|
|
76
|
-
A disabled mapping can be re-enabled to the *same* remote token. A misconfigured mapping requires deploying a new sucker.
|
|
35
|
+
## Critical Invariants
|
|
77
36
|
|
|
78
|
-
|
|
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.
|
|
79
41
|
|
|
80
|
-
##
|
|
42
|
+
## Where Complexity Lives
|
|
81
43
|
|
|
82
|
-
The
|
|
44
|
+
- The abstract lifecycle is simple, but every transport backend has its own failure modes and native-token quirks.
|
|
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.
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
**What it does:** Sets `emergencyHatch = true` and `enabled = false` for each token's remote mapping. This is irreversible — once the emergency hatch is enabled for a token, it cannot be disabled or remapped.
|
|
87
|
-
|
|
88
|
-
**How users exit:** Users call `exitThroughEmergencyHatch(JBClaim claimData)` with a merkle proof against the *outbox* tree. The sucker validates:
|
|
89
|
-
1. The emergency hatch is enabled for that token (or the sucker is in `DEPRECATED` / `SENDING_DISABLED` state).
|
|
90
|
-
2. The leaf has not already been sent to the remote chain (`index >= numberOfClaimsSent`). Leaves whose roots were already bridged cannot be emergency-exited, since they could also be claimed remotely.
|
|
91
|
-
3. The leaf has not already been claimed (bitmap check).
|
|
92
|
-
|
|
93
|
-
The sucker then adds the terminal tokens back to the project's balance (via `terminal.addToBalanceOf`) and mints project tokens to the beneficiary on the local chain.
|
|
94
|
-
|
|
95
|
-
## Extension Points
|
|
96
|
-
|
|
97
|
-
| Point | Interface | Purpose |
|
|
98
|
-
|-------|-----------|---------|
|
|
99
|
-
| Bridge transport | Chain-specific sucker | OP, Arb, CCIP implementations |
|
|
100
|
-
| Sucker deployer | `IJBSuckerDeployer` | Factory for new suckers |
|
|
101
|
-
| Registry | `IJBSuckerRegistry` | Discovery and access control |
|
|
102
|
-
|
|
103
|
-
## Design Decisions
|
|
104
|
-
|
|
105
|
-
**Dual merkle trees instead of direct bridging.** Each sucker maintains separate outbox and inbox merkle trees per token. Outbound transfers are batched into the outbox tree via `prepare()`, and a single `toRemote()` call bridges the root and accumulated funds together. This amortizes bridge costs across many users — only one cross-chain message per batch rather than one per transfer. The inbox tree on the receiving side lets users self-serve claims with merkle proofs at their own pace.
|
|
106
|
-
|
|
107
|
-
**Bitmap for claim tracking.** Each leaf index is tracked in an OpenZeppelin `BitMaps.BitMap` (`_executedFor`), which packs 256 booleans per storage slot. This is far cheaper than a `mapping(uint256 => bool)` for dense sequential indices, and the merkle tree's sequential leaf indices are a natural fit.
|
|
108
|
-
|
|
109
|
-
**Immutable token mappings once outbox has entries.** After a token mapping has been used (outbox tree count > 0), remapping to a different remote token is blocked. Without this, an attacker could prepare tokens mapped to token A, then remap to token B before `toRemote()` is called, allowing the same local funds to be claimed against both tokens on the remote chain. Disabling (mapping to `address(0)`) is still allowed since it flushes remaining outbox entries and stops new ones.
|
|
48
|
+
## Dependencies
|
|
110
49
|
|
|
111
|
-
|
|
50
|
+
- `nana-core-v6` terminals and project-token semantics
|
|
51
|
+
- Native bridge infrastructure for OP Stack, Arbitrum, CCIP, and supported variants
|
|
52
|
+
- `nana-permission-ids-v6` for deploy, map, safety, and deprecation permissions
|
|
112
53
|
|
|
113
|
-
|
|
54
|
+
## Safe Change Guide
|
|
114
55
|
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
- `@openzeppelin/contracts` — BitMaps, SafeERC20, ERC165, Initializable, ERC2771Context, Ownable, EnumerableMap
|
|
121
|
-
- `solady` — LibClone (deterministic clone deployment for sucker deployers)
|
|
56
|
+
- Review every cross-chain change from both sides of the pair.
|
|
57
|
+
- Do not change merkle leaf encoding casually; proofs and remote compatibility depend on it.
|
|
58
|
+
- Keep registry policy, deployer configuration, and singleton logic aligned.
|
|
59
|
+
- Test chain-specific native-token wrapping paths separately from the abstract lifecycle.
|
|
60
|
+
- Assume bridge-edge behavior is the primary review target, not the happy path.
|