@bananapus/suckers-v6 0.0.41 → 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/foundry.toml +1 -0
- package/package.json +1 -1
- package/src/JBSwapCCIPSucker.sol +63 -15
- package/src/libraries/JBSwapPoolLib.sol +13 -0
package/foundry.toml
CHANGED
package/package.json
CHANGED
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -309,6 +309,14 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
309
309
|
delivered: deliveredToken, expected: address(BRIDGE_TOKEN)
|
|
310
310
|
});
|
|
311
311
|
}
|
|
312
|
+
// Zero delivery alongside a positive root is structurally indistinguishable from
|
|
313
|
+
// "no delivery + positive root" — both leave the local sucker with nothing to back
|
|
314
|
+
// the leaves the root advertises. Reject so a peer cannot mint a claimable rate
|
|
315
|
+
// that records `leafTotal=N, localTotal=0` and lets later claims withdraw against
|
|
316
|
+
// unrelated balance.
|
|
317
|
+
if (leafTotal > 0 && deliveredAmount == 0) {
|
|
318
|
+
revert JBSwapCCIPSucker_PositiveRootWithoutDelivery(leafTotal);
|
|
319
|
+
}
|
|
312
320
|
}
|
|
313
321
|
}
|
|
314
322
|
|
|
@@ -658,29 +666,69 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
658
666
|
//*********************************************************************//
|
|
659
667
|
|
|
660
668
|
/// @notice Find the nonce whose batch contains the given leaf index.
|
|
661
|
-
/// @dev
|
|
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.
|
|
662
679
|
/// @param token The local token address.
|
|
663
680
|
/// @param leafIndex The leaf index from the claim.
|
|
664
|
-
/// @return The nonce of the batch containing this leaf, or 0 if no
|
|
681
|
+
/// @return The nonce of the batch containing this leaf, or 0 if no batches have been recorded.
|
|
665
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.
|
|
666
685
|
uint64 maxNonce = _highestReceivedNonce[token];
|
|
667
686
|
if (maxNonce == 0) return 0;
|
|
668
687
|
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
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
|
+
}
|
|
672
729
|
}
|
|
673
730
|
|
|
731
|
+
// Window collapsed without a hit — surface the same error the legacy linear scan used.
|
|
674
732
|
revert JBSwapCCIPSucker_BatchNotReceived({nonce: 0});
|
|
675
733
|
}
|
|
676
|
-
|
|
677
|
-
/// @notice Check whether the given nonce's batch range contains the leaf index.
|
|
678
|
-
/// @param token The local token address.
|
|
679
|
-
/// @param nonce The nonce to check.
|
|
680
|
-
/// @param leafIndex The leaf index to look for.
|
|
681
|
-
/// @return True if the nonce's [start, end) range contains `leafIndex`.
|
|
682
|
-
function _nonceContainsLeaf(address token, uint64 nonce, uint256 leafIndex) internal view returns (bool) {
|
|
683
|
-
uint256 end = _batchEndOf[token][nonce];
|
|
684
|
-
return end != 0 && leafIndex >= _batchStartOf[token][nonce] && leafIndex < end;
|
|
685
|
-
}
|
|
686
734
|
}
|
|
@@ -51,6 +51,7 @@ library JBSwapPoolLib {
|
|
|
51
51
|
error JBSwapPoolLib_InsufficientTwapHistory(address pool, uint256 availableWindow, uint256 requiredWindow);
|
|
52
52
|
error JBSwapPoolLib_NoLiquidity(address pool, PoolId poolId);
|
|
53
53
|
error JBSwapPoolLib_NoPool(address tokenIn, address tokenOut);
|
|
54
|
+
error JBSwapPoolLib_PartialFill(uint256 consumed, uint256 requested);
|
|
54
55
|
error JBSwapPoolLib_SlippageExceeded(uint256 amountOut, uint256 minAmountOut);
|
|
55
56
|
|
|
56
57
|
//*********************************************************************//
|
|
@@ -989,6 +990,18 @@ library JBSwapPoolLib {
|
|
|
989
990
|
data: abi.encode(originalTokenIn, normalizedTokenIn, normalizedTokenOut)
|
|
990
991
|
});
|
|
991
992
|
|
|
993
|
+
// Reject partial fills: when the V3 pool's price limit is hit before the full input is
|
|
994
|
+
// consumed, the pool returns only the consumed portion of `amount` and the caller is left
|
|
995
|
+
// holding the unconsumed remainder. The sucker's accounting assumes the full bridge amount
|
|
996
|
+
// was either swapped or made retryable, so a silent partial fill strands input tokens and
|
|
997
|
+
// breaks cross-chain solvency. Revert so the caller can either retry with a smaller size
|
|
998
|
+
// or wait for liquidity to return.
|
|
999
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1000
|
+
uint256 consumedAmount = uint256(zeroForOne ? amount0 : amount1);
|
|
1001
|
+
if (consumedAmount < amount) {
|
|
1002
|
+
revert JBSwapPoolLib_PartialFill({consumed: consumedAmount, requested: amount});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
992
1005
|
// Extract the output amount from the signed delta (negative = tokens received).
|
|
993
1006
|
amountOut = uint256(-(zeroForOne ? amount1 : amount0));
|
|
994
1007
|
|