@bananapus/suckers-v6 0.0.3 → 0.0.5
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/README.md +115 -58
- package/SKILLS.md +124 -66
- package/package.json +3 -3
- package/src/JBCCIPSucker.sol +2 -3
- package/src/JBSucker.sol +2 -2
- package/test/SuckerDeepAttacks.t.sol +1 -1
- package/test/unit/ccip_refund.t.sol +3 -3
- package/test/unit/invariants.t.sol +469 -0
- package/test/unit/manual_balance.t.sol +487 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Juicebox Suckers
|
|
2
2
|
|
|
3
|
-
Cross-chain bridging for Juicebox
|
|
3
|
+
Cross-chain bridging for Juicebox V6 projects. Suckers let users cash out project tokens on one chain, move the backing funds across a bridge, and mint the same number of project tokens on another chain -- all via merkle-tree-based claims and chain-specific bridges.
|
|
4
4
|
|
|
5
5
|
<details>
|
|
6
6
|
<summary>Table of Contents</summary>
|
|
@@ -9,6 +9,9 @@ Cross-chain bridging for Juicebox V5 projects. Suckers let users burn project to
|
|
|
9
9
|
<li><a href="#architecture">Architecture</a></li>
|
|
10
10
|
<li><a href="#bridging-flow">Bridging Flow</a></li>
|
|
11
11
|
<li><a href="#bridging-tokens">Bridging Tokens</a></li>
|
|
12
|
+
<li><a href="#token-mapping">Token Mapping</a></li>
|
|
13
|
+
<li><a href="#deprecation-lifecycle">Deprecation Lifecycle</a></li>
|
|
14
|
+
<li><a href="#emergency-hatch">Emergency Hatch</a></li>
|
|
12
15
|
<li><a href="#launching-suckers">Launching Suckers</a></li>
|
|
13
16
|
<li><a href="#managing-suckers">Managing Suckers</a></li>
|
|
14
17
|
<li><a href="#using-the-relayer">Using the Relayer</a></li>
|
|
@@ -24,7 +27,7 @@ Cross-chain bridging for Juicebox V5 projects. Suckers let users burn project to
|
|
|
24
27
|
</ol>
|
|
25
28
|
</details>
|
|
26
29
|
|
|
27
|
-
_If you're having trouble understanding this contract, take a look at the [core protocol contracts](https://github.com/Bananapus/nana-core-
|
|
30
|
+
_If you're having trouble understanding this contract, take a look at the [core protocol contracts](https://github.com/Bananapus/nana-core-v6) and the [documentation](https://docs.juicebox.money/) first. If you have questions, reach out on [Discord](https://discord.com/invite/ErQYmth4dS)._
|
|
28
31
|
|
|
29
32
|
## What are Suckers?
|
|
30
33
|
|
|
@@ -32,17 +35,17 @@ _If you're having trouble understanding this contract, take a look at the [core
|
|
|
32
35
|
|
|
33
36
|
| Sucker | Networks | Description |
|
|
34
37
|
|--------|----------|-------------|
|
|
35
|
-
| [`JBCCIPSucker`](src/JBCCIPSucker.sol) | Any CCIP-connected chains | Uses [Chainlink CCIP](https://docs.chain.link/ccip) (`ccipSend`/`ccipReceive`). Handles native token wrapping/unwrapping for chains with different native assets. |
|
|
38
|
+
| [`JBCCIPSucker`](src/JBCCIPSucker.sol) | Any CCIP-connected chains | Uses [Chainlink CCIP](https://docs.chain.link/ccip) (`ccipSend`/`ccipReceive`). Handles native token wrapping/unwrapping for chains with different native assets. Supports Ethereum, Optimism, Base, Arbitrum, Polygon, Avalanche, BNB Chain, and their testnets. |
|
|
36
39
|
| [`JBOptimismSucker`](src/JBOptimismSucker.sol) | Ethereum and Optimism | Uses the [OP Standard Bridge](https://docs.optimism.io/builders/app-developers/bridging/standard-bridge) and the [OP Messenger](https://docs.optimism.io/builders/app-developers/bridging/messaging). |
|
|
37
40
|
| [`JBBaseSucker`](src/JBBaseSucker.sol) | Ethereum and Base | A thin wrapper around `JBOptimismSucker` with Base chain IDs. |
|
|
38
|
-
| [`JBArbitrumSucker`](src/JBArbitrumSucker.sol) | Ethereum and Arbitrum | Uses the [Arbitrum Inbox](https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging) and the [Arbitrum Gateway](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/get-started). Handles L1<->L2 retryable tickets. |
|
|
41
|
+
| [`JBArbitrumSucker`](src/JBArbitrumSucker.sol) | Ethereum and Arbitrum | Uses the [Arbitrum Inbox](https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging) and the [Arbitrum Gateway](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/get-started). Handles L1<->L2 retryable tickets and address aliasing. |
|
|
39
42
|
|
|
40
43
|
Suckers use two [merkle trees](https://en.wikipedia.org/wiki/Merkle_tree) to track project token claims associated with each terminal token they support:
|
|
41
44
|
|
|
42
|
-
- The **outbox tree** tracks tokens on the local chain
|
|
43
|
-
- The **inbox tree** tracks tokens which have been bridged from the peer chain
|
|
45
|
+
- The **outbox tree** tracks tokens on the local chain -- the network that the sucker is on.
|
|
46
|
+
- The **inbox tree** tracks tokens which have been bridged from the peer chain -- the network that the sucker's peer is on.
|
|
44
47
|
|
|
45
|
-
For example, a sucker which supports bridging ETH and USDC would have four trees
|
|
48
|
+
For example, a sucker which supports bridging ETH and USDC would have four trees -- an inbox and outbox tree for each token. These trees are append-only, and when they're bridged over to the other chain, they aren't deleted -- they only update the remote inbox tree with the latest root.
|
|
46
49
|
|
|
47
50
|
To insert project tokens into the outbox tree, users call `JBSucker.prepare(...)` with the amount of project tokens to bridge and the terminal token to bridge with them. The sucker cashes out those project tokens to reclaim the chosen terminal token from the project's primary terminal. Then it inserts a claim with this information into the outbox tree.
|
|
48
51
|
|
|
@@ -79,21 +82,21 @@ graph TD;
|
|
|
79
82
|
|
|
80
83
|
| Contract | Description |
|
|
81
84
|
|----------|-------------|
|
|
82
|
-
| `JBSucker` | Abstract base. Manages outbox/inbox merkle trees, `prepare`/`toRemote`/`claim` lifecycle, token mapping, deprecation, and emergency hatch. Deployed as clones via `Initializable`. |
|
|
83
|
-
| `JBCCIPSucker` | Extends `JBSucker`. Bridges via Chainlink CCIP (`ccipSend`/`ccipReceive`). Supports any CCIP-connected chain.
|
|
84
|
-
| `JBOptimismSucker` | Extends `JBSucker`. Bridges via OP Standard Bridge + OP Messenger. |
|
|
85
|
-
| `JBBaseSucker` | Thin wrapper around `JBOptimismSucker` with Base chain IDs. |
|
|
86
|
-
| `JBArbitrumSucker` | Extends `JBSucker`. Bridges via Arbitrum Inbox + Gateway Router.
|
|
87
|
-
| `JBSuckerRegistry` | Tracks all suckers per project. Manages deployer allowlist. Entry point for `deploySuckersFor`. |
|
|
88
|
-
| `JBSuckerDeployer` | Abstract base deployer. Clones a singleton sucker via `LibClone.cloneDeterministic` and initializes it. |
|
|
89
|
-
| `JBCCIPSuckerDeployer` | Deployer for `JBCCIPSucker`. Stores CCIP router, remote chain ID, and chain selector. |
|
|
90
|
-
| `JBOptimismSuckerDeployer` | Deployer for `JBOptimismSucker`. Stores OP Messenger and OP Bridge addresses. |
|
|
91
|
-
| `JBBaseSuckerDeployer` | Thin wrapper around `JBOptimismSuckerDeployer` for Base. |
|
|
92
|
-
| `JBArbitrumSuckerDeployer` | Deployer for `JBArbitrumSucker`. Stores Arbitrum Inbox, Gateway Router, and layer (L1
|
|
93
|
-
| `MerkleLib` | Incremental merkle tree (depth 32, modeled on eth2 deposit contract). Used for outbox/inbox trees. |
|
|
94
|
-
| `CCIPHelper` | CCIP router addresses, chain selectors, and WETH addresses per chain. |
|
|
95
|
-
| `ARBAddresses` | Arbitrum bridge contract addresses (Inbox, Gateway Router) for mainnet and Sepolia. |
|
|
96
|
-
| `ARBChains` | Arbitrum chain ID constants. |
|
|
85
|
+
| [`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. |
|
|
86
|
+
| [`JBCCIPSucker`](src/JBCCIPSucker.sol) | Extends `JBSucker`. Bridges via Chainlink CCIP (`ccipSend`/`ccipReceive`). Supports any CCIP-connected chain pair. Wraps native ETH to WETH before bridging (CCIP only transports ERC-20s) and unwraps on the receiving end. Can map `NATIVE_TOKEN` to ERC-20 addresses on the remote chain (unlike OP/Arbitrum suckers). |
|
|
87
|
+
| [`JBOptimismSucker`](src/JBOptimismSucker.sol) | Extends `JBSucker`. Bridges via OP Standard Bridge + OP Messenger. No `msg.value` required for transport. |
|
|
88
|
+
| [`JBBaseSucker`](src/JBBaseSucker.sol) | Thin wrapper around `JBOptimismSucker` with Base chain IDs (Ethereum 1 <-> Base 8453, Sepolia 11155111 <-> Base Sepolia 84532). |
|
|
89
|
+
| [`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. |
|
|
90
|
+
| [`JBSuckerRegistry`](src/JBSuckerRegistry.sol) | Tracks all suckers per project. Manages deployer allowlist (owner-only). Entry point for `deploySuckersFor`. Can remove deprecated suckers via `removeDeprecatedSucker`. |
|
|
91
|
+
| [`JBSuckerDeployer`](src/JBSuckerDeployer.sol) | Abstract base deployer. Clones a singleton sucker via `LibClone.cloneDeterministic` and initializes it. Two-phase setup: `setChainSpecificConstants` then `configureSingleton`. |
|
|
92
|
+
| [`JBCCIPSuckerDeployer`](src/deployers/JBCCIPSuckerDeployer.sol) | Deployer for `JBCCIPSucker`. Stores CCIP router, remote chain ID, and CCIP chain selector. |
|
|
93
|
+
| [`JBOptimismSuckerDeployer`](src/deployers/JBOptimismSuckerDeployer.sol) | Deployer for `JBOptimismSucker`. Stores OP Messenger and OP Bridge addresses. |
|
|
94
|
+
| [`JBBaseSuckerDeployer`](src/deployers/JBBaseSuckerDeployer.sol) | Thin wrapper around `JBOptimismSuckerDeployer` for Base. |
|
|
95
|
+
| [`JBArbitrumSuckerDeployer`](src/deployers/JBArbitrumSuckerDeployer.sol) | Deployer for `JBArbitrumSucker`. Stores Arbitrum Inbox, Gateway Router, and layer (`JBLayer.L1` or `JBLayer.L2`). |
|
|
96
|
+
| [`MerkleLib`](src/utils/MerkleLib.sol) | Incremental merkle tree (depth 32, max ~4 billion leaves, modeled on eth2 deposit contract). Used for outbox/inbox trees. Gas-optimized with inline assembly for `root()` and `branchRoot()`. |
|
|
97
|
+
| [`CCIPHelper`](src/libraries/CCIPHelper.sol) | CCIP router addresses, chain selectors, and WETH addresses per chain. Covers Ethereum, Optimism, Arbitrum, Base, Polygon, Avalanche, and BNB Chain (mainnet and testnets). |
|
|
98
|
+
| [`ARBAddresses`](src/libraries/ARBAddresses.sol) | Arbitrum bridge contract addresses (Inbox, Gateway Router) for mainnet and Sepolia. |
|
|
99
|
+
| [`ARBChains`](src/libraries/ARBChains.sol) | Arbitrum chain ID constants. |
|
|
97
100
|
|
|
98
101
|
## Bridging Flow
|
|
99
102
|
|
|
@@ -109,14 +112,19 @@ Chain A Chain B
|
|
|
109
112
|
| - sends merkle root + funds -->|
|
|
110
113
|
| |
|
|
111
114
|
| 3. fromRemote(root)
|
|
112
|
-
| -
|
|
115
|
+
| - validates message version
|
|
116
|
+
| - updates inbox tree (if nonce > current)
|
|
113
117
|
| |
|
|
114
118
|
| 4. claim(proof)
|
|
115
119
|
| - verifies merkle proof
|
|
116
120
|
| - mints project tokens
|
|
117
|
-
| - adds funds to balance
|
|
121
|
+
| - adds funds to balance (if ON_CLAIM)
|
|
118
122
|
```
|
|
119
123
|
|
|
124
|
+
Each `toRemote` call increments the outbox nonce and sends the complete merkle root. On the receiving side, `fromRemote` only accepts roots with a nonce strictly greater than the current inbox nonce. If nonces arrive out of order (possible with CCIP), earlier nonces are silently skipped and their claims become unclaimable on that chain. The sender would need to use the emergency hatch on the source chain to recover funds from skipped roots.
|
|
125
|
+
|
|
126
|
+
Messages include a `MESSAGE_VERSION` (currently `1`) to reject incompatible messages from peers running different protocol versions.
|
|
127
|
+
|
|
120
128
|
## Bridging Tokens
|
|
121
129
|
|
|
122
130
|
Imagine that "OhioDAO" is deployed on Ethereum mainnet and Optimism:
|
|
@@ -153,18 +161,18 @@ JBERC20.approve({
|
|
|
153
161
|
});
|
|
154
162
|
```
|
|
155
163
|
|
|
156
|
-
**3. Prepare the bridge.** Jimmy calls `prepare(...)` on the mainnet sucker:
|
|
164
|
+
**3. Prepare the bridge.** Jimmy calls `prepare(...)` on the mainnet sucker. Note that `beneficiary` is `bytes32` for cross-VM compatibility (e.g., Solana public keys):
|
|
157
165
|
|
|
158
166
|
```solidity
|
|
159
167
|
JBOptimismSucker.prepare({
|
|
160
|
-
|
|
161
|
-
beneficiary: jimmy,
|
|
168
|
+
projectTokenCount: 1e18,
|
|
169
|
+
beneficiary: bytes32(uint256(uint160(jimmy))), // bytes32 for cross-VM compat
|
|
162
170
|
minTokensReclaimed: 0,
|
|
163
171
|
token: JBConstants.NATIVE_TOKEN
|
|
164
172
|
});
|
|
165
173
|
```
|
|
166
174
|
|
|
167
|
-
The sucker transfers Jimmy's $OHIO to itself, cashes them out using OhioDAO's primary ETH terminal, and inserts a leaf into the ETH outbox tree. The leaf is a `keccak256` hash of the beneficiary
|
|
175
|
+
The sucker transfers Jimmy's $OHIO to itself, cashes them out using OhioDAO's primary ETH terminal, and inserts a leaf into the ETH outbox tree. The leaf is a `keccak256` hash of the beneficiary (bytes32), the project token count, and the terminal token amount reclaimed. Both amounts are capped at `uint128` for SVM compatibility.
|
|
168
176
|
|
|
169
177
|
**4. Bridge to remote.** Jimmy (or anyone) calls `toRemote(...)`:
|
|
170
178
|
|
|
@@ -172,15 +180,15 @@ The sucker transfers Jimmy's $OHIO to itself, cashes them out using OhioDAO's pr
|
|
|
172
180
|
JBOptimismSucker.toRemote(JBConstants.NATIVE_TOKEN);
|
|
173
181
|
```
|
|
174
182
|
|
|
175
|
-
This sends the outbox merkle root and the accumulated ETH to the peer sucker on Optimism. After the bridge completes, the Optimism sucker's ETH inbox tree is updated with the new root containing Jimmy's claim.
|
|
183
|
+
This sends the outbox merkle root and the accumulated ETH to the peer sucker on Optimism. The outbox balance is cleared and the nonce incremented. After the bridge completes, the Optimism sucker's ETH inbox tree is updated with the new root containing Jimmy's claim.
|
|
176
184
|
|
|
177
185
|
**5. Claim on the remote chain.** Jimmy claims his $OHIO on Optimism by calling `claim(...)` with a [`JBClaim`](src/structs/JBClaim.sol):
|
|
178
186
|
|
|
179
187
|
```solidity
|
|
180
188
|
struct JBClaim {
|
|
181
|
-
address token;
|
|
182
|
-
JBLeaf leaf;
|
|
183
|
-
bytes32[32] proof;
|
|
189
|
+
address token; // The terminal token to claim
|
|
190
|
+
JBLeaf leaf; // The leaf data
|
|
191
|
+
bytes32[32] proof; // Merkle proof (TREE_DEPTH = 32)
|
|
184
192
|
}
|
|
185
193
|
```
|
|
186
194
|
|
|
@@ -188,14 +196,14 @@ The [`JBLeaf`](src/structs/JBLeaf.sol):
|
|
|
188
196
|
|
|
189
197
|
```solidity
|
|
190
198
|
struct JBLeaf {
|
|
191
|
-
uint256 index;
|
|
192
|
-
|
|
193
|
-
uint256 projectTokenCount;
|
|
194
|
-
uint256 terminalTokenAmount;
|
|
199
|
+
uint256 index; // Position in the merkle tree
|
|
200
|
+
bytes32 beneficiary; // Recipient address (bytes32 for cross-VM compat)
|
|
201
|
+
uint256 projectTokenCount; // Project tokens to mint
|
|
202
|
+
uint256 terminalTokenAmount; // Terminal tokens reclaimed
|
|
195
203
|
}
|
|
196
204
|
```
|
|
197
205
|
|
|
198
|
-
Building these claims manually requires tracking every insertion and computing merkle proofs. The [`juicerkle`](https://github.com/Bananapus/juicerkle) service simplifies this
|
|
206
|
+
Building these claims manually requires tracking every insertion and computing merkle proofs. The [`juicerkle`](https://github.com/Bananapus/juicerkle) service simplifies this -- `POST` a JSON request to `/claims`:
|
|
199
207
|
|
|
200
208
|
```json
|
|
201
209
|
{
|
|
@@ -217,21 +225,68 @@ The service looks through the entire inbox tree and returns all available claims
|
|
|
217
225
|
|
|
218
226
|
If the sucker's `ADD_TO_BALANCE_MODE` is `ON_CLAIM`, the bridged ETH is immediately added to OhioDAO's Optimism balance. Otherwise, someone calls `addOutstandingAmountToBalance(token)` to add it manually.
|
|
219
227
|
|
|
228
|
+
## Token Mapping
|
|
229
|
+
|
|
230
|
+
Token mappings define which local terminal token corresponds to which remote terminal token. Key rules:
|
|
231
|
+
|
|
232
|
+
- **`remoteToken` is `bytes32`**, not `address` -- this supports cross-VM compatibility (e.g., Solana program addresses). For EVM addresses, left-pad with zeros: `bytes32(uint256(uint160(address)))`.
|
|
233
|
+
- **Immutable once used.** After an outbox tree has entries for a token, the mapping cannot be changed to a different remote token. It can only be disabled (by setting `remoteToken` to `bytes32(0)`), which triggers a final root flush to settle outstanding claims. A disabled mapping can be re-enabled back to the same remote token.
|
|
234
|
+
- **Minimum gas enforcement.** ERC-20 mappings must specify `minGas >= MESSENGER_ERC20_MIN_GAS_LIMIT` (200,000). Native token mappings on the base `JBSucker` do not require minimum gas, but `JBCCIPSucker` requires it for all tokens (because CCIP wraps native to WETH, an ERC-20 transfer).
|
|
235
|
+
- **Native token rules.** On `JBSucker` (OP/Arb), `NATIVE_TOKEN` can only map to `NATIVE_TOKEN` or `bytes32(0)`. `JBCCIPSucker` overrides this to allow `NATIVE_TOKEN` mapping to any remote address (for chains where ETH is an ERC-20).
|
|
236
|
+
- **`minBridgeAmount`** prevents spam by requiring a minimum outbox balance before `toRemote` can be called.
|
|
237
|
+
|
|
238
|
+
```solidity
|
|
239
|
+
struct JBTokenMapping {
|
|
240
|
+
address localToken; // Local terminal token address
|
|
241
|
+
uint32 minGas; // Minimum gas for bridging
|
|
242
|
+
bytes32 remoteToken; // Remote token (bytes32 for cross-VM compat)
|
|
243
|
+
uint256 minBridgeAmount; // Minimum balance to trigger bridging
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Deprecation Lifecycle
|
|
248
|
+
|
|
249
|
+
Suckers have a four-state deprecation lifecycle controlled by `setDeprecation(timestamp)`:
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
ENABLED --> DEPRECATION_PENDING --> SENDING_DISABLED --> DEPRECATED
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
| State | Condition | Behavior |
|
|
256
|
+
|-------|-----------|----------|
|
|
257
|
+
| `ENABLED` | `deprecatedAfter == 0` | Fully functional. All operations allowed. |
|
|
258
|
+
| `DEPRECATION_PENDING` | `now < deprecatedAfter - _maxMessagingDelay()` | Warning state. All operations still allowed. Deprecation can be cancelled by setting timestamp to `0`. |
|
|
259
|
+
| `SENDING_DISABLED` | `now < deprecatedAfter` | No new `prepare` or `toRemote` calls. Incoming `fromRemote` still accepted. Emergency exit available for all tokens. |
|
|
260
|
+
| `DEPRECATED` | `now >= deprecatedAfter` | Fully shut down. No new `fromRemote` accepted. Emergency exit available for all tokens. |
|
|
261
|
+
|
|
262
|
+
The deprecation timestamp must be at least `_maxMessagingDelay()` (14 days) in the future. This ensures in-flight messages have time to arrive before the sucker stops accepting them. Once in `SENDING_DISABLED` or `DEPRECATED` state, the deprecation can no longer be modified.
|
|
263
|
+
|
|
264
|
+
Permission: `SET_SUCKER_DEPRECATION` from the project owner.
|
|
265
|
+
|
|
266
|
+
## Emergency Hatch
|
|
267
|
+
|
|
268
|
+
The emergency hatch lets users exit on the chain where they deposited when the bridge is broken or a token is no longer compatible.
|
|
269
|
+
|
|
270
|
+
- **Per-token activation.** Call `enableEmergencyHatchFor(tokens)` with `SUCKER_SAFETY` permission. This is irreversible -- once opened for a token, that token can never be bridged by this sucker again.
|
|
271
|
+
- **Automatic activation.** When the sucker reaches `SENDING_DISABLED` or `DEPRECATED` state, all tokens automatically allow emergency exit.
|
|
272
|
+
- **Who can exit.** Only users whose leaves have NOT already been sent to the remote chain (i.e., leaf index >= `numberOfClaimsSent`) can use the emergency hatch. This prevents double-spending where the same leaf is claimed on both chains.
|
|
273
|
+
- **How it works.** Users call `exitThroughEmergencyHatch(claimData)` with a proof against the **outbox** tree (not the inbox tree). The sucker mints project tokens and adds terminal tokens to the project balance, just like a normal claim.
|
|
274
|
+
|
|
220
275
|
## Launching Suckers
|
|
221
276
|
|
|
222
277
|
Requirements for deploying a sucker pair:
|
|
223
278
|
|
|
224
279
|
1. **Projects on both chains.** Project IDs don't have to match.
|
|
225
|
-
2. **
|
|
280
|
+
2. **0% cash out tax rate.** Both projects must have a `cashOutTaxRate` of `0` so suckers can fully cash out project tokens for terminal tokens.
|
|
226
281
|
3. **Owner minting enabled.** Both projects must have `allowOwnerMinting` set to `true` so suckers can mint bridged project tokens.
|
|
227
|
-
4. **ERC-20 project token.** Both projects must have a deployed ERC-20 token (via `JBController.deployERC20For(...)`).
|
|
282
|
+
4. **ERC-20 project token.** Both projects must have a deployed ERC-20 token (via `JBController.deployERC20For(...)`). The sucker uses `safeTransferFrom` to pull project tokens from the caller.
|
|
228
283
|
|
|
229
284
|
Suckers are deployed through the [`JBSuckerRegistry`](src/JBSuckerRegistry.sol) on each chain. The registry maps local tokens to remote tokens during deployment, so it needs permission:
|
|
230
285
|
|
|
231
286
|
```solidity
|
|
232
287
|
// Give the registry MAP_SUCKER_TOKEN permission for project 12
|
|
233
288
|
uint256[] memory permissionIds = new uint256[](1);
|
|
234
|
-
permissionIds[0] = JBPermissionIds.MAP_SUCKER_TOKEN;
|
|
289
|
+
permissionIds[0] = JBPermissionIds.MAP_SUCKER_TOKEN;
|
|
235
290
|
|
|
236
291
|
permissions.setPermissionsFor(
|
|
237
292
|
projectOwner,
|
|
@@ -251,7 +306,7 @@ JBTokenMapping[] memory mappings = new JBTokenMapping[](1);
|
|
|
251
306
|
mappings[0] = JBTokenMapping({
|
|
252
307
|
localToken: JBConstants.NATIVE_TOKEN,
|
|
253
308
|
minGas: 200_000,
|
|
254
|
-
remoteToken: JBConstants.NATIVE_TOKEN,
|
|
309
|
+
remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN))), // bytes32
|
|
255
310
|
minBridgeAmount: 0.025 ether
|
|
256
311
|
});
|
|
257
312
|
|
|
@@ -267,16 +322,17 @@ address[] memory suckers = registry.deploySuckersFor(12, salt, configs);
|
|
|
267
322
|
```
|
|
268
323
|
|
|
269
324
|
- The [`JBTokenMapping`](src/structs/JBTokenMapping.sol) maps local mainnet ETH to remote Optimism ETH.
|
|
270
|
-
- `
|
|
271
|
-
- `
|
|
272
|
-
-
|
|
325
|
+
- `remoteToken` is `bytes32`, not `address`. For EVM addresses, use `bytes32(uint256(uint160(addr)))`.
|
|
326
|
+
- `minBridgeAmount` prevents spam -- ours blocks attempts to bridge less than 0.025 ETH.
|
|
327
|
+
- `minGas` requires a gas limit of at least 200,000 for ERC-20s. If your token has expensive transfer logic, you may need more.
|
|
328
|
+
- The [`JBSuckerDeployerConfig`](src/structs/JBSuckerDeployerConfig.sol) specifies which deployer to use. You can only use approved deployers through the registry -- check for `SuckerDeployerAllowed` events or contact the registry's owner.
|
|
273
329
|
- **For the suckers to be peers, the `salt` has to match on both chains and the same address must call `deploySuckersFor(...)`.**
|
|
274
330
|
|
|
275
331
|
Finally, give the sucker permission to mint bridged project tokens:
|
|
276
332
|
|
|
277
333
|
```solidity
|
|
278
334
|
uint256[] memory mintPermissionIds = new uint256[](1);
|
|
279
|
-
mintPermissionIds[0] = JBPermissionIds.MINT_TOKENS;
|
|
335
|
+
mintPermissionIds[0] = JBPermissionIds.MINT_TOKENS;
|
|
280
336
|
|
|
281
337
|
permissions.setPermissionsFor(
|
|
282
338
|
projectOwner,
|
|
@@ -298,15 +354,15 @@ Once configured, suckers manage themselves. Stay up-to-date on changes to the br
|
|
|
298
354
|
|
|
299
355
|
### Disable a token
|
|
300
356
|
|
|
301
|
-
If a bridge change affects only certain tokens, call `mapToken(...)` with `remoteToken` set to `
|
|
357
|
+
If a bridge change affects only certain tokens, call `mapToken(...)` with `remoteToken` set to `bytes32(0)` to disable that token. This triggers a final `toRemote` to flush remaining outbox funds to the peer. If the bridge won't allow a final transfer with the remaining funds, activate the emergency hatch for the affected tokens instead.
|
|
302
358
|
|
|
303
|
-
The emergency hatch lets depositors withdraw their funds on the chain where they deposited. Only those whose funds have not been sent to the remote chain can withdraw. Once opened for a token, that token can never be bridged by this sucker again
|
|
359
|
+
The emergency hatch lets depositors withdraw their funds on the chain where they deposited. Only those whose funds have not been sent to the remote chain can withdraw. Once opened for a token, that token can never be bridged by this sucker again -- deploy a new sucker instead.
|
|
304
360
|
|
|
305
361
|
### Deprecate the suckers
|
|
306
362
|
|
|
307
|
-
If the bridging infrastructure will no longer work, deprecate the sucker to begin shutdown.
|
|
363
|
+
If the bridging infrastructure will no longer work, deprecate the sucker to begin shutdown. Call `setDeprecation(timestamp)` with a timestamp at least 14 days (`_maxMessagingDelay()`) in the future. The sucker transitions through `DEPRECATION_PENDING` -> `SENDING_DISABLED` -> `DEPRECATED`. After full deprecation, all tokens allow exit through the emergency hatch and no new messages are accepted. This protects against future fake or malicious bridge messages.
|
|
308
364
|
|
|
309
|
-
When deprecating, ensure no pending bridge messages need retrying
|
|
365
|
+
When deprecating, ensure no pending bridge messages need retrying -- once deprecation completes, those messages will be rejected.
|
|
310
366
|
|
|
311
367
|
## Using the Relayer
|
|
312
368
|
|
|
@@ -320,14 +376,14 @@ Users can do this manually, but it's a hassle. The [`bananapus-sucker-relayer`](
|
|
|
320
376
|
|
|
321
377
|
## Resources
|
|
322
378
|
|
|
323
|
-
- [`MerkleLib`](src/utils/MerkleLib.sol)
|
|
324
|
-
- [`juicerkle`](https://github.com/Bananapus/juicerkle)
|
|
325
|
-
- [`juicerkle-tester`](https://github.com/Bananapus/juicerkle-tester)
|
|
379
|
+
- [`MerkleLib`](src/utils/MerkleLib.sol) -- Incremental merkle tree based on [Nomad's implementation](https://github.com/nomad-xyz/nomad-monorepo/blob/main/solidity/nomad-core/libs/Merkle.sol) and the eth2 deposit contract.
|
|
380
|
+
- [`juicerkle`](https://github.com/Bananapus/juicerkle) -- Service that returns available claims for a beneficiary (generates merkle proofs). Includes a [Go merkle tree implementation](https://github.com/Bananapus/juicerkle/blob/master/tree/tree.go) for computing roots and building/verifying proofs.
|
|
381
|
+
- [`juicerkle-tester`](https://github.com/Bananapus/juicerkle-tester) -- End-to-end bridging test: deploys projects, tokens, and suckers, then bridges between them. Useful as a bridging walkthrough.
|
|
326
382
|
|
|
327
383
|
## Repository Layout
|
|
328
384
|
|
|
329
385
|
```
|
|
330
|
-
nana-suckers-
|
|
386
|
+
nana-suckers-v6/
|
|
331
387
|
├── script/
|
|
332
388
|
│ ├── Deploy.s.sol - Deployment script.
|
|
333
389
|
│ └── helpers/
|
|
@@ -348,6 +404,7 @@ nana-suckers-v5/
|
|
|
348
404
|
│ └── MerkleLib.sol - Incremental merkle tree (depth 32).
|
|
349
405
|
└── test/
|
|
350
406
|
├── Fork.t.sol - Fork tests.
|
|
407
|
+
├── InteropCompat.t.sol - Cross-VM compatibility tests.
|
|
351
408
|
├── SuckerAttacks.t.sol - Security-focused attack tests.
|
|
352
409
|
├── SuckerDeepAttacks.t.sol - Deep attack scenario tests.
|
|
353
410
|
├── mocks/ - Mock contracts for testing.
|
|
@@ -361,20 +418,20 @@ nana-suckers-v5/
|
|
|
361
418
|
For projects using `npm` to manage dependencies (recommended):
|
|
362
419
|
|
|
363
420
|
```bash
|
|
364
|
-
npm install @bananapus/suckers-
|
|
421
|
+
npm install @bananapus/suckers-v6
|
|
365
422
|
```
|
|
366
423
|
|
|
367
424
|
For projects using `forge` to manage dependencies:
|
|
368
425
|
|
|
369
426
|
```bash
|
|
370
|
-
forge install Bananapus/nana-suckers-
|
|
427
|
+
forge install Bananapus/nana-suckers-v6
|
|
371
428
|
```
|
|
372
429
|
|
|
373
|
-
If you're using `forge`, add `@bananapus/suckers-
|
|
430
|
+
If you're using `forge`, add `@bananapus/suckers-v6/=lib/nana-suckers-v6/` to `remappings.txt`. You'll also need to install `nana-suckers-v6`'s dependencies and add similar remappings for them.
|
|
374
431
|
|
|
375
432
|
### Develop
|
|
376
433
|
|
|
377
|
-
`nana-suckers-
|
|
434
|
+
`nana-suckers-v6` uses [npm](https://www.npmjs.com/) (version >=20.0.0) for package management and the [Foundry](https://github.com/foundry-rs/foundry) development toolchain for builds, tests, and deployments. To get set up, [install Node.js](https://nodejs.org/en/download) and install [Foundry](https://github.com/foundry-rs/foundry):
|
|
378
435
|
|
|
379
436
|
```bash
|
|
380
437
|
curl -L https://foundry.paradigm.xyz | sh
|