@@ -24,6 +24,7 @@ import { CCTPLib } from "./libraries/CCTPLib.sol";
2424import { CurveLib } from "./libraries/CurveLib.sol";
2525import { ERC4626Lib } from "./libraries/ERC4626Lib.sol";
2626import { IDaiUsdsLike, IPSMLike, PSMLib } from "./libraries/PSMLib.sol";
27+ import { UniswapV4Lib } from "./libraries/UniswapV4Lib.sol";
2728
2829import { OptionsBuilder } from "layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
2930
@@ -95,6 +96,11 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
9596 uint256 claimed18;
9697 }
9798
99+ struct UniswapV4Limits {
100+ int24 tickLowerMin;
101+ int24 tickUpperMax;
102+ }
103+
98104 /**********************************************************************************************/
99105 /*** Events ***/
100106 /**********************************************************************************************/
@@ -129,6 +135,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
129135 bool isWhitelisted
130136 );
131137 event RelayerRemoved(address indexed relayer);
138+ event UniswapV4TickLimitsSet(bytes32 indexed poolId, int24 tickLowerMin, int24 tickUpperMax);
132139
133140 /**********************************************************************************************/
134141 /*** State variables ***/
@@ -153,6 +160,8 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
153160 bytes32 public LIMIT_SPARK_VAULT_TAKE = keccak256("LIMIT_SPARK_VAULT_TAKE");
154161 bytes32 public LIMIT_SUPERSTATE_SUBSCRIBE = keccak256("LIMIT_SUPERSTATE_SUBSCRIBE");
155162 bytes32 public LIMIT_SUSDE_COOLDOWN = keccak256("LIMIT_SUSDE_COOLDOWN");
163+ bytes32 public LIMIT_UNISWAP_V4_DEPOSIT = keccak256("LIMIT_UNISWAP_V4_DEPOSIT");
164+ bytes32 public LIMIT_UNISWAP_V4_WITHDRAW = keccak256("LIMIT_UNISWAP_V4_WITHDRAW");
156165 bytes32 public LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP");
157166 bytes32 public LIMIT_USDC_TO_DOMAIN = keccak256("LIMIT_USDC_TO_DOMAIN");
158167 bytes32 public LIMIT_USDE_BURN = keccak256("LIMIT_USDE_BURN");
@@ -194,6 +203,9 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
194203 // ERC4626 exchange rate thresholds (1e36 precision)
195204 mapping(address token => uint256 maxExchangeRate) public maxExchangeRates;
196205
206+ // Uniswap V4 tick ranges
207+ mapping(bytes32 poolId => UniswapV4Limits uniswapV4Limits) public uniswapV4Limits;
208+
197209 /**********************************************************************************************/
198210 /*** Initialization ***/
199211 /**********************************************************************************************/
@@ -307,6 +319,25 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
307319 );
308320 }
309321
322+ function setUniswapV4TickLimits(
323+ bytes32 poolId,
324+ int24 tickLowerMin,
325+ int24 tickUpperMax
326+ )
327+ external nonReentrant
328+ {
329+ _checkRole(DEFAULT_ADMIN_ROLE);
330+
331+ require(tickLowerMin <= tickUpperMax, "MC/invalid-ticks");
332+
333+ uniswapV4Limits[poolId] = UniswapV4Limits({
334+ tickLowerMin : tickLowerMin,
335+ tickUpperMax : tickUpperMax
336+ });
337+
338+ emit UniswapV4TickLimitsSet(poolId, tickLowerMin, tickUpperMax);
339+ }
340+
310341 /**********************************************************************************************/
311342 /*** Freezer functions ***/
312343 /**********************************************************************************************/
@@ -598,6 +629,127 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
598629 }));
599630 }
600631
632+ /**********************************************************************************************/
633+ /*** Uniswap V4 functions ***/
634+ /**********************************************************************************************/
635+
636+ function mintPositionUniswapV4(
637+ bytes32 poolId,
638+ int24 tickUpper,
639+ int24 tickLower,
640+ uint128 liquidity,
641+ uint256 amount0Max,
642+ uint256 amount1Max
643+ )
644+ external nonReentrant
645+ {
646+ _checkRole(RELAYER);
647+
648+ UniswapV4Limits memory limits = uniswapV4Limits[poolId];
649+
650+ require(tickLower >= limits.tickLowerMin, "MC/tickLower-too-low");
651+ require(tickUpper <= limits.tickUpperMax, "MC/tickUpper-too-high");
652+
653+ // NOTE: `maxSlippages` is a mapping from address to uint256, so we have to take the lower
654+ // 160 bits of the id. It is possible, buit highly unliekly there us a collision.
655+ UniswapV4Lib.mintPosition({
656+ commonParams: UniswapV4Lib.CommonParams({
657+ proxy : address(proxy),
658+ rateLimits : address(rateLimits),
659+ rateLimitId : LIMIT_UNISWAP_V4_DEPOSIT,
660+ // TODO: Use central state contract
661+ maxSlippage : maxSlippages[address(uint160(uint256(poolId)))],
662+ poolId : poolId
663+ }),
664+ tickLower : tickLower,
665+ tickUpper : tickUpper,
666+ liquidity : liquidity,
667+ amount0Max : amount0Max,
668+ amount1Max : amount1Max
669+ });
670+ }
671+
672+ function increaseLiquidityUniswapV4(
673+ bytes32 poolId,
674+ uint256 tokenId,
675+ uint128 liquidityIncrease,
676+ uint256 amount0Max,
677+ uint256 amount1Max
678+ )
679+ external
680+ {
681+ _checkRole(RELAYER);
682+
683+ // NOTE: `maxSlippages` is a mapping from address to uint256, so we have to take the lower
684+ // 160 bits of the id. It is possible, buit highly unliekly there us a collision.
685+ UniswapV4Lib.increasePosition({
686+ commonParams: UniswapV4Lib.CommonParams({
687+ proxy : address(proxy),
688+ rateLimits : address(rateLimits),
689+ rateLimitId : LIMIT_UNISWAP_V4_DEPOSIT,
690+ // TODO: Use central state contract
691+ maxSlippage : maxSlippages[address(uint160(uint256(poolId)))],
692+ poolId : poolId
693+ }),
694+ tokenId : tokenId,
695+ liquidityIncrease : liquidityIncrease,
696+ amount0Max : amount0Max,
697+ amount1Max : amount1Max
698+ });
699+ }
700+
701+ function burnPositionUniswapV4(
702+ bytes32 poolId,
703+ uint256 tokenId,
704+ uint256 amount0Min,
705+ uint256 amount1Min
706+ )
707+ external
708+ {
709+ _checkRole(RELAYER);
710+
711+ UniswapV4Lib.burnPosition({
712+ commonParams: UniswapV4Lib.CommonParams({
713+ proxy : address(proxy),
714+ rateLimits : address(rateLimits),
715+ rateLimitId : LIMIT_UNISWAP_V4_WITHDRAW,
716+ // TODO: Use central state contract
717+ maxSlippage : maxSlippages[address(uint160(uint256(poolId)))],
718+ poolId : poolId
719+ }),
720+ tokenId : tokenId,
721+ amount0Min : amount0Min,
722+ amount1Min : amount1Min
723+ });
724+ }
725+
726+ function decreaseLiquidityUniswapV4(
727+ bytes32 poolId,
728+ uint256 tokenId,
729+ uint128 liquidityDecrease,
730+ uint256 amount0Min,
731+ uint256 amount1Min
732+ )
733+ external
734+ {
735+ _checkRole(RELAYER);
736+
737+ UniswapV4Lib.decreasePosition({
738+ commonParams: UniswapV4Lib.CommonParams({
739+ proxy : address(proxy),
740+ rateLimits : address(rateLimits),
741+ rateLimitId : LIMIT_UNISWAP_V4_WITHDRAW,
742+ // TODO: Use central state contract
743+ maxSlippage : maxSlippages[address(uint160(uint256(poolId)))],
744+ poolId : poolId
745+ }),
746+ tokenId : tokenId,
747+ liquidityDecrease : liquidityDecrease,
748+ amount0Min : amount0Min,
749+ amount1Min : amount1Min
750+ });
751+ }
752+
601753 /**********************************************************************************************/
602754 /*** Relayer Ethena functions ***/
603755 /**********************************************************************************************/
@@ -813,7 +965,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
813965 });
814966
815967 // Query the min amount received on the destination chain and set it.
816- ( ,, OFTReceipt memory receipt ) = ILayerZero(oftAddress).quoteOFT(sendParams);
968+ ( , , OFTReceipt memory receipt ) = ILayerZero(oftAddress).quoteOFT(sendParams);
817969 sendParams.minAmountLD = receipt.amountReceivedLD;
818970
819971 MessagingFee memory fee = ILayerZero(oftAddress).quoteSend(sendParams, false);
0 commit comments