Skip to content

Commit abe98f0

Browse files
committed
feat: UniswapV4 support
1 parent 7a87195 commit abe98f0

File tree

15 files changed

+1686
-9
lines changed

15 files changed

+1686
-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

.lintstagedrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"*.{json,md,sol,yml}": [
3+
"npm run prettier"
4+
],
5+
"*.sol": [
6+
"npm run solhint"
7+
]
8+
}

.prettierignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# NPM modules
2+
node_modules
3+
4+
# Solidity libraries
5+
lib

.prettierrc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"plugins": [
3+
"prettier-plugin-solidity"
4+
],
5+
"overrides": [
6+
{
7+
"files": "*.sol",
8+
"options": {
9+
"bracketSpacing": true,
10+
"parser": "solidity-parse",
11+
"printWidth": 120,
12+
"tabWidth": 4
13+
}
14+
},
15+
{
16+
"files": "*.md",
17+
"options": {
18+
"parser": "markdown",
19+
"printWidth": 120,
20+
"tabWidth": 2
21+
}
22+
}
23+
]
24+
}

.solhint.json

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
{
2+
"extends": [
3+
"solhint:recommended"
4+
],
5+
"plugins": [
6+
"prettier"
7+
],
8+
"rules": {
9+
"prettier/prettier": "error",
10+
"avoid-call-value": "off",
11+
"avoid-low-level-calls": "off",
12+
"avoid-sha3": "error",
13+
"avoid-tx-origin": "error",
14+
"code-complexity": [
15+
"warn",
16+
10
17+
],
18+
"compiler-version": [
19+
"warn",
20+
"^0.8.0"
21+
],
22+
"comprehensive-interface": "off",
23+
"const-name-snakecase": "error",
24+
"contract-name-capwords": "error",
25+
"duplicated-imports": "error",
26+
"event-name-capwords": "error",
27+
"explicit-types": [
28+
"error",
29+
"explicit"
30+
],
31+
"function-max-lines": [
32+
"warn",
33+
100
34+
],
35+
"func-name-mixedcase": "warn",
36+
"func-named-parameters": [
37+
"warn",
38+
6
39+
],
40+
"func-param-name-mixedcase": "error",
41+
"func-visibility": [
42+
"error",
43+
{
44+
"ignoreConstructors": true
45+
}
46+
],
47+
"gas-calldata-parameters": "warn",
48+
"gas-custom-errors": "warn",
49+
"gas-increment-by-one": "warn",
50+
"gas-indexed-events": "warn",
51+
"gas-named-return-values": "warn",
52+
"gas-small-strings": "warn",
53+
"immutable-vars-naming": [
54+
"error",
55+
{
56+
"immutablesAsConstants": false
57+
}
58+
],
59+
"imports-on-top": "error",
60+
"interface-starts-with-i": "error",
61+
"max-line-length": [
62+
"warn",
63+
120
64+
],
65+
"modifier-name-mixedcase": "error",
66+
"named-parameters-mapping": "warn",
67+
"no-console": "warn",
68+
"no-empty-blocks": "warn",
69+
"no-global-import": "error",
70+
"no-unused-import": "warn",
71+
"no-unused-vars": "warn",
72+
"no-inline-assembly": "off",
73+
"not-rely-on-time": "off",
74+
"ordering": "warn",
75+
"private-vars-leading-underscore": [
76+
"warn",
77+
{
78+
"strict": true
79+
}
80+
],
81+
"quotes": [
82+
"error",
83+
"double"
84+
],
85+
"reentrancy": "warn",
86+
"state-visibility": "error",
87+
"var-name-mixedcase": "error",
88+
"visibility-modifier-order": "error"
89+
}
90+
}

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 59d3ecf

lib/uniswap-v4-periphery

Submodule uniswap-v4-periphery added at 60cd938

src/MainnetController.sol

Lines changed: 153 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

@@ -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);

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
}

0 commit comments

Comments
 (0)