Skip to content

Commit 9ca1d11

Browse files
committed
feat: UniswapV4 mint/increase/decrease liquidity support
1 parent 2aed93a commit 9ca1d11

File tree

11 files changed

+2898
-9
lines changed

11 files changed

+2898
-9
lines changed

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,9 @@
4747
path = lib/spark-vaults-v2
4848
url = https://github.com/sparkdotfi/spark-vaults-v2
4949
branch = dev
50+
[submodule "lib/uniswap-v4-periphery"]
51+
path = lib/uniswap-v4-periphery
52+
url = https://github.com/Uniswap/v4-periphery.git
53+
[submodule "lib/uniswap-v4-core"]
54+
path = lib/uniswap-v4-core
55+
url = https://github.com/Uniswap/v4-core.git

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ remappings = [
1818
'@layerzerolabs/lz-evm-messagelib-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/messagelib/',
1919
'solidity-bytes-utils/=lib/solidity-bytes-utils/',
2020
'forge-std/=lib/forge-std/src/',
21+
'@uniswap/v4-core/=lib/uniswap-v4-core/'
2122
]
2223

2324
[fuzz]

lib/uniswap-v4-core

Submodule uniswap-v4-core added at e50237c

lib/uniswap-v4-periphery

Submodule uniswap-v4-periphery added at 3779387

src/MainnetController.sol

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { CCTPLib } from "./libraries/CCTPLib.sol";
2424
import { CurveLib } from "./libraries/CurveLib.sol";
2525
import { ERC4626Lib } from "./libraries/ERC4626Lib.sol";
2626
import { IDaiUsdsLike, IPSMLike, PSMLib } from "./libraries/PSMLib.sol";
27+
import { UniswapV4Lib } from "./libraries/UniswapV4Lib.sol";
2728

2829
import { OptionsBuilder } from "layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
2930

@@ -129,6 +130,12 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
129130
bool isWhitelisted
130131
);
131132
event RelayerRemoved(address indexed relayer);
133+
event UniswapV4TickLimitsSet(
134+
bytes32 indexed poolId,
135+
int24 tickLowerMin,
136+
int24 tickUpperMax,
137+
uint24 maxTickSpacing
138+
);
132139

133140
/**********************************************************************************************/
134141
/*** State variables ***/
@@ -153,6 +160,9 @@ 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 = UniswapV4Lib.LIMIT_DEPOSIT;
164+
bytes32 public LIMIT_UNISWAP_V4_WITHDRAW = UniswapV4Lib.LIMIT_WITHDRAW;
165+
bytes32 public LIMIT_UNISWAP_V4_SWAP = UniswapV4Lib.LIMIT_SWAP;
156166
bytes32 public LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP");
157167
bytes32 public LIMIT_USDC_TO_DOMAIN = keccak256("LIMIT_USDC_TO_DOMAIN");
158168
bytes32 public LIMIT_USDE_BURN = keccak256("LIMIT_USDE_BURN");
@@ -194,6 +204,9 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
194204
// ERC4626 exchange rate thresholds (1e36 precision)
195205
mapping(address token => uint256 maxExchangeRate) public maxExchangeRates;
196206

207+
// Uniswap V4 tick ranges
208+
mapping(bytes32 poolId => UniswapV4Lib.TickLimits tickLimits) public uniswapV4TickLimits;
209+
197210
/**********************************************************************************************/
198211
/*** Initialization ***/
199212
/**********************************************************************************************/
@@ -307,6 +320,31 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
307320
);
308321
}
309322

323+
function setUniswapV4TickLimits(
324+
bytes32 poolId,
325+
int24 tickLowerMin,
326+
int24 tickUpperMax,
327+
uint24 maxTickSpacing
328+
)
329+
external nonReentrant
330+
{
331+
_checkRole(DEFAULT_ADMIN_ROLE);
332+
333+
require(
334+
((tickLowerMin == 0) && (tickUpperMax == 0) && (maxTickSpacing == 0)) ||
335+
((maxTickSpacing > 0) && (tickLowerMin < tickUpperMax)),
336+
"MC/invalid-ticks"
337+
);
338+
339+
uniswapV4TickLimits[poolId] = UniswapV4Lib.TickLimits({
340+
tickLowerMin : tickLowerMin,
341+
tickUpperMax : tickUpperMax,
342+
maxTickSpacing : maxTickSpacing
343+
});
344+
345+
emit UniswapV4TickLimitsSet(poolId, tickLowerMin, tickUpperMax, maxTickSpacing);
346+
}
347+
310348
/**********************************************************************************************/
311349
/*** Freezer functions ***/
312350
/**********************************************************************************************/
@@ -598,6 +636,100 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
598636
}));
599637
}
600638

639+
/**********************************************************************************************/
640+
/*** Uniswap V4 functions ***/
641+
/**********************************************************************************************/
642+
643+
function mintPositionUniswapV4(
644+
bytes32 poolId,
645+
int24 tickLower,
646+
int24 tickUpper,
647+
uint128 liquidity,
648+
uint256 amount0Max,
649+
uint256 amount1Max
650+
)
651+
external nonReentrant
652+
{
653+
_checkRole(RELAYER);
654+
655+
UniswapV4Lib.mintPosition({
656+
proxy : address(proxy),
657+
rateLimits : address(rateLimits),
658+
poolId : poolId,
659+
tickLower : tickLower,
660+
tickUpper : tickUpper,
661+
liquidity : liquidity,
662+
amount0Max : amount0Max,
663+
amount1Max : amount1Max,
664+
tickLimits : uniswapV4TickLimits
665+
});
666+
}
667+
668+
function increaseLiquidityUniswapV4(
669+
bytes32 poolId,
670+
uint256 tokenId,
671+
uint128 liquidityIncrease,
672+
uint256 amount0Max,
673+
uint256 amount1Max
674+
)
675+
external nonReentrant
676+
{
677+
_checkRole(RELAYER);
678+
679+
UniswapV4Lib.increasePosition({
680+
proxy : address(proxy),
681+
rateLimits : address(rateLimits),
682+
poolId : poolId,
683+
tokenId : tokenId,
684+
liquidityIncrease : liquidityIncrease,
685+
amount0Max : amount0Max,
686+
amount1Max : amount1Max
687+
});
688+
}
689+
690+
function decreaseLiquidityUniswapV4(
691+
bytes32 poolId,
692+
uint256 tokenId,
693+
uint128 liquidityDecrease,
694+
uint256 amount0Min,
695+
uint256 amount1Min
696+
)
697+
external nonReentrant
698+
{
699+
_checkRole(RELAYER);
700+
701+
UniswapV4Lib.decreasePosition({
702+
proxy : address(proxy),
703+
rateLimits : address(rateLimits),
704+
poolId : poolId,
705+
tokenId : tokenId,
706+
liquidityDecrease : liquidityDecrease,
707+
amount0Min : amount0Min,
708+
amount1Min : amount1Min
709+
});
710+
}
711+
712+
function swapUniswapV4(
713+
bytes32 poolId,
714+
address tokenIn,
715+
uint128 amountIn,
716+
uint128 amountOutMin
717+
)
718+
external nonReentrant
719+
{
720+
_checkRole(RELAYER);
721+
722+
UniswapV4Lib.swap({
723+
proxy : address(proxy),
724+
rateLimits : address(rateLimits),
725+
poolId : poolId,
726+
tokenIn : tokenIn,
727+
amountIn : amountIn,
728+
amountOutMin : amountOutMin,
729+
maxSlippage : maxSlippages[address(uint160(uint256(poolId)))]
730+
});
731+
}
732+
601733
/**********************************************************************************************/
602734
/*** Relayer Ethena functions ***/
603735
/**********************************************************************************************/
@@ -813,7 +945,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
813945
});
814946

815947
// Query the min amount received on the destination chain and set it.
816-
( ,, OFTReceipt memory receipt ) = ILayerZero(oftAddress).quoteOFT(sendParams);
948+
( , , OFTReceipt memory receipt ) = ILayerZero(oftAddress).quoteOFT(sendParams);
817949
sendParams.minAmountLD = receipt.amountReceivedLD;
818950

819951
MessagingFee memory fee = ILayerZero(oftAddress).quoteSend(sendParams, false);

src/RateLimitHelpers.sol

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ pragma solidity ^0.8.21;
33

44
library RateLimitHelpers {
55

6-
function makeAddressKey(bytes32 key, address asset) internal pure returns (bytes32) {
7-
return keccak256(abi.encode(key, asset));
6+
function makeAddressKey(bytes32 key, address a) internal pure returns (bytes32) {
7+
return keccak256(abi.encode(key, a));
88
}
99

10-
function makeAddressAddressKey(bytes32 key, address asset, address destination) internal pure returns (bytes32) {
11-
return keccak256(abi.encode(key, asset, destination));
10+
function makeAddressAddressKey(bytes32 key, address a, address b) internal pure returns (bytes32) {
11+
return keccak256(abi.encode(key, a, b));
1212
}
1313

14-
function makeUint32Key(bytes32 key, uint32 domain) internal pure returns (bytes32) {
15-
return keccak256(abi.encode(key, domain));
14+
function makeBytes32Key(bytes32 key, bytes32 a) internal pure returns (bytes32) {
15+
return keccak256(abi.encode(key, a));
16+
}
17+
18+
function makeUint32Key(bytes32 key, uint32 a) internal pure returns (bytes32) {
19+
return keccak256(abi.encode(key, a));
1620
}
1721

1822
}

src/interfaces/Common.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
pragma solidity ^0.8.21;
3+
4+
interface IERC20Like {
5+
6+
function approve(address spender, uint256 amount) external returns (bool success);
7+
8+
function balanceOf(address account) external view returns (uint256 balance);
9+
10+
function decimals() external view returns (uint8 decimals);
11+
12+
}
13+
14+
interface IPermit2Like {
15+
16+
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
17+
18+
}

src/interfaces/UniswapV4.sol

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
pragma solidity ^0.8.21;
3+
4+
import { PoolId } from "../../lib/uniswap-v4-core/src/types/PoolId.sol";
5+
import { PoolKey } from "../../lib/uniswap-v4-core/src/types/PoolKey.sol";
6+
7+
import { PositionInfo } from "../../lib/uniswap-v4-periphery/src/libraries/PositionInfoLibrary.sol";
8+
9+
interface IPositionManagerLike {
10+
11+
function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable;
12+
13+
function getPoolAndPositionInfo(
14+
uint256 tokenId
15+
) external view returns (PoolKey memory poolKey, PositionInfo info);
16+
17+
function poolKeys(bytes25 poolId) external view returns (PoolKey memory poolKey);
18+
19+
function ownerOf(uint256 tokenId) external view returns (address owner);
20+
21+
}
22+
23+
24+
interface IUniversalRouterLike {
25+
26+
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external;
27+
28+
}

0 commit comments

Comments
 (0)