@bananapus/suckers-v6 0.0.48 → 0.0.49
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 +3 -3
- package/src/JBArbitrumSucker.sol +4 -1
- package/src/JBCeloSucker.sol +2 -0
- package/src/JBOptimismSucker.sol +2 -0
- package/src/JBSucker.sol +35 -1
- package/src/JBSuckerRegistry.sol +76 -57
- package/src/JBSwapCCIPSucker.sol +33 -96
- package/src/interfaces/IJBSucker.sol +11 -6
- package/src/interfaces/IJBSuckerRegistry.sol +6 -3
- package/src/libraries/CCIPHelper.sol +64 -64
- package/src/libraries/JBCCIPLib.sol +18 -0
- package/src/libraries/JBSuckerLib.sol +157 -161
- package/src/libraries/JBSwapPoolLib.sol +268 -268
- package/src/structs/PeerValueScratch.sol +18 -0
|
@@ -183,6 +183,45 @@ library JBSwapPoolLib {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/// @notice Execute the body of a V3 swap callback. Called via DELEGATECALL from the sucker's
|
|
187
|
+
/// `uniswapV3SwapCallback` so the V3 callback logic lives in library bytecode.
|
|
188
|
+
/// @dev DELEGATECALL preserves msg.sender (the V3 pool), allowing pool verification.
|
|
189
|
+
/// @param v3Factory The Uniswap V3 factory for pool verification.
|
|
190
|
+
/// @param amount0Delta The amount of token0 used for the swap.
|
|
191
|
+
/// @param amount1Delta The amount of token1 used for the swap.
|
|
192
|
+
/// @param data Encoded (originalTokenIn, normalizedTokenIn, normalizedTokenOut).
|
|
193
|
+
function executeV3SwapCallback(
|
|
194
|
+
IUniswapV3Factory v3Factory,
|
|
195
|
+
int256 amount0Delta,
|
|
196
|
+
int256 amount1Delta,
|
|
197
|
+
bytes calldata data
|
|
198
|
+
)
|
|
199
|
+
external
|
|
200
|
+
{
|
|
201
|
+
// Decode the callback data packed during _executeV3Swap.
|
|
202
|
+
(address originalTokenIn, address normalizedIn, address normalizedOut) =
|
|
203
|
+
abi.decode(data, (address, address, address));
|
|
204
|
+
|
|
205
|
+
// Verify caller is a legitimate V3 pool via the factory.
|
|
206
|
+
uint24 fee = IUniswapV3Pool(msg.sender).fee();
|
|
207
|
+
address expectedPool = v3Factory.getPool({tokenA: normalizedIn, tokenB: normalizedOut, fee: fee});
|
|
208
|
+
if (msg.sender != expectedPool) revert JBSwapPoolLib_CallerNotPool(msg.sender);
|
|
209
|
+
|
|
210
|
+
// The positive delta is what we owe to the pool.
|
|
211
|
+
// The V3 pool callback guarantees exactly one positive delta for exact-input swaps.
|
|
212
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
213
|
+
uint256 amountToSend = amount0Delta < 0 ? uint256(amount1Delta) : uint256(amount0Delta);
|
|
214
|
+
|
|
215
|
+
// If input is the native token, wrap for V3.
|
|
216
|
+
// When originalTokenIn == NATIVE_TOKEN, normalizedIn is already the wrapped native address.
|
|
217
|
+
if (originalTokenIn == JBConstants.NATIVE_TOKEN) {
|
|
218
|
+
IWrappedNativeToken(normalizedIn).deposit{value: amountToSend}();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Transfer the owed tokens to the V3 pool.
|
|
222
|
+
IERC20(normalizedIn).safeTransfer({to: msg.sender, value: amountToSend});
|
|
223
|
+
}
|
|
224
|
+
|
|
186
225
|
/// @notice Execute the body of a V4 unlock callback. Called via DELEGATECALL from the sucker's
|
|
187
226
|
/// `unlockCallback` so the V4 swap logic lives in library bytecode instead of the sucker's.
|
|
188
227
|
/// @dev DELEGATECALL preserves msg.sender, address(this), and the sucker's token balances.
|
|
@@ -265,45 +304,6 @@ library JBSwapPoolLib {
|
|
|
265
304
|
return abi.encode(amountOut);
|
|
266
305
|
}
|
|
267
306
|
|
|
268
|
-
/// @notice Execute the body of a V3 swap callback. Called via DELEGATECALL from the sucker's
|
|
269
|
-
/// `uniswapV3SwapCallback` so the V3 callback logic lives in library bytecode.
|
|
270
|
-
/// @dev DELEGATECALL preserves msg.sender (the V3 pool), allowing pool verification.
|
|
271
|
-
/// @param v3Factory The Uniswap V3 factory for pool verification.
|
|
272
|
-
/// @param amount0Delta The amount of token0 used for the swap.
|
|
273
|
-
/// @param amount1Delta The amount of token1 used for the swap.
|
|
274
|
-
/// @param data Encoded (originalTokenIn, normalizedTokenIn, normalizedTokenOut).
|
|
275
|
-
function executeV3SwapCallback(
|
|
276
|
-
IUniswapV3Factory v3Factory,
|
|
277
|
-
int256 amount0Delta,
|
|
278
|
-
int256 amount1Delta,
|
|
279
|
-
bytes calldata data
|
|
280
|
-
)
|
|
281
|
-
external
|
|
282
|
-
{
|
|
283
|
-
// Decode the callback data packed during _executeV3Swap.
|
|
284
|
-
(address originalTokenIn, address normalizedIn, address normalizedOut) =
|
|
285
|
-
abi.decode(data, (address, address, address));
|
|
286
|
-
|
|
287
|
-
// Verify caller is a legitimate V3 pool via the factory.
|
|
288
|
-
uint24 fee = IUniswapV3Pool(msg.sender).fee();
|
|
289
|
-
address expectedPool = v3Factory.getPool({tokenA: normalizedIn, tokenB: normalizedOut, fee: fee});
|
|
290
|
-
if (msg.sender != expectedPool) revert JBSwapPoolLib_CallerNotPool(msg.sender);
|
|
291
|
-
|
|
292
|
-
// The positive delta is what we owe to the pool.
|
|
293
|
-
// The V3 pool callback guarantees exactly one positive delta for exact-input swaps.
|
|
294
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
295
|
-
uint256 amountToSend = amount0Delta < 0 ? uint256(amount1Delta) : uint256(amount0Delta);
|
|
296
|
-
|
|
297
|
-
// If input is the native token, wrap for V3.
|
|
298
|
-
// When originalTokenIn == NATIVE_TOKEN, normalizedIn is already the wrapped native address.
|
|
299
|
-
if (originalTokenIn == JBConstants.NATIVE_TOKEN) {
|
|
300
|
-
IWrappedNativeToken(normalizedIn).deposit{value: amountToSend}();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Transfer the owed tokens to the V3 pool.
|
|
304
|
-
IERC20(normalizedIn).safeTransfer({to: msg.sender, value: amountToSend});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
307
|
//*********************************************************************//
|
|
308
308
|
// ----------------------- external views ---------------------------- //
|
|
309
309
|
//*********************************************************************//
|
|
@@ -518,46 +518,42 @@ library JBSwapPoolLib {
|
|
|
518
518
|
}
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
/// @notice
|
|
522
|
-
/// @param
|
|
523
|
-
/// @param
|
|
524
|
-
/// @param
|
|
525
|
-
/// @param
|
|
526
|
-
/// @param
|
|
527
|
-
/// @
|
|
528
|
-
/// @return
|
|
529
|
-
function
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
address
|
|
533
|
-
address
|
|
534
|
-
|
|
521
|
+
/// @notice Compute the sigmoid slippage tolerance for a given swap.
|
|
522
|
+
/// @param amountIn The amount of input tokens.
|
|
523
|
+
/// @param liquidity The pool's in-range liquidity.
|
|
524
|
+
/// @param tokenOut The output token address.
|
|
525
|
+
/// @param tokenIn The input token address.
|
|
526
|
+
/// @param arithmeticMeanTick The arithmetic mean tick from the TWAP.
|
|
527
|
+
/// @param poolFeeBps The pool's fee in basis points.
|
|
528
|
+
/// @return The slippage tolerance in basis points (out of _SLIPPAGE_DENOMINATOR).
|
|
529
|
+
function _getSlippageTolerance(
|
|
530
|
+
uint256 amountIn,
|
|
531
|
+
uint128 liquidity,
|
|
532
|
+
address tokenOut,
|
|
533
|
+
address tokenIn,
|
|
534
|
+
int24 arithmeticMeanTick,
|
|
535
|
+
uint256 poolFeeBps
|
|
535
536
|
)
|
|
536
537
|
internal
|
|
537
|
-
|
|
538
|
-
returns (
|
|
538
|
+
pure
|
|
539
|
+
returns (uint256)
|
|
539
540
|
{
|
|
540
|
-
//
|
|
541
|
-
|
|
541
|
+
// Sort the tokens to determine swap direction.
|
|
542
|
+
address token0 = tokenOut < tokenIn ? tokenOut : tokenIn;
|
|
543
|
+
bool zeroForOne = tokenIn == token0;
|
|
542
544
|
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
currency0: Currency.wrap(sorted0),
|
|
546
|
-
currency1: Currency.wrap(sorted1),
|
|
547
|
-
fee: fee,
|
|
548
|
-
tickSpacing: tickSpacing,
|
|
549
|
-
hooks: IHooks(hookAddr)
|
|
550
|
-
});
|
|
545
|
+
// Get the sqrt price at the mean tick for impact calculation.
|
|
546
|
+
uint160 sqrtP = V3TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
|
|
551
547
|
|
|
552
|
-
//
|
|
553
|
-
|
|
548
|
+
// If sqrtP is zero, return maximum slippage (accept any output).
|
|
549
|
+
if (sqrtP == 0) return _SLIPPAGE_DENOMINATOR;
|
|
554
550
|
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
551
|
+
// Calculate the price impact of the swap.
|
|
552
|
+
uint256 impact =
|
|
553
|
+
JBSwapLib.calculateImpact({amountIn: amountIn, liquidity: liquidity, sqrtP: sqrtP, zeroForOne: zeroForOne});
|
|
558
554
|
|
|
559
|
-
//
|
|
560
|
-
|
|
555
|
+
// Map the impact to a sigmoid slippage tolerance.
|
|
556
|
+
return JBSwapLib.getSlippageTolerance({impact: impact, poolFeeBps: poolFeeBps});
|
|
561
557
|
}
|
|
562
558
|
|
|
563
559
|
/// @notice Get a TWAP-based quote with dynamic slippage for a V3 pool.
|
|
@@ -685,77 +681,6 @@ library JBSwapPoolLib {
|
|
|
685
681
|
});
|
|
686
682
|
}
|
|
687
683
|
|
|
688
|
-
/// @notice Checks whether a V3 pool can serve the full default TWAP window.
|
|
689
|
-
/// @dev Reads the observation ring directly so discovery can skip young pools without reverting. The oldest
|
|
690
|
-
/// initialized observation must be at least `_DEFAULT_TWAP_WINDOW` seconds old.
|
|
691
|
-
/// @param pool The V3 pool to inspect.
|
|
692
|
-
/// @return True if the pool has enough initialized history for a default-window TWAP.
|
|
693
|
-
function _v3PoolHasFullTwapHistory(IUniswapV3Pool pool) internal view returns (bool) {
|
|
694
|
-
// slot0 gives the current observation cursor and total initialized/available observation slots.
|
|
695
|
-
(bool slot0Ok, uint16 observationIndex, uint16 observationCardinality) = _v3ObservationStateOf(pool);
|
|
696
|
-
if (!slot0Ok || observationCardinality == 0) return false;
|
|
697
|
-
|
|
698
|
-
// In a full ring, the next slot after the cursor is the oldest observation.
|
|
699
|
-
uint256 oldestIndex = (uint256(observationIndex) + 1) % uint256(observationCardinality);
|
|
700
|
-
(bool observationOk, uint32 observationTimestamp, bool initialized) =
|
|
701
|
-
_v3ObservationOf({pool: pool, index: oldestIndex});
|
|
702
|
-
if (!observationOk) return false;
|
|
703
|
-
|
|
704
|
-
// If the ring has not wrapped yet, slot 0 is the oldest initialized observation.
|
|
705
|
-
if (!initialized) {
|
|
706
|
-
(observationOk, observationTimestamp, initialized) = _v3ObservationOf({pool: pool, index: 0});
|
|
707
|
-
if (!observationOk || !initialized) return false;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
return _observationIsOldEnough({observationTimestamp: observationTimestamp, window: _DEFAULT_TWAP_WINDOW});
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
/// @notice Reads the observation cursor and cardinality from a V3 pool's slot0.
|
|
714
|
-
/// @dev Uses `staticcall` instead of the typed interface so malformed or hooklike candidate pools are rejected
|
|
715
|
-
/// as unusable candidates without reverting the whole bounded pool-discovery scan.
|
|
716
|
-
/// @param pool The V3 pool candidate to inspect.
|
|
717
|
-
/// @return ok True if `slot0()` returned enough data to decode.
|
|
718
|
-
/// @return observationIndex The pool's current observation cursor.
|
|
719
|
-
/// @return observationCardinality The number of initialized/available observation slots.
|
|
720
|
-
function _v3ObservationStateOf(IUniswapV3Pool pool)
|
|
721
|
-
internal
|
|
722
|
-
view
|
|
723
|
-
returns (bool ok, uint16 observationIndex, uint16 observationCardinality)
|
|
724
|
-
{
|
|
725
|
-
// Pool discovery intentionally probes candidate pools in a bounded fee-tier list; failed probes mean "skip".
|
|
726
|
-
(bool success, bytes memory data) =
|
|
727
|
-
address(pool).staticcall(abi.encodeWithSelector(IUniswapV3PoolState.slot0.selector));
|
|
728
|
-
if (!success || data.length < 224) return (false, 0, 0);
|
|
729
|
-
|
|
730
|
-
(,, observationIndex, observationCardinality,,,) =
|
|
731
|
-
abi.decode(data, (uint160, int24, uint16, uint16, uint16, uint8, bool));
|
|
732
|
-
ok = true;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/// @notice Reads one V3 observation.
|
|
736
|
-
/// @dev Uses `staticcall` so a bad candidate pool cannot interrupt pool discovery.
|
|
737
|
-
/// @param pool The V3 pool candidate to inspect.
|
|
738
|
-
/// @param index The observation ring index to read.
|
|
739
|
-
/// @return ok True if the observation returned enough data to decode.
|
|
740
|
-
/// @return observationTimestamp The timestamp stored at `index`.
|
|
741
|
-
/// @return initialized True if the observation slot has been initialized.
|
|
742
|
-
function _v3ObservationOf(
|
|
743
|
-
IUniswapV3Pool pool,
|
|
744
|
-
uint256 index
|
|
745
|
-
)
|
|
746
|
-
internal
|
|
747
|
-
view
|
|
748
|
-
returns (bool ok, uint32 observationTimestamp, bool initialized)
|
|
749
|
-
{
|
|
750
|
-
// Pool discovery intentionally probes candidate pools in a bounded fee-tier list; failed probes mean "skip".
|
|
751
|
-
(bool success, bytes memory data) =
|
|
752
|
-
address(pool).staticcall(abi.encodeWithSelector(IUniswapV3PoolState.observations.selector, index));
|
|
753
|
-
if (!success || data.length < 128) return (false, 0, false);
|
|
754
|
-
|
|
755
|
-
(observationTimestamp,,, initialized) = abi.decode(data, (uint32, int56, uint160, bool));
|
|
756
|
-
ok = true;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
684
|
/// @notice Check whether an oracle observation is old enough to cover a TWAP window ending at the current block.
|
|
760
685
|
/// @dev Current or future timestamps are rejected before subtracting so the age check cannot underflow.
|
|
761
686
|
/// @param observationTimestamp The timestamp recorded in the pool's oracle observation.
|
|
@@ -769,27 +694,46 @@ library JBSwapPoolLib {
|
|
|
769
694
|
return block.timestamp - observationTimestamp >= window;
|
|
770
695
|
}
|
|
771
696
|
|
|
772
|
-
/// @notice
|
|
773
|
-
/// @
|
|
774
|
-
///
|
|
775
|
-
/// @param
|
|
776
|
-
/// @
|
|
777
|
-
|
|
778
|
-
|
|
697
|
+
/// @notice Probe a single V4 pool configuration for liquidity.
|
|
698
|
+
/// @param poolManager The Uniswap V4 pool manager to query.
|
|
699
|
+
/// @param sorted0 The lower-address token in the pair (sorted).
|
|
700
|
+
/// @param sorted1 The higher-address token in the pair (sorted).
|
|
701
|
+
/// @param hookAddr The hook address to use for this pool configuration.
|
|
702
|
+
/// @param tierIndex The fee tier index (0-3) to probe.
|
|
703
|
+
/// @return key The constructed pool key for this configuration.
|
|
704
|
+
/// @return poolLiquidity The current in-range liquidity of the pool, or 0 if uninitialized.
|
|
705
|
+
function _probeV4Pool(
|
|
706
|
+
IPoolManager poolManager,
|
|
707
|
+
address sorted0,
|
|
708
|
+
address sorted1,
|
|
709
|
+
address hookAddr,
|
|
710
|
+
uint256 tierIndex
|
|
711
|
+
)
|
|
712
|
+
internal
|
|
713
|
+
view
|
|
714
|
+
returns (PoolKey memory key, uint128 poolLiquidity)
|
|
715
|
+
{
|
|
716
|
+
// Look up fee and tick spacing for this tier index.
|
|
717
|
+
(uint24 fee, int24 tickSpacing) = _v4FeeAndTickSpacing(tierIndex);
|
|
779
718
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
719
|
+
// Construct the pool key from the sorted tokens and tier parameters.
|
|
720
|
+
key = PoolKey({
|
|
721
|
+
currency0: Currency.wrap(sorted0),
|
|
722
|
+
currency1: Currency.wrap(sorted1),
|
|
723
|
+
fee: fee,
|
|
724
|
+
tickSpacing: tickSpacing,
|
|
725
|
+
hooks: IHooks(hookAddr)
|
|
726
|
+
});
|
|
783
727
|
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
)
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
728
|
+
// Derive the pool ID from the key.
|
|
729
|
+
PoolId id = key.toId();
|
|
730
|
+
|
|
731
|
+
// Check if pool is initialized (sqrtPriceX96 != 0).
|
|
732
|
+
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(id);
|
|
733
|
+
if (sqrtPriceX96 == 0) return (key, 0);
|
|
734
|
+
|
|
735
|
+
// Query the pool's current in-range liquidity.
|
|
736
|
+
poolLiquidity = poolManager.getLiquidity(id);
|
|
793
737
|
}
|
|
794
738
|
|
|
795
739
|
/// @notice Compute the minimum acceptable output using sigmoid slippage at the given tick.
|
|
@@ -842,119 +786,104 @@ library JBSwapPoolLib {
|
|
|
842
786
|
minAmountOut -= (minAmountOut * slippageTolerance) / _SLIPPAGE_DENOMINATOR;
|
|
843
787
|
}
|
|
844
788
|
|
|
845
|
-
/// @notice
|
|
846
|
-
/// @
|
|
847
|
-
/// @param
|
|
848
|
-
/// @param
|
|
849
|
-
/// @
|
|
850
|
-
/// @
|
|
851
|
-
/// @
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
uint256
|
|
855
|
-
uint128 liquidity,
|
|
856
|
-
address tokenOut,
|
|
857
|
-
address tokenIn,
|
|
858
|
-
int24 arithmeticMeanTick,
|
|
859
|
-
uint256 poolFeeBps
|
|
789
|
+
/// @notice Reads one V3 observation.
|
|
790
|
+
/// @dev Uses `staticcall` so a bad candidate pool cannot interrupt pool discovery.
|
|
791
|
+
/// @param pool The V3 pool candidate to inspect.
|
|
792
|
+
/// @param index The observation ring index to read.
|
|
793
|
+
/// @return ok True if the observation returned enough data to decode.
|
|
794
|
+
/// @return observationTimestamp The timestamp stored at `index`.
|
|
795
|
+
/// @return initialized True if the observation slot has been initialized.
|
|
796
|
+
function _v3ObservationOf(
|
|
797
|
+
IUniswapV3Pool pool,
|
|
798
|
+
uint256 index
|
|
860
799
|
)
|
|
861
800
|
internal
|
|
862
|
-
|
|
863
|
-
returns (
|
|
801
|
+
view
|
|
802
|
+
returns (bool ok, uint32 observationTimestamp, bool initialized)
|
|
864
803
|
{
|
|
865
|
-
//
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
// Get the sqrt price at the mean tick for impact calculation.
|
|
870
|
-
uint160 sqrtP = V3TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
|
|
804
|
+
// Pool discovery intentionally probes candidate pools in a bounded fee-tier list; failed probes mean "skip".
|
|
805
|
+
(bool success, bytes memory data) =
|
|
806
|
+
address(pool).staticcall(abi.encodeWithSelector(IUniswapV3PoolState.observations.selector, index));
|
|
807
|
+
if (!success || data.length < 128) return (false, 0, false);
|
|
871
808
|
|
|
872
|
-
|
|
873
|
-
|
|
809
|
+
(observationTimestamp,,, initialized) = abi.decode(data, (uint32, int56, uint160, bool));
|
|
810
|
+
ok = true;
|
|
811
|
+
}
|
|
874
812
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
813
|
+
/// @notice Reads the observation cursor and cardinality from a V3 pool's slot0.
|
|
814
|
+
/// @dev Uses `staticcall` instead of the typed interface so malformed or hooklike candidate pools are rejected
|
|
815
|
+
/// as unusable candidates without reverting the whole bounded pool-discovery scan.
|
|
816
|
+
/// @param pool The V3 pool candidate to inspect.
|
|
817
|
+
/// @return ok True if `slot0()` returned enough data to decode.
|
|
818
|
+
/// @return observationIndex The pool's current observation cursor.
|
|
819
|
+
/// @return observationCardinality The number of initialized/available observation slots.
|
|
820
|
+
function _v3ObservationStateOf(IUniswapV3Pool pool)
|
|
821
|
+
internal
|
|
822
|
+
view
|
|
823
|
+
returns (bool ok, uint16 observationIndex, uint16 observationCardinality)
|
|
824
|
+
{
|
|
825
|
+
// Pool discovery intentionally probes candidate pools in a bounded fee-tier list; failed probes mean "skip".
|
|
826
|
+
(bool success, bytes memory data) =
|
|
827
|
+
address(pool).staticcall(abi.encodeWithSelector(IUniswapV3PoolState.slot0.selector));
|
|
828
|
+
if (!success || data.length < 224) return (false, 0, 0);
|
|
878
829
|
|
|
879
|
-
|
|
880
|
-
|
|
830
|
+
(,, observationIndex, observationCardinality,,,) =
|
|
831
|
+
abi.decode(data, (uint160, int24, uint16, uint16, uint16, uint8, bool));
|
|
832
|
+
ok = true;
|
|
881
833
|
}
|
|
882
834
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
835
|
+
/// @notice Checks whether a V3 pool can serve the full default TWAP window.
|
|
836
|
+
/// @dev Reads the observation ring directly so discovery can skip young pools without reverting. The oldest
|
|
837
|
+
/// initialized observation must be at least `_DEFAULT_TWAP_WINDOW` seconds old.
|
|
838
|
+
/// @param pool The V3 pool to inspect.
|
|
839
|
+
/// @return True if the pool has enough initialized history for a default-window TWAP.
|
|
840
|
+
function _v3PoolHasFullTwapHistory(IUniswapV3Pool pool) internal view returns (bool) {
|
|
841
|
+
// slot0 gives the current observation cursor and total initialized/available observation slots.
|
|
842
|
+
(bool slot0Ok, uint16 observationIndex, uint16 observationCardinality) = _v3ObservationStateOf(pool);
|
|
843
|
+
if (!slot0Ok || observationCardinality == 0) return false;
|
|
886
844
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
/// @param amount The amount of input tokens to swap.
|
|
893
|
-
/// @return amountOut The amount of output tokens received.
|
|
894
|
-
function _quoteAndSwapV4(
|
|
895
|
-
SwapConfig memory config,
|
|
896
|
-
PoolKey memory key,
|
|
897
|
-
address normalizedTokenIn,
|
|
898
|
-
address normalizedTokenOut,
|
|
899
|
-
address originalTokenIn,
|
|
900
|
-
uint256 amount
|
|
901
|
-
)
|
|
902
|
-
internal
|
|
903
|
-
returns (uint256 amountOut)
|
|
904
|
-
{
|
|
905
|
-
// Get the TWAP-based minimum output for slippage protection.
|
|
906
|
-
uint256 minOut = _getV4Quote({
|
|
907
|
-
config: config,
|
|
908
|
-
key: key,
|
|
909
|
-
normalizedTokenIn: normalizedTokenIn,
|
|
910
|
-
normalizedTokenOut: normalizedTokenOut,
|
|
911
|
-
amount: amount
|
|
912
|
-
});
|
|
845
|
+
// In a full ring, the next slot after the cursor is the oldest observation.
|
|
846
|
+
uint256 oldestIndex = (uint256(observationIndex) + 1) % uint256(observationCardinality);
|
|
847
|
+
(bool observationOk, uint32 observationTimestamp, bool initialized) =
|
|
848
|
+
_v3ObservationOf({pool: pool, index: oldestIndex});
|
|
849
|
+
if (!observationOk) return false;
|
|
913
850
|
|
|
914
|
-
//
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
minAmountOut: minOut
|
|
922
|
-
});
|
|
851
|
+
// If the ring has not wrapped yet, slot 0 is the oldest initialized observation.
|
|
852
|
+
if (!initialized) {
|
|
853
|
+
(observationOk, observationTimestamp, initialized) = _v3ObservationOf({pool: pool, index: 0});
|
|
854
|
+
if (!observationOk || !initialized) return false;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return _observationIsOldEnough({observationTimestamp: observationTimestamp, window: _DEFAULT_TWAP_WINDOW});
|
|
923
858
|
}
|
|
924
859
|
|
|
925
|
-
/// @notice
|
|
926
|
-
/// @
|
|
927
|
-
///
|
|
928
|
-
/// @param
|
|
929
|
-
/// @
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
function _quoteAndSwapV3(
|
|
933
|
-
IUniswapV3Pool pool,
|
|
934
|
-
address normalizedTokenIn,
|
|
935
|
-
address normalizedTokenOut,
|
|
936
|
-
uint256 amount,
|
|
937
|
-
address originalTokenIn
|
|
938
|
-
)
|
|
939
|
-
internal
|
|
940
|
-
returns (uint256 amountOut)
|
|
941
|
-
{
|
|
942
|
-
// Get the TWAP-based minimum output for slippage protection.
|
|
943
|
-
uint256 minOut = _getV3TwapQuote({
|
|
944
|
-
pool: pool, normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut, amount: amount
|
|
945
|
-
});
|
|
860
|
+
/// @notice Check whether a V4 hooked pool can return cumulative ticks for the required TWAP window.
|
|
861
|
+
/// @dev Hookless pools return false. Reverting hooks and hooks that return fewer than two cumulative tick values
|
|
862
|
+
/// are treated as unusable for TWAP routing.
|
|
863
|
+
/// @param key The V4 pool key whose hook should be probed.
|
|
864
|
+
/// @return True if the hook can serve both the historical and current cumulative tick observations.
|
|
865
|
+
function _v4PoolHasTwap(PoolKey memory key) internal view returns (bool) {
|
|
866
|
+
if (address(key.hooks) == address(0)) return false;
|
|
946
867
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
868
|
+
uint32[] memory secondsAgos = new uint32[](2);
|
|
869
|
+
secondsAgos[0] = _V4_TWAP_WINDOW;
|
|
870
|
+
secondsAgos[1] = 0;
|
|
871
|
+
|
|
872
|
+
// Pool discovery intentionally probes candidate hooks in a bounded pool list. The seconds-per-liquidity array
|
|
873
|
+
// is not needed for the history check.
|
|
874
|
+
try IGeomeanOracle(address(key.hooks)).observe({key: key, secondsAgos: secondsAgos}) returns (
|
|
875
|
+
int56[] memory tickCumulatives, uint160[] memory
|
|
876
|
+
) {
|
|
877
|
+
return tickCumulatives.length >= 2;
|
|
878
|
+
} catch {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
956
881
|
}
|
|
957
882
|
|
|
883
|
+
//*********************************************************************//
|
|
884
|
+
// ----------------------- internal helpers -------------------------- //
|
|
885
|
+
//*********************************************************************//
|
|
886
|
+
|
|
958
887
|
/// @notice Execute a swap through a V3 pool.
|
|
959
888
|
/// @param pool The V3 pool to execute the swap on.
|
|
960
889
|
/// @param normalizedTokenIn The normalized input token address.
|
|
@@ -1070,14 +999,6 @@ library JBSwapPoolLib {
|
|
|
1070
999
|
amountOut = abi.decode(result, (uint256));
|
|
1071
1000
|
}
|
|
1072
1001
|
|
|
1073
|
-
/// @notice Normalize a token address, converting the NATIVE_TOKEN sentinel to the wrapped native token.
|
|
1074
|
-
/// @param token The token address to normalize.
|
|
1075
|
-
/// @param wrappedNativeToken The wrapped native token address on this chain.
|
|
1076
|
-
/// @return The normalized token address.
|
|
1077
|
-
function _normalize(address token, address wrappedNativeToken) internal pure returns (address) {
|
|
1078
|
-
return token == JBConstants.NATIVE_TOKEN ? wrappedNativeToken : token;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
1002
|
/// @notice Get the Uniswap V3 fee tier for a given index.
|
|
1082
1003
|
/// @param index The fee tier index (0 = 0.3%, 1 = 0.05%, 2 = 1%, 3 = 0.01%).
|
|
1083
1004
|
/// @return fee The fee tier in hundredths of a basis point.
|
|
@@ -1088,6 +1009,85 @@ library JBSwapPoolLib {
|
|
|
1088
1009
|
return 100;
|
|
1089
1010
|
}
|
|
1090
1011
|
|
|
1012
|
+
/// @notice Normalize a token address, converting the NATIVE_TOKEN sentinel to the wrapped native token.
|
|
1013
|
+
/// @param token The token address to normalize.
|
|
1014
|
+
/// @param wrappedNativeToken The wrapped native token address on this chain.
|
|
1015
|
+
/// @return The normalized token address.
|
|
1016
|
+
function _normalize(address token, address wrappedNativeToken) internal pure returns (address) {
|
|
1017
|
+
return token == JBConstants.NATIVE_TOKEN ? wrappedNativeToken : token;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/// @notice Quote via V3 TWAP and execute swap. Separate function for stack isolation.
|
|
1021
|
+
/// @param pool The V3 pool to swap through.
|
|
1022
|
+
/// @param normalizedTokenIn The normalized input token address.
|
|
1023
|
+
/// @param normalizedTokenOut The normalized output token address.
|
|
1024
|
+
/// @param amount The amount of input tokens to swap.
|
|
1025
|
+
/// @param originalTokenIn The original (pre-normalization) input token address.
|
|
1026
|
+
/// @return amountOut The amount of output tokens received.
|
|
1027
|
+
function _quoteAndSwapV3(
|
|
1028
|
+
IUniswapV3Pool pool,
|
|
1029
|
+
address normalizedTokenIn,
|
|
1030
|
+
address normalizedTokenOut,
|
|
1031
|
+
uint256 amount,
|
|
1032
|
+
address originalTokenIn
|
|
1033
|
+
)
|
|
1034
|
+
internal
|
|
1035
|
+
returns (uint256 amountOut)
|
|
1036
|
+
{
|
|
1037
|
+
// Get the TWAP-based minimum output for slippage protection.
|
|
1038
|
+
uint256 minOut = _getV3TwapQuote({
|
|
1039
|
+
pool: pool, normalizedTokenIn: normalizedTokenIn, normalizedTokenOut: normalizedTokenOut, amount: amount
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
// Execute the swap through the V3 pool.
|
|
1043
|
+
amountOut = _executeV3Swap({
|
|
1044
|
+
pool: pool,
|
|
1045
|
+
normalizedTokenIn: normalizedTokenIn,
|
|
1046
|
+
normalizedTokenOut: normalizedTokenOut,
|
|
1047
|
+
amount: amount,
|
|
1048
|
+
minAmountOut: minOut,
|
|
1049
|
+
originalTokenIn: originalTokenIn
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/// @notice Quote via V4 TWAP/spot and execute swap. Separate function for stack isolation.
|
|
1054
|
+
/// @param config The swap configuration (pool manager, wrapped native token addresses).
|
|
1055
|
+
/// @param key The V4 pool key to swap through.
|
|
1056
|
+
/// @param normalizedTokenIn The normalized input token address.
|
|
1057
|
+
/// @param normalizedTokenOut The normalized output token address.
|
|
1058
|
+
/// @param amount The amount of input tokens to swap.
|
|
1059
|
+
/// @return amountOut The amount of output tokens received.
|
|
1060
|
+
function _quoteAndSwapV4(
|
|
1061
|
+
SwapConfig memory config,
|
|
1062
|
+
PoolKey memory key,
|
|
1063
|
+
address normalizedTokenIn,
|
|
1064
|
+
address normalizedTokenOut,
|
|
1065
|
+
address originalTokenIn,
|
|
1066
|
+
uint256 amount
|
|
1067
|
+
)
|
|
1068
|
+
internal
|
|
1069
|
+
returns (uint256 amountOut)
|
|
1070
|
+
{
|
|
1071
|
+
// Get the TWAP-based minimum output for slippage protection.
|
|
1072
|
+
uint256 minOut = _getV4Quote({
|
|
1073
|
+
config: config,
|
|
1074
|
+
key: key,
|
|
1075
|
+
normalizedTokenIn: normalizedTokenIn,
|
|
1076
|
+
normalizedTokenOut: normalizedTokenOut,
|
|
1077
|
+
amount: amount
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
// Execute the swap through the V4 PoolManager.
|
|
1081
|
+
amountOut = _executeV4Swap({
|
|
1082
|
+
config: config,
|
|
1083
|
+
key: key,
|
|
1084
|
+
normalizedTokenIn: normalizedTokenIn,
|
|
1085
|
+
originalTokenIn: originalTokenIn,
|
|
1086
|
+
amount: amount,
|
|
1087
|
+
minAmountOut: minOut
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
1091
|
/// @notice Get the Uniswap V4 fee and tick spacing for a given tier index.
|
|
1092
1092
|
/// @param index The fee tier index (0 = 0.3%/60, 1 = 0.05%/10, 2 = 1%/200, 3 = 0.01%/1).
|
|
1093
1093
|
/// @return fee The fee in hundredths of a basis point.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
/// @notice Scratch space used while aggregating one value per peer chain across a project's suckers.
|
|
5
|
+
/// @dev Sized to the project's sucker count for in-memory de-duplication; `chainCount` tracks populated entries.
|
|
6
|
+
/// @custom:member chainIds The peer chain IDs that have been observed.
|
|
7
|
+
/// @custom:member values The selected aggregate value for each observed peer chain.
|
|
8
|
+
/// @custom:member snapshotTimestamps The freshness key associated with each selected value.
|
|
9
|
+
/// @custom:member hasActiveValue Whether the selected value came from an active sucker instead of a deprecated
|
|
10
|
+
/// fallback.
|
|
11
|
+
/// @custom:member chainCount The number of populated peer-chain entries.
|
|
12
|
+
struct PeerValueScratch {
|
|
13
|
+
uint256[] chainIds;
|
|
14
|
+
uint256[] values;
|
|
15
|
+
uint256[] snapshotTimestamps;
|
|
16
|
+
bool[] hasActiveValue;
|
|
17
|
+
uint256 chainCount;
|
|
18
|
+
}
|