@bananapus/suckers-v6 0.0.42 → 0.0.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/suckers-v6",
3
- "version": "0.0.42",
3
+ "version": "0.0.43",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -666,29 +666,69 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
666
666
  //*********************************************************************//
667
667
 
668
668
  /// @notice Find the nonce whose batch contains the given leaf index.
669
- /// @dev Scans from the newest nonce backwards so recent batches resolve first without storing extra cache state.
669
+ /// @dev Binary search by nonce. Source-side `prepare()` appends batches in nonce order with each
670
+ /// new batch's `batchStart` equal to the previous batch's `batchEnd`, so across populated
671
+ /// destination slots `_batchStartOf` is strictly increasing in nonce — the populated subset is
672
+ /// monotonic even when CCIP delivery is out of order and leaves intermediate slots empty.
673
+ /// Binary search exploits that monotonicity for O(log N) lookup.
674
+ /// @dev Gap handling: when the midpoint slot is empty (CCIP out-of-order delivery or sparse
675
+ /// attacker writes), the search falls back to a single linear scan over the remaining window.
676
+ /// This matches the previous linear-scan cost in the worst case, so the change is never worse
677
+ /// than the prior implementation. The fallback also keeps bytecode small enough that
678
+ /// JBSwapCCIPSucker stays under EIP-170.
670
679
  /// @param token The local token address.
671
680
  /// @param leafIndex The leaf index from the claim.
672
- /// @return The nonce of the batch containing this leaf, or 0 if no conversion rates exist.
681
+ /// @return The nonce of the batch containing this leaf, or 0 if no batches have been recorded.
673
682
  function _findNonceForLeafIndex(address token, uint256 leafIndex) internal view returns (uint64) {
683
+ // `_highestReceivedNonce` upper-bounds any populated slot for this token; zero means
684
+ // nothing has been received yet, so the leaf belongs to no batch.
674
685
  uint64 maxNonce = _highestReceivedNonce[token];
675
686
  if (maxNonce == 0) return 0;
676
687
 
677
- // Scan from most recent nonce backwards so the normal just-bridged claim path exits quickly.
678
- for (uint64 n = maxNonce; n >= 1; n--) {
679
- if (_nonceContainsLeaf({token: token, nonce: n, leafIndex: leafIndex})) return n;
688
+ // Nonce 0 is reserved by inbox initialization and never holds a batch, so search [1, max].
689
+ uint64 lo = 1;
690
+ uint64 hi = maxNonce;
691
+
692
+ // Wrap arithmetic in `unchecked` — `lo` and `hi` are always in `[1, maxNonce]` and the
693
+ // edge-guards (`mid == lo` / `mid == hi` breaks) prevent the only ways `mid - 1` or
694
+ // `mid + 1` could over/underflow. Skipping the compiler checks shaves enough bytecode to
695
+ // stay under EIP-170 without changing semantics.
696
+ unchecked {
697
+ while (lo <= hi) {
698
+ uint64 mid = lo + (hi - lo) / 2;
699
+ // `batchEnd == 0` is the established sentinel for "no batch recorded" (see the
700
+ // write guard in `ccipReceive`); a real batch always has `batchEnd > batchStart`.
701
+ uint256 end = _batchEndOf[token][mid];
702
+
703
+ // Empty midpoint from out-of-order CCIP delivery. Fall back to a tight linear
704
+ // scan over the remaining window — same complexity as the pre-fix path, but only
705
+ // on the gap branch (the audit's dense-nonce attack never trips this).
706
+ if (end == 0) {
707
+ for (uint64 n = lo; n <= hi; n++) {
708
+ uint256 nEnd = _batchEndOf[token][n];
709
+ if (nEnd == 0) continue;
710
+ uint256 nStart = _batchStartOf[token][n];
711
+ if (leafIndex >= nStart && leafIndex < nEnd) return n;
712
+ }
713
+ break;
714
+ }
715
+
716
+ // Each batch covers [batchStart, batchEnd). Across populated nonces these ranges
717
+ // are non-overlapping and strictly increasing, so the standard comparison applies.
718
+ uint256 start = _batchStartOf[token][mid];
719
+ if (leafIndex < start) {
720
+ if (mid == lo) break; // Guard against `mid - 1` underflow at the lower edge.
721
+ hi = mid - 1;
722
+ } else if (leafIndex >= end) {
723
+ if (mid == hi) break; // Mirror guard at the upper edge.
724
+ lo = mid + 1;
725
+ } else {
726
+ return mid; // `start <= leafIndex < end`: leaf is inside this batch.
727
+ }
728
+ }
680
729
  }
681
730
 
731
+ // Window collapsed without a hit — surface the same error the legacy linear scan used.
682
732
  revert JBSwapCCIPSucker_BatchNotReceived({nonce: 0});
683
733
  }
684
-
685
- /// @notice Check whether the given nonce's batch range contains the leaf index.
686
- /// @param token The local token address.
687
- /// @param nonce The nonce to check.
688
- /// @param leafIndex The leaf index to look for.
689
- /// @return True if the nonce's [start, end) range contains `leafIndex`.
690
- function _nonceContainsLeaf(address token, uint64 nonce, uint256 leafIndex) internal view returns (bool) {
691
- uint256 end = _batchEndOf[token][nonce];
692
- return end != 0 && leafIndex >= _batchStartOf[token][nonce] && leafIndex < end;
693
- }
694
734
  }