1
+ /*
2
+ Copyright 2022 Set Labs Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+
16
+ SPDX-License-Identifier: Apache License, Version 2.0
17
+ */
18
+
19
+ pragma solidity 0.6.10 ;
20
+
21
+ import "@openzeppelin/contracts/math/SafeMath.sol " ;
22
+ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
23
+ import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol " ;
24
+ import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol " ;
25
+
26
+ import "../../../interfaces/IAmmAdapter.sol " ;
27
+ import "../../../interfaces/external/IArrakisVaultV1.sol " ;
28
+
29
+ /**
30
+ * @title UniswapV3AmmAdapter
31
+ * @author Zishan Sami
32
+ *
33
+ * Adapter for Arrakis Vault representing Uniswap V3 liquidity position that encodes adding and removing liquidty
34
+ */
35
+ contract ArrakisUniswapV3AmmAdapter is IAmmAdapter {
36
+ using SafeMath for uint256 ;
37
+
38
+ /* ============ State Variables ============ */
39
+
40
+ // Address of Arrakis Router contract
41
+ address public immutable router;
42
+
43
+ // UniswapV3 factory contract
44
+ IUniswapV3Factory public immutable uniV3Factory;
45
+
46
+ // Internal function string for adding liquidity
47
+ string internal constant ADD_LIQUIDITY =
48
+ "addLiquidity(address,uint256,uint256,uint256,uint256,address) " ;
49
+ // Internal function string for removing liquidity
50
+ string internal constant REMOVE_LIQUIDITY =
51
+ "removeLiquidity(address,uint256,uint256,uint256,address) " ;
52
+
53
+ /* ============ Constructor ============ */
54
+
55
+ /**
56
+ * Set state variables
57
+ *
58
+ * @param _router Address of Arrakis Router contract
59
+ * @param _uniV3Factory Address of UniswapV3 Factory contract
60
+ */
61
+ constructor (address _router , address _uniV3Factory ) public {
62
+ require (_router != address (0 ),"_router address must not be zero address " );
63
+ require (_uniV3Factory != address (0 ),"_uniV3Factory address must not be zero address " );
64
+ router = _router;
65
+ uniV3Factory = IUniswapV3Factory (_uniV3Factory);
66
+ }
67
+
68
+ /* ============ External Getter Functions ============ */
69
+
70
+ /**
71
+ * Return calldata for the add liquidity call
72
+ *
73
+ * @param _setToken Address of the SetToken
74
+ * @param _pool Address of liquidity token
75
+ * @param _components Token address array required to remove liquidity
76
+ * @param _maxTokensIn AmountsIn desired to add liquidity
77
+ * @param _minLiquidity Min liquidity amount to add
78
+ */
79
+ function getProvideLiquidityCalldata (
80
+ address _setToken ,
81
+ address _pool ,
82
+ address [] calldata _components ,
83
+ uint256 [] calldata _maxTokensIn ,
84
+ uint256 _minLiquidity
85
+ )
86
+ external
87
+ view
88
+ override
89
+ returns (address target , uint256 value , bytes memory data )
90
+ {
91
+ address setToken = _setToken;
92
+ address [] memory components = _components;
93
+ uint256 [] memory maxTokensIn = _maxTokensIn;
94
+ uint256 minLiquidity = _minLiquidity;
95
+
96
+ require (maxTokensIn[0 ] > 0 && maxTokensIn[1 ] > 0 , "Component quantity must be nonzero " );
97
+
98
+ IArrakisVaultV1 arrakisVaultPool = IArrakisVaultV1 (_pool);
99
+
100
+ // Sort the amount in order of tokens stored in Arrakis Pool
101
+ (uint256 maxTokensInA , uint256 maxTokensInB ) = _getOrderedAmount (components[0 ], components[1 ], maxTokensIn[0 ], maxTokensIn[1 ]);
102
+
103
+ (uint256 amountAMin , uint256 amountBMin , uint256 liquidityExpectedFromSuppliedTokens ) = arrakisVaultPool.getMintAmounts (maxTokensInA, maxTokensInB);
104
+
105
+ require (
106
+ minLiquidity <= liquidityExpectedFromSuppliedTokens,
107
+ "_minLiquidity is too high for input token limit "
108
+ );
109
+
110
+ target = router;
111
+ value = 0 ;
112
+ data = abi.encodeWithSignature (
113
+ ADD_LIQUIDITY,
114
+ arrakisVaultPool,
115
+ maxTokensInA,
116
+ maxTokensInB,
117
+ amountAMin,
118
+ amountBMin,
119
+ setToken
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Return calldata for the add liquidity call for a single asset
125
+ */
126
+ function getProvideLiquiditySingleAssetCalldata (
127
+ address /*_setToken*/ ,
128
+ address /*_pool*/ ,
129
+ address /*_component*/ ,
130
+ uint256 /*_maxTokenIn*/ ,
131
+ uint256 /*_minLiquidity*/
132
+ )
133
+ external
134
+ view
135
+ override
136
+ returns (address /*target*/ , uint256 /*value*/ , bytes memory /*data*/ )
137
+ {
138
+ revert ("Arrakis single asset addition is not supported " );
139
+ }
140
+
141
+ /**
142
+ * Return calldata for the remove liquidity call
143
+ *
144
+ * @param _setToken Address of the SetToken
145
+ * @param _pool Address of liquidity token
146
+ * @param _components Token address array required to remove liquidity
147
+ * @param _minTokensOut AmountsOut minimum to remove liquidity
148
+ * @param _liquidity Liquidity amount to remove
149
+ */
150
+ function getRemoveLiquidityCalldata (
151
+ address _setToken ,
152
+ address _pool ,
153
+ address [] calldata _components ,
154
+ uint256 [] calldata _minTokensOut ,
155
+ uint256 _liquidity
156
+ )
157
+ external
158
+ view
159
+ override
160
+ returns (address target , uint256 value , bytes memory data )
161
+ {
162
+ address setToken = _setToken;
163
+ address [] memory components = _components;
164
+ uint256 [] memory minTokensOut = _minTokensOut;
165
+ uint256 liquidity = _liquidity;
166
+ IArrakisVaultV1 arrakisVaultPool = IArrakisVaultV1 (_pool);
167
+
168
+ // Make sure that only up to the amount of liquidity tokens owned by the Set Token are redeemed
169
+ uint256 setTokenLiquidityBalance = arrakisVaultPool.balanceOf (setToken);
170
+ require (liquidity <= setTokenLiquidityBalance, "_liquidity must be <= to current balance " );
171
+
172
+ // Checks for minTokensOut
173
+ require (minTokensOut[0 ] > 0 && minTokensOut[1 ] > 0 , "Minimum quantity must be nonzero " );
174
+
175
+ // Sort the amount in order of tokens stored in Arrakis Pool
176
+ (uint256 minTokensOutA , uint256 minTokensOutB ) = _getOrderedAmount (components[0 ], components[1 ], minTokensOut[0 ], minTokensOut[1 ]);
177
+
178
+ target = router;
179
+ value = 0 ;
180
+ data = abi.encodeWithSignature (
181
+ REMOVE_LIQUIDITY,
182
+ arrakisVaultPool,
183
+ liquidity,
184
+ minTokensOutA,
185
+ minTokensOutB,
186
+ setToken
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Return calldata for the remove liquidity single asset call
192
+ */
193
+ function getRemoveLiquiditySingleAssetCalldata (
194
+ address /* _setToken */ ,
195
+ address /*_pool*/ ,
196
+ address /*_component*/ ,
197
+ uint256 /*_minTokenOut*/ ,
198
+ uint256 /*_liquidity*/
199
+ )
200
+ external
201
+ view
202
+ override
203
+ returns (address /*target*/ , uint256 /*value*/ , bytes memory /*data*/ )
204
+ {
205
+ revert ("Arrakis single asset removal is not supported " );
206
+ }
207
+
208
+ /**
209
+ * Returns the address of the spender
210
+ */
211
+ function getSpenderAddress (address /*_pool*/ )
212
+ external
213
+ view
214
+ override
215
+ returns (address spender )
216
+ {
217
+ spender = router;
218
+ }
219
+
220
+ /**
221
+ * Verifies that this is an Arrakis Vault pool holding valid UniswapV3 position
222
+ *
223
+ * @param _pool Address of liquidity token
224
+ * @param _components Address array of supplied/requested tokens
225
+ */
226
+ function isValidPool (address _pool , address [] memory _components )
227
+ external
228
+ view
229
+ override
230
+ returns (bool )
231
+ {
232
+ // Attempt to get the tokens of the provided pool
233
+ address token0;
234
+ address token1;
235
+ try IArrakisVaultV1 (_pool).token0 () returns (IERC20 _token0 ) {
236
+ token0 = address (_token0);
237
+ } catch {
238
+ return false ;
239
+ }
240
+ try IArrakisVaultV1 (_pool).token1 () returns (IERC20 _token1 ) {
241
+ token1 = address (_token1);
242
+ } catch {
243
+ return false ;
244
+ }
245
+
246
+ // Make sure that components length is two
247
+ if (_components.length != 2 ) {
248
+ return false ;
249
+ }
250
+
251
+ // Make sure that _components[0] is either of token0 or token1
252
+ if (! (_components[0 ] == token0 || _components[0 ] == token1) ) {
253
+ return false ;
254
+ }
255
+
256
+ // Make sure that _components[1] is either of token0 or token1
257
+ if (! (_components[1 ] == token0 || _components[1 ] == token1) ) {
258
+ return false ;
259
+ }
260
+
261
+ // Make sure the pool address follows IERC20 interface
262
+ try IArrakisVaultV1 (_pool).totalSupply () returns (uint256 ) {
263
+ } catch {
264
+ return false ;
265
+ }
266
+
267
+ return true ;
268
+ }
269
+
270
+ /**
271
+ * Sorts the amount in order of tokens stored in Arrakis/UniswapV3 Pool
272
+ *
273
+ * @param _token0 Address of token0
274
+ * @param _token1 Address of token1
275
+ * @param _amount0 Amount of token0
276
+ * @param _amount1 Amount of token1
277
+ */
278
+ function _getOrderedAmount (address _token0 , address _token1 , uint256 _amount0 , uint256 _amount1 ) private pure returns (uint256 , uint256 ) {
279
+ return _token0 < _token1 ? (_amount0, _amount1) : (_amount1, _amount0);
280
+ }
281
+ }
0 commit comments