@bananapus/suckers-v6 0.0.18 → 0.0.20
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 +19 -0
- package/SKILLS.md +1 -0
- package/package.json +3 -3
- package/src/JBSucker.sol +8 -1
- package/src/libraries/CCIPHelper.sol +6 -0
- package/test/unit/emergency.t.sol +9 -0
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
|
|
|
@@ -463,3 +464,21 @@ The `Deploy.s.sol` script's `_optimismSucker()`, `_baseSucker()`, `_arbitrumSuck
|
|
|
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`.
|
|
464
465
|
|
|
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` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/suckers-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@arbitrum/nitro-contracts": "^1.2.1",
|
|
22
22
|
"@bananapus/address-registry-v6": "^0.0.16",
|
|
23
|
-
"@bananapus/core-v6": "^0.0.
|
|
24
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
23
|
+
"@bananapus/core-v6": "^0.0.30",
|
|
24
|
+
"@bananapus/permission-ids-v6": "^0.0.15",
|
|
25
25
|
"@chainlink/contracts-ccip": "^1.5.0",
|
|
26
26
|
"@chainlink/local": "github:smartcontractkit/chainlink-local",
|
|
27
27
|
"@openzeppelin/contracts": "^5.6.1",
|
package/src/JBSucker.sol
CHANGED
|
@@ -68,6 +68,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
68
68
|
error JBSucker_TokenAlreadyMapped(address localToken, bytes32 mappedTo);
|
|
69
69
|
error JBSucker_TokenHasInvalidEmergencyHatchState(address token);
|
|
70
70
|
error JBSucker_TokenNotMapped(address token);
|
|
71
|
+
error JBSucker_IndexOutOfRange(uint256 index);
|
|
71
72
|
error JBSucker_UnexpectedMsgValue(uint256 value);
|
|
72
73
|
error JBSucker_ZeroBeneficiary();
|
|
73
74
|
error JBSucker_ZeroERC20Token();
|
|
@@ -227,7 +228,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
227
228
|
|
|
228
229
|
/// @notice The peer sucker on the remote chain, as a bytes32 for cross-VM compatibility.
|
|
229
230
|
/// @dev Defaults to `_toBytes32(address(this))`, assuming deterministic cross-chain deployment via CREATE2. The
|
|
230
|
-
/// deployer (`JBSuckerDeployer`) uses `salt = keccak256(abi.
|
|
231
|
+
/// deployer (`JBSuckerDeployer`) uses `salt = keccak256(abi.encodePacked(_msgSender(), salt))` to ensure
|
|
231
232
|
/// sender-specific determinism. This assumption breaks if CREATE2 conditions differ across chains (e.g.,
|
|
232
233
|
/// different factory nonces, different init code, or different deployer addresses). In such cases, subclasses
|
|
233
234
|
/// must override this function to return the correct peer address (e.g., a Solana program/PDA address for
|
|
@@ -1115,6 +1116,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1115
1116
|
)
|
|
1116
1117
|
internal
|
|
1117
1118
|
{
|
|
1119
|
+
// Ensure the index is within tree bounds (max 2^TREE_DEPTH - 1).
|
|
1120
|
+
if (index >= (1 << _TREE_DEPTH)) revert JBSucker_IndexOutOfRange(index);
|
|
1121
|
+
|
|
1118
1122
|
// Make sure the leaf has not already been executed.
|
|
1119
1123
|
if (_executedFor[terminalToken].get(index)) {
|
|
1120
1124
|
revert JBSucker_LeafAlreadyExecuted(terminalToken, index);
|
|
@@ -1201,6 +1205,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1201
1205
|
)
|
|
1202
1206
|
internal
|
|
1203
1207
|
{
|
|
1208
|
+
// Ensure the index is within tree bounds (max 2^TREE_DEPTH - 1).
|
|
1209
|
+
if (index >= (1 << _TREE_DEPTH)) revert JBSucker_IndexOutOfRange(index);
|
|
1210
|
+
|
|
1204
1211
|
// Make sure that the emergencyHatch is enabled for the token.
|
|
1205
1212
|
JBSuckerState deprecationState = state();
|
|
1206
1213
|
if (
|
|
@@ -53,6 +53,8 @@ library CCIPHelper {
|
|
|
53
53
|
address public constant AVA_WETH = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7;
|
|
54
54
|
address public constant BNB_WETH = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
|
|
55
55
|
address public constant BASE_WETH = 0x4200000000000000000000000000000000000006;
|
|
56
|
+
address public constant OP_SEP_WETH = 0x4200000000000000000000000000000000000006;
|
|
57
|
+
address public constant BASE_SEP_WETH = 0x4200000000000000000000000000000000000006;
|
|
56
58
|
|
|
57
59
|
function routerOfChain(uint256 _chainId) internal pure returns (address router) {
|
|
58
60
|
if (_chainId == ETH_ID) {
|
|
@@ -129,6 +131,10 @@ library CCIPHelper {
|
|
|
129
131
|
return ETH_SEP_WETH;
|
|
130
132
|
} else if (_chainId == ARB_SEP_ID) {
|
|
131
133
|
return ARB_SEP_WETH;
|
|
134
|
+
} else if (_chainId == OP_SEP_ID) {
|
|
135
|
+
return OP_SEP_WETH;
|
|
136
|
+
} else if (_chainId == BASE_SEP_ID) {
|
|
137
|
+
return BASE_SEP_WETH;
|
|
132
138
|
} else {
|
|
133
139
|
revert CCIPHelper_UnsupportedChain(_chainId);
|
|
134
140
|
}
|
|
@@ -35,6 +35,9 @@ contract SuckerEmergencyTest is Test {
|
|
|
35
35
|
|
|
36
36
|
/// @notice Ensures that if a sucker is deprecated and a claim is valid that a user can withdraw their deposit.
|
|
37
37
|
function testEmergencyExitWhenDeprecated(bool setAsDeprecated, bool isValidClaim, JBClaim memory claim) external {
|
|
38
|
+
// Bound index to valid tree range (< 2^32).
|
|
39
|
+
claim.leaf.index = bound(claim.leaf.index, 0, type(uint32).max);
|
|
40
|
+
|
|
38
41
|
uint256 projectId = 1;
|
|
39
42
|
TestSucker sucker = _createTestSucker(projectId, "");
|
|
40
43
|
|
|
@@ -84,6 +87,9 @@ contract SuckerEmergencyTest is Test {
|
|
|
84
87
|
|
|
85
88
|
/// @notice Ensures that if a sucker is send disabled and a claim is valid that a user can withdraw their deposit.
|
|
86
89
|
function testEmergencyExitWhenSendingDisabled(bool sendDisabled, bool isValidClaim, JBClaim memory claim) external {
|
|
90
|
+
// Bound index to valid tree range (< 2^32).
|
|
91
|
+
claim.leaf.index = bound(claim.leaf.index, 0, type(uint32).max);
|
|
92
|
+
|
|
87
93
|
uint256 projectId = 1;
|
|
88
94
|
TestSucker sucker = _createTestSucker(projectId, "");
|
|
89
95
|
|
|
@@ -139,6 +145,9 @@ contract SuckerEmergencyTest is Test {
|
|
|
139
145
|
)
|
|
140
146
|
external
|
|
141
147
|
{
|
|
148
|
+
// Bound index to valid tree range (< 2^32).
|
|
149
|
+
claim.leaf.index = bound(claim.leaf.index, 0, type(uint32).max);
|
|
150
|
+
|
|
142
151
|
uint256 projectId = 1;
|
|
143
152
|
TestSucker sucker = _createTestSucker(projectId, "");
|
|
144
153
|
|