@bananapus/suckers-v6 0.0.17 → 0.0.19
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/CHANGE_LOG.md +42 -0
- package/SKILLS.md +3 -0
- package/package.json +3 -3
- package/script/Deploy.s.sol +396 -215
- package/script/helpers/SuckerDeploymentLib.sol +4 -1
- package/src/JBArbitrumSucker.sol +1 -0
- package/src/JBCCIPSucker.sol +8 -2
- package/src/JBCeloSucker.sol +1 -0
- package/src/JBOptimismSucker.sol +1 -0
- package/src/JBSucker.sol +28 -15
- package/src/interfaces/IJBCCIPSuckerDeployer.sol +0 -1
- package/src/interfaces/IJBSucker.sol +0 -2
- package/src/libraries/CCIPHelper.sol +6 -0
- package/src/structs/JBClaim.sol +1 -0
- package/src/structs/JBInboxTreeRoot.sol +1 -0
- package/src/structs/JBLeaf.sol +1 -0
- package/src/structs/JBMessageRoot.sol +1 -0
- package/src/structs/JBOutboxTree.sol +1 -0
- package/src/structs/JBRemoteToken.sol +1 -0
- package/src/structs/JBSuckerDeployerConfig.sol +1 -0
- package/src/structs/JBSuckersPair.sol +1 -0
- package/src/structs/JBTokenMapping.sol +1 -0
- package/test/Fork.t.sol +5 -19
- package/test/ForkArbitrum.t.sol +2 -0
- package/test/ForkCelo.t.sol +5 -0
- package/test/ForkClaim.t.sol +5 -15
- package/test/ForkMainnet.t.sol +3 -1
- package/test/ForkOPStack.t.sol +2 -0
- package/test/InteropCompat.t.sol +12 -1
- package/test/SuckerAttacks.t.sol +4 -3
- package/test/SuckerDeepAttacks.t.sol +12 -1
- package/test/SuckerRegressions.t.sol +5 -3
- package/test/TestAuditGaps.sol +6 -5
- package/test/audit/ArbitrumL2ToRemoteFeeDoS.t.sol +8 -0
- package/test/audit/{CodexNemesisPoC.t.sol → DeprecatedSuckerDestination.t.sol} +11 -13
- package/test/audit/ToRemoteFeeFallback.t.sol +131 -0
- package/test/audit/TrustedForwarderSpoof.t.sol +109 -0
- package/test/audit/TrustedForwarderSpoofCCIP.t.sol +112 -0
- package/test/fork/OptimismSuckerFork.t.sol +3 -1
- package/test/mocks/ERC20Mock.sol +1 -0
- package/test/mocks/MockMessenger.sol +1 -0
- package/test/regression/MapTokensDust.t.sol +3 -1
- package/test/unit/arb.t.sol +1 -1
- package/test/unit/ccip_native_interop.t.sol +12 -2
- package/test/unit/ccip_refund.t.sol +3 -5
- package/test/unit/deployer.t.sol +17 -7
- package/test/unit/emergency.t.sol +15 -0
- package/test/unit/fee_fallback.t.sol +243 -0
- package/test/unit/invariants.t.sol +3 -1
- package/test/unit/merkle.t.sol +6 -0
- package/test/unit/multi_chain_evolution.t.sol +23 -2
- package/test/unit/registry.t.sol +3 -4
package/CHANGE_LOG.md
CHANGED
|
@@ -166,6 +166,7 @@ In v5, `JBSucker.initialize()` used `msg.sender` to set the `deployer` field. In
|
|
|
166
166
|
| `JBSucker` | `JBSucker_AmountExceedsUint128(uint256 amount)` | Thrown when `terminalTokenAmount` or `projectTokenCount` exceeds `uint128` in `_insertIntoTree`. Guards against overflow for SVM/Solana compatibility. |
|
|
167
167
|
| `JBSucker` | `JBSucker_InvalidMessageVersion(uint8 received, uint8 expected)` | Thrown in `fromRemote` when the message version does not match `MESSAGE_VERSION`. Prevents processing incompatible messages. |
|
|
168
168
|
| `JBSucker` | `JBSucker_NothingToSend()` | Thrown in `toRemote()` when the outbox has zero balance and no unsent claims. Prevents unnecessary bridge calls. |
|
|
169
|
+
| `JBSucker` | `JBSucker_IndexOutOfRange(uint256 index)` | Thrown in `_validate()` and `_validateForEmergencyExit()` when the leaf index exceeds the tree depth (`>= 2^32`). Prevents wasted gas on impossible proofs. |
|
|
169
170
|
| `CCIPHelper` | `CCIPHelper_UnsupportedChain(uint256 chainId)` | Replaces bare `revert("Unsupported chain")` strings with a typed error. |
|
|
170
171
|
| `JBSuckerRegistry` | `JBSuckerRegistry_FeeExceedsMax(uint256 fee, uint256 max)` | Thrown when `setToRemoteFee` is called with a fee exceeding `MAX_TO_REMOTE_FEE`. |
|
|
171
172
|
|
|
@@ -188,6 +189,13 @@ In v5, `JBSucker.initialize()` used `msg.sender` to set the `deployer` field. In
|
|
|
188
189
|
|
|
189
190
|
## 3. Event Changes
|
|
190
191
|
|
|
192
|
+
### 3.0 Indexer Notes
|
|
193
|
+
|
|
194
|
+
This repo requires the largest schema change for subgraphs:
|
|
195
|
+
- any entity keyed by beneficiary, remote address, or remote token must now support raw `bytes32`;
|
|
196
|
+
- do not assume every remote identifier can be rendered as an EVM checksum address;
|
|
197
|
+
- separate transport-fee accounting from bridged-token accounting, since `toRemoteFee` replaced `minBridgeAmount`.
|
|
198
|
+
|
|
191
199
|
### 3.1 New Events
|
|
192
200
|
|
|
193
201
|
See section 2.3 above.
|
|
@@ -440,3 +448,37 @@ Throughout the codebase, function calls were updated to use named argument synta
|
|
|
440
448
|
| `ARBChains` | `ARBChains` | Identical |
|
|
441
449
|
| `CCIPHelper` | `CCIPHelper` | Bare `revert("Unsupported chain")` replaced with typed `CCIPHelper_UnsupportedChain` error. |
|
|
442
450
|
| `MerkleLib` | `MerkleLib` | Identical |
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## 9. Post-Audit Fixes
|
|
455
|
+
|
|
456
|
+
### 9.1 Deploy Script Resumability (NEW-L-1)
|
|
457
|
+
|
|
458
|
+
The `Deploy.s.sol` script's `_optimismSucker()`, `_baseSucker()`, `_arbitrumSucker()`, and `_deployCCIPSuckerWith()` functions previously returned early when the deployer contract existed at the CREATE2 address, without checking whether singleton configuration or registry allowlisting had completed. If a non-Sphinx deployment was interrupted after deployer creation but before `setChainSpecificConstants()`, `configureSingleton()`, or `allowSuckerDeployers()`, re-running the script would skip the incomplete deployer, leaving it unusable.
|
|
459
|
+
|
|
460
|
+
**Fix**: The early return now requires both `singleton != address(0)` and `REGISTRY.suckerDeployerIsAllowed(deployer)` to be true. If the deployer exists but is not fully configured, the script resumes from where it left off — skipping already-completed steps (deployer creation, chain-specific constants, singleton deployment) and executing only the missing ones. A new `_computeAddress()` helper was added to reuse the CREATE2 address computation logic.
|
|
461
|
+
|
|
462
|
+
### 9.2 Fee Fallback DoS on Zero-Cost Bridges
|
|
463
|
+
|
|
464
|
+
The `JBSucker.toRemote()` fee payment has a best-effort pattern: if the fee terminal is missing or the `pay()` call reverts, the transaction should proceed without the fee. Previously, the catch block and no-terminal branch reset `transportPayment = msg.value`, restoring the fee amount into the transport payment. This caused zero-cost bridges (OP, Base, Celo, Arbitrum L2->L1) to revert with `JBSucker_UnexpectedMsgValue` because they check `if (transportPayment != 0) revert`.
|
|
465
|
+
|
|
466
|
+
**Fix**: On fee payment failure, `transportPayment` is no longer overwritten. It stays at `msg.value - _toRemoteFee` (which is 0 when the caller sends exactly the fee amount). The fee ETH is retained by the sucker contract rather than being added back to the transport payment. This preserves bridge compatibility for all zero-cost bridge implementations.
|
|
467
|
+
|
|
468
|
+
### 9.3 Index Bounds Check in `_validate` and `_validateForEmergencyExit`
|
|
469
|
+
|
|
470
|
+
`_validate()` and `_validateForEmergencyExit()` did not check whether the leaf `index` exceeded the tree depth (`2^32 - 1`). While the merkle proof verification would still fail for out-of-range indices (since no valid proof exists), the missing check allowed unnecessary gas consumption and unclear revert messages.
|
|
471
|
+
|
|
472
|
+
**Fix**: Both functions now revert with `JBSucker_IndexOutOfRange(index)` if `index >= (1 << _TREE_DEPTH)`. This is a new custom error added to `JBSucker`.
|
|
473
|
+
|
|
474
|
+
### 9.4 Missing `wethOfChain` Entries for OP Sepolia and Base Sepolia
|
|
475
|
+
|
|
476
|
+
`CCIPHelper.wethOfChain()` did not have entries for `OP_SEP_ID` (Optimism Sepolia) or `BASE_SEP_ID` (Base Sepolia), causing CCIP sucker deployments on those testnets to revert with `CCIPHelper_UnsupportedChain`.
|
|
477
|
+
|
|
478
|
+
**Fix**: Added `OP_SEP_WETH` and `BASE_SEP_WETH` constants (`0x4200000000000000000000000000000000000006`) and corresponding branches in `wethOfChain()`.
|
|
479
|
+
|
|
480
|
+
### 9.5 NatSpec Fix for Salt Encoding
|
|
481
|
+
|
|
482
|
+
The NatSpec comment on `JBSucker.peer()` incorrectly described the deployer's salt computation as `keccak256(abi.encode(_msgSender(), salt))`. The actual code in `JBSuckerDeployer.createForSender()` uses `keccak256(abi.encodePacked(_msgSender(), salt))`.
|
|
483
|
+
|
|
484
|
+
**Fix**: Updated the NatSpec to say `abi.encodePacked` to match the implementation.
|
package/SKILLS.md
CHANGED
|
@@ -109,6 +109,7 @@ Cross-chain token and fund bridging for Juicebox V6 projects, using merkle trees
|
|
|
109
109
|
| `JBSucker_Deprecated` | `JBSucker` | `prepare` or `toRemote` called when sucker is `SENDING_DISABLED` or `DEPRECATED` |
|
|
110
110
|
| `JBSucker_DeprecationTimestampTooSoon` | `JBSucker` | `setDeprecation` timestamp is less than `_maxMessagingDelay()` in the future |
|
|
111
111
|
| `JBSucker_ExpectedMsgValue` | `JBSucker` | `toRemote` called without required `msg.value` for transport payment |
|
|
112
|
+
| `JBSucker_IndexOutOfRange` | `JBSucker` | Leaf index exceeds tree depth (`>= 2^32`) in `_validate` or `_validateForEmergencyExit` |
|
|
112
113
|
| `JBSucker_InsufficientBalance` | `JBSucker` | Emergency hatch exit amount exceeds outbox balance |
|
|
113
114
|
| `JBSucker_InsufficientMsgValue` | `JBSucker` | `msg.value` is less than the `toRemoteFee` |
|
|
114
115
|
| `JBSucker_InvalidMessageVersion` | `JBSucker` | `fromRemote` receives a message with wrong `MESSAGE_VERSION` |
|
|
@@ -158,6 +159,8 @@ Cross-chain token and fund bridging for Juicebox V6 projects, using merkle trees
|
|
|
158
159
|
- `toRemote` fee payment is best-effort: if the fee project has no native token terminal or `terminal.pay()` reverts, `toRemote` proceeds without collecting the fee. The caller still receives project tokens from the fee payment when it succeeds.
|
|
159
160
|
- `JBCCIPSucker` transport payment refund uses a low-level `call` that does NOT revert on failure. If the refund fails (e.g., caller is a non-payable contract), the excess ETH is permanently stuck. The `TransportPaymentRefundFailed` event provides observability.
|
|
160
161
|
- The sucker has an unrestricted `receive()` function -- it must accept ETH from bridges, WETH unwrapping, and terminal cash-outs. Excess ETH increases `amountToAddToBalanceOf` for the project (not a double-spend risk).
|
|
162
|
+
- `fromRemote()` validates the peer using `msg.sender`, not `_msgSender()`. Using `_msgSender()` would allow a trusted ERC-2771 forwarder to spoof the bridge peer address. Never use a meta-tx forwarder as a relay for `fromRemote` calls.
|
|
163
|
+
- `ccipReceive()` validates the CCIP router using `msg.sender`, not `_msgSender()`, for the same reason. A trusted forwarder could append the router address via the ERC-2771 calldata suffix and fully control the `Any2EVMMessage` struct.
|
|
161
164
|
|
|
162
165
|
### CRITICAL: NATIVE_TOKEN Mismatch on Non-ETH Chains
|
|
163
166
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/suckers-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@arbitrum/nitro-contracts": "^1.2.1",
|
|
22
|
-
"@bananapus/address-registry-v6": "^0.0.
|
|
23
|
-
"@bananapus/core-v6": "^0.0.
|
|
22
|
+
"@bananapus/address-registry-v6": "^0.0.16",
|
|
23
|
+
"@bananapus/core-v6": "^0.0.28",
|
|
24
24
|
"@bananapus/permission-ids-v6": "^0.0.14",
|
|
25
25
|
"@chainlink/contracts-ccip": "^1.5.0",
|
|
26
26
|
"@chainlink/local": "github:smartcontractkit/chainlink-local",
|