@bananapus/suckers-v6 0.0.43 → 0.0.46
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/CHANGELOG.md +9 -0
- package/package.json +2 -2
- package/src/JBSwapCCIPSucker.sol +73 -11
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,15 @@ This file describes the verified change from `nana-suckers-v5` to the current `n
|
|
|
15
15
|
- `JBCeloSucker`
|
|
16
16
|
- the deployers, structs, and interfaces under `src/`
|
|
17
17
|
|
|
18
|
+
## 0.0.46 — Bump nana-core-v6 to 0.0.53
|
|
19
|
+
|
|
20
|
+
`@bananapus/core-v6@0.0.53` ([nana-core-v6 PR #145](https://github.com/Bananapus/nana-core-v6/pull/145)) drops the `via_ir` requirement on `JBCashOutHookSpecsLib`, which lets this package consume the cross-project cashout work (`payAfterCashOutTokensOf` / `addToBalanceAfterCashOutTokensOf`) without needing `via_ir = true` in its own foundry profile.
|
|
21
|
+
|
|
22
|
+
- No src changes — suckers doesn't reference `IJBFeeTerminal.FEE()` or any of the touched core surfaces.
|
|
23
|
+
- All `JBRulesetMetadata` test literals patched to include `pauseCrossProjectFeeFreeInflows: false`.
|
|
24
|
+
|
|
25
|
+
`package.json`: version `0.0.44 → 0.0.46` (skipping 0.0.45 because nothing shipped at that intermediate revision), core dep `^0.0.48 → ^0.0.53`.
|
|
26
|
+
|
|
18
27
|
## Summary
|
|
19
28
|
|
|
20
29
|
- Cross-chain identifiers are now modeled for a wider address space. The v6 repo uses `bytes32` where the v5 repo used EVM `address` assumptions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/suckers-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@arbitrum/nitro-contracts": "3.2.0",
|
|
33
|
-
"@bananapus/core-v6": "^0.0.
|
|
33
|
+
"@bananapus/core-v6": "^0.0.53",
|
|
34
34
|
"@bananapus/permission-ids-v6": "^0.0.25",
|
|
35
35
|
"@chainlink/contracts-ccip": "1.6.4",
|
|
36
36
|
"@chainlink/local": "0.2.7",
|
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -179,6 +179,19 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
179
179
|
/// @custom:param token The local token address.
|
|
180
180
|
mapping(address token => uint64) internal _highestReceivedNonce;
|
|
181
181
|
|
|
182
|
+
/// @notice Count of populated batch nonces per token. Appended exactly once per batch in
|
|
183
|
+
/// `ccipReceive`, so it equals the number of received batches independent of CCIP ordering.
|
|
184
|
+
/// @custom:param token The local token address.
|
|
185
|
+
mapping(address token => uint64) internal _populatedNonceCount;
|
|
186
|
+
|
|
187
|
+
/// @notice Populated batch nonces per token, indexed by insertion order. Enables the
|
|
188
|
+
/// empty-midpoint fallback in `_findNonceForLeafIndex` to walk only the K populated nonces
|
|
189
|
+
/// (O(K) worst case) instead of the full [lo, hi] nonce span (O(N) worst case under sparse
|
|
190
|
+
/// adversarial patterns).
|
|
191
|
+
/// @custom:param token The local token address.
|
|
192
|
+
/// @custom:param index The insertion index in [0, _populatedNonceCount[token]).
|
|
193
|
+
mapping(address token => mapping(uint64 index => uint64 nonce)) internal _populatedNonceByIndex;
|
|
194
|
+
|
|
182
195
|
/// @notice Cumulative leaf count at the last `_sendRootOverAMB` call, per token.
|
|
183
196
|
/// @dev Used on the sender side to derive the batch start index for the next send.
|
|
184
197
|
/// @custom:param token The local token address.
|
|
@@ -366,8 +379,33 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
366
379
|
// Record the batch range so _findNonceForLeafIndex can resolve leaf ownership
|
|
367
380
|
// independently of nonce ordering. Each nonce is self-describing: [start, end).
|
|
368
381
|
if (batchEnd > 0) {
|
|
382
|
+
// Record this batch's half-open leaf range `[batchStart, batchEnd)`. Self-
|
|
383
|
+
// describing per-nonce — no implicit chain across nonces — so out-of-order
|
|
384
|
+
// delivery can still resolve a leaf to its batch.
|
|
369
385
|
_batchStartOf[localToken][nonce] = batchStart;
|
|
370
386
|
_batchEndOf[localToken][nonce] = batchEnd;
|
|
387
|
+
|
|
388
|
+
// Append `nonce` to the populated-nonce list for this token. The outer
|
|
389
|
+
// `_batchEndOf == 0 && _conversionRateOf == 0 && pendingSwapOf == 0` guard
|
|
390
|
+
// fires at most once per (token, nonce), so each populated nonce is appended
|
|
391
|
+
// exactly once — the array stays duplicate-free without extra checks.
|
|
392
|
+
//
|
|
393
|
+
// Reading `_populatedNonceCount[localToken]` first into a local lets us write
|
|
394
|
+
// the new slot and the new count in a single read-modify-write pair (one
|
|
395
|
+
// SLOAD, two SSTOREs to distinct slots). The `unchecked` increment is safe:
|
|
396
|
+
// `priorCount` is bounded by the total number of populated nonces, which is
|
|
397
|
+
// upper-bounded by the CCIP nonce space (`uint64`) — overflow requires more
|
|
398
|
+
// batches than `uint64.max`, which the inbox can never produce.
|
|
399
|
+
uint64 priorCount = _populatedNonceCount[localToken];
|
|
400
|
+
_populatedNonceByIndex[localToken][priorCount] = nonce;
|
|
401
|
+
unchecked {
|
|
402
|
+
_populatedNonceCount[localToken] = priorCount + 1;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Track the highest nonce ever observed for this token. Read by
|
|
406
|
+
// `_findNonceForLeafIndex` as the binary-search upper bound. Out-of-order
|
|
407
|
+
// delivery keeps this monotonic — we only advance it when the new nonce is
|
|
408
|
+
// strictly higher than the prior maximum.
|
|
371
409
|
if (nonce > _highestReceivedNonce[localToken]) {
|
|
372
410
|
_highestReceivedNonce[localToken] = nonce;
|
|
373
411
|
}
|
|
@@ -672,10 +710,10 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
672
710
|
/// monotonic even when CCIP delivery is out of order and leaves intermediate slots empty.
|
|
673
711
|
/// Binary search exploits that monotonicity for O(log N) lookup.
|
|
674
712
|
/// @dev Gap handling: when the midpoint slot is empty (CCIP out-of-order delivery or sparse
|
|
675
|
-
/// attacker writes), the
|
|
676
|
-
///
|
|
677
|
-
///
|
|
678
|
-
///
|
|
713
|
+
/// attacker writes), the fallback walks `_populatedNonceByIndex` — the list of every nonce
|
|
714
|
+
/// actually populated. That bounds the worst case at `O(K)` SLOADs, where `K` is the number
|
|
715
|
+
/// of received batches, regardless of how sparse the populated set is inside `[1, maxNonce]`.
|
|
716
|
+
/// The empty-slot scans that drove `O(N)` under the prior linear fallback are eliminated.
|
|
679
717
|
/// @param token The local token address.
|
|
680
718
|
/// @param leafIndex The leaf index from the claim.
|
|
681
719
|
/// @return The nonce of the batch containing this leaf, or 0 if no batches have been recorded.
|
|
@@ -700,16 +738,40 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
700
738
|
// write guard in `ccipReceive`); a real batch always has `batchEnd > batchStart`.
|
|
701
739
|
uint256 end = _batchEndOf[token][mid];
|
|
702
740
|
|
|
703
|
-
// Empty midpoint from out-of-order CCIP delivery
|
|
704
|
-
//
|
|
705
|
-
//
|
|
741
|
+
// Empty midpoint from out-of-order CCIP delivery (the sender minted a higher nonce
|
|
742
|
+
// than the inbox has yet received, leaving holes in `[1, maxNonce]`) or a sparse
|
|
743
|
+
// pattern an attacker assembled by inflating `_highestReceivedNonce` without
|
|
744
|
+
// populating intermediate slots. The earlier linear `[lo, hi]` scan walked every
|
|
745
|
+
// empty slot, so worst-case cost was `O(maxNonce)` SLOADs — that's the residual
|
|
746
|
+
// the audit flagged. Walk `_populatedNonceByIndex` instead: it's `K` entries (one
|
|
747
|
+
// per received batch) and contains exactly the slots a real batch can cover.
|
|
706
748
|
if (end == 0) {
|
|
707
|
-
for
|
|
749
|
+
// Number of populated batch slots for this token — equal to the array length.
|
|
750
|
+
// Maintained by `ccipReceive`'s append (one SSTORE per first-time receive).
|
|
751
|
+
uint64 count = _populatedNonceCount[token];
|
|
752
|
+
|
|
753
|
+
// Linear walk over the populated set. Bounded by `count`, not `maxNonce`, so
|
|
754
|
+
// sparse adversarial patterns can't inflate this loop with empty slots.
|
|
755
|
+
for (uint64 i; i < count; i++) {
|
|
756
|
+
// Insertion-ordered nonce at index `i`. May be any value in `[1, maxNonce]`
|
|
757
|
+
// because CCIP delivery is out-of-order; the array is not sorted by nonce.
|
|
758
|
+
uint64 n = _populatedNonceByIndex[token][i];
|
|
759
|
+
|
|
760
|
+
// Read end of this batch's leaf range. Always `> 0` here — we only push to
|
|
761
|
+
// `_populatedNonceByIndex` after the corresponding `_batchEndOf` write.
|
|
708
762
|
uint256 nEnd = _batchEndOf[token][n];
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
763
|
+
|
|
764
|
+
// Coverage test: each batch's `[batchStart, batchEnd)` is non-overlapping
|
|
765
|
+
// across populated nonces, so a hit is unique. The `[lo, hi]` window from
|
|
766
|
+
// the binary search isn't applied — the binary-search invariant guarantees
|
|
767
|
+
// the leaf can only live in a populated nonce, so an out-of-window match
|
|
768
|
+
// would mean the binary search was already wrong; falling through to the
|
|
769
|
+
// next iteration costs less than the extra two compares per iteration.
|
|
770
|
+
if (leafIndex >= _batchStartOf[token][n] && leafIndex < nEnd) return n;
|
|
712
771
|
}
|
|
772
|
+
|
|
773
|
+
// No populated nonce contains `leafIndex` for this token. Break out of the
|
|
774
|
+
// outer binary-search loop and fall through to the shared revert below.
|
|
713
775
|
break;
|
|
714
776
|
}
|
|
715
777
|
|