-
Notifications
You must be signed in to change notification settings - Fork 11
uniswapv3 amm adapter #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 23 commits
ce0add3
10cde65
b02754f
3aa13c7
6f096a1
831a0b1
4193b04
0b475d5
c88ecd5
44a940a
755137f
375cd81
206a476
36f9257
3e4cfe1
be7d93e
7c55055
7d44cf2
a016dbb
5e51ada
6a7bb40
18de1cf
3a0acb8
2b65b92
ba5e4ce
aa6728f
5f789f3
6aadea0
f2a1145
570b65f
6982692
4663019
1df82d0
de8d270
bfb9d68
eec32a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /* | ||
| Copyright 2022 Set Labs Inc. | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| pragma solidity >=0.6.10 < 0.9; | ||
|
|
||
|
|
||
| import { | ||
| IUniswapV3Pool | ||
| } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; | ||
|
|
||
| interface IArrakisVaultV1 { | ||
| function mint(uint256 mintAmount, address receiver) | ||
| external | ||
| returns ( | ||
| uint256 amount0, | ||
| uint256 amount1, | ||
| uint128 liquidityMinted | ||
| ); | ||
|
|
||
| function burn(uint256 burnAmount, address receiver) | ||
| external | ||
| returns ( | ||
| uint256 amount0, | ||
| uint256 amount1, | ||
| uint128 liquidityBurned | ||
| ); | ||
|
|
||
| function getMintAmounts(uint256 amount0Max, uint256 amount1Max) | ||
| external | ||
| view | ||
| returns ( | ||
| uint256 amount0, | ||
| uint256 amount1, | ||
| uint256 mintAmount | ||
| ); | ||
|
|
||
| function getUnderlyingBalances() | ||
| external | ||
| view | ||
| returns (uint256 amount0, uint256 amount1); | ||
|
|
||
| function getUnderlyingBalancesAtPrice(uint160 sqrtRatioX96) | ||
| external | ||
| view | ||
| returns (uint256 amount0Current, uint256 amount1Current); | ||
|
|
||
| function getPositionID() external view returns (bytes32 positionID); | ||
|
|
||
| function token0() external view returns (address); | ||
|
|
||
| function token1() external view returns (address); | ||
|
|
||
| function upperTick() external view returns (int24); | ||
|
|
||
| function lowerTick() external view returns (int24); | ||
|
|
||
| function pool() external view returns (IUniswapV3Pool); | ||
|
|
||
| function totalSupply() external view returns (uint256); | ||
|
|
||
| function balanceOf(address account) external view returns (uint256); | ||
|
|
||
| function executiveRebalance( | ||
| int24 newLowerTick, | ||
| int24 newUpperTick, | ||
| uint160 swapThresholdPrice, | ||
| uint256 swapAmountBPS, | ||
| bool zeroForOne | ||
| ) external; | ||
|
|
||
| function withdrawManagerBalance() external; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,277 @@ | ||
| /* | ||
| Copyright 2022 Set Labs Inc. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be 2023 and (maybe) Index Coop. No ? |
||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| pragma solidity 0.8.17; | ||
|
|
||
| import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; | ||
| import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; | ||
|
|
||
| import "../../../interfaces/IAmmAdapter.sol"; | ||
| import "../../../interfaces/external/IArrakisVaultV1.sol"; | ||
|
|
||
| /** | ||
| * @title UniswapV3AmmAdapter | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The title should probably be the same as the contract name. |
||
| * @author Zishan Sami | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without diminishing Zishans contribution I'd suggest changing this to "Index Coop" as we usually do for all contracts I think. |
||
| * | ||
| * Adapter for Arrakis Vault representing Uniswap V3 liquidity position that encodes adding and removing liquidty | ||
| */ | ||
| contract ArrakisUniswapV3AmmAdapter is IAmmAdapter { | ||
| /* ============ State Variables ============ */ | ||
|
|
||
| // Address of Arrakis Router contract | ||
| address public immutable router; | ||
|
|
||
| // UniswapV3 factory contract | ||
| IUniswapV3Factory public immutable uniV3Factory; | ||
|
|
||
| // Internal function string for adding liquidity | ||
| string internal constant ADD_LIQUIDITY = | ||
| "addLiquidity(address,uint256,uint256,uint256,uint256,address)"; | ||
| // Internal function string for removing liquidity | ||
| string internal constant REMOVE_LIQUIDITY = | ||
| "removeLiquidity(address,uint256,uint256,uint256,address)"; | ||
|
|
||
| /* ============ Constructor ============ */ | ||
|
|
||
| /** | ||
| * Set state variables | ||
| * | ||
| * @param _router Address of Arrakis Router contract | ||
| * @param _uniV3Factory Address of UniswapV3 Factory contract | ||
| */ | ||
| constructor(address _router, address _uniV3Factory){ | ||
| require(_router != address(0),"_router address must not be zero address"); | ||
| require(_uniV3Factory != address(0),"_uniV3Factory address must not be zero address"); | ||
| router = _router; | ||
| uniV3Factory = IUniswapV3Factory(_uniV3Factory); | ||
| } | ||
|
|
||
| /* ============ External Getter Functions ============ */ | ||
|
|
||
| /** | ||
| * Return calldata for the add liquidity call | ||
| * | ||
| * @param _setToken Address of the SetToken | ||
| * @param _pool Address of liquidity token | ||
| * @param _components Token address array required to remove liquidity | ||
| * @param _maxTokensIn AmountsIn desired to add liquidity | ||
| * @param _minLiquidity Min liquidity amount to add | ||
| */ | ||
| function getProvideLiquidityCalldata( | ||
| address _setToken, | ||
| address _pool, | ||
| address[] calldata _components, | ||
| uint256[] calldata _maxTokensIn, | ||
| uint256 _minLiquidity | ||
| ) | ||
| external | ||
| view | ||
| override | ||
| returns (address target, uint256 value, bytes memory data) | ||
| { | ||
| address setToken = _setToken; | ||
| address[] memory components = _components; | ||
| uint256[] memory maxTokensIn = _maxTokensIn; | ||
| uint256 minLiquidity = _minLiquidity; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we doing these reassignments ? Seems like a waste of gas no ? (For the arrays I suggest just changing the type to "memory" in the call signature if those arrays need to be edited. If they are read only then calldata is cheaper anyway. |
||
|
|
||
| require(maxTokensIn[0] > 0 && maxTokensIn[1] > 0, "Component quantity must be nonzero"); | ||
|
|
||
| IArrakisVaultV1 arrakisVaultPool = IArrakisVaultV1(_pool); | ||
|
|
||
| // Sort the amount in order of tokens stored in Arrakis Pool | ||
| (uint256 maxTokensInA, uint256 maxTokensInB) = _getOrderedAmount(components[0], components[1], maxTokensIn[0], maxTokensIn[1]); | ||
|
|
||
| (uint256 amountAMin, uint256 amountBMin, uint256 liquidityExpectedFromSuppliedTokens) = arrakisVaultPool.getMintAmounts(maxTokensInA, maxTokensInB); | ||
|
|
||
| require( | ||
| minLiquidity <= liquidityExpectedFromSuppliedTokens, | ||
| "_minLiquidity is too high for input token limit" | ||
| ); | ||
|
|
||
| target = router; | ||
| value = 0; | ||
| data = abi.encodeWithSignature( | ||
| ADD_LIQUIDITY, | ||
| arrakisVaultPool, | ||
| maxTokensInA, | ||
| maxTokensInB, | ||
| amountAMin, | ||
| amountBMin, | ||
| setToken | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Return calldata for the add liquidity call for a single asset | ||
| */ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make sure all external methods have full docstring documentation including all parameters. |
||
| function getProvideLiquiditySingleAssetCalldata( | ||
| address /*_setToken*/, | ||
| address /*_pool*/, | ||
| address /*_component*/, | ||
| uint256 /*_maxTokenIn*/, | ||
| uint256 /*_minLiquidity*/ | ||
| ) | ||
| external | ||
| pure | ||
| override | ||
| returns (address /*target*/, uint256 /*value*/, bytes memory /*data*/) | ||
| { | ||
| revert("Arrakis single asset addition is not supported"); | ||
| } | ||
|
|
||
| /** | ||
| * Return calldata for the remove liquidity call | ||
| * | ||
| * @param _setToken Address of the SetToken | ||
| * @param _pool Address of liquidity token | ||
| * @param _components Token address array required to remove liquidity | ||
| * @param _minTokensOut AmountsOut minimum to remove liquidity | ||
| * @param _liquidity Liquidity amount to remove | ||
| */ | ||
| function getRemoveLiquidityCalldata( | ||
| address _setToken, | ||
| address _pool, | ||
| address[] calldata _components, | ||
| uint256[] calldata _minTokensOut, | ||
| uint256 _liquidity | ||
| ) | ||
| external | ||
| view | ||
| override | ||
| returns (address target, uint256 value, bytes memory data) | ||
| { | ||
| address setToken = _setToken; | ||
| address[] memory components = _components; | ||
| uint256[] memory minTokensOut = _minTokensOut; | ||
| uint256 liquidity = _liquidity; | ||
| IArrakisVaultV1 arrakisVaultPool = IArrakisVaultV1(_pool); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. Those reassignments seem unnecessary to me |
||
|
|
||
| // Make sure that only up to the amount of liquidity tokens owned by the Set Token are redeemed | ||
| uint256 setTokenLiquidityBalance = arrakisVaultPool.balanceOf(setToken); | ||
| require(liquidity <= setTokenLiquidityBalance, "_liquidity must be <= to current balance"); | ||
|
|
||
| // Checks for minTokensOut | ||
| require(minTokensOut[0] > 0 && minTokensOut[1] > 0, "Minimum quantity must be nonzero"); | ||
|
|
||
| // Sort the amount in order of tokens stored in Arrakis Pool | ||
| (uint256 minTokensOutA, uint256 minTokensOutB) = _getOrderedAmount(components[0], components[1], minTokensOut[0], minTokensOut[1]); | ||
|
|
||
| target = router; | ||
| value = 0; | ||
| data = abi.encodeWithSignature( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could do something like: Advantages:
|
||
| REMOVE_LIQUIDITY, | ||
| arrakisVaultPool, | ||
| liquidity, | ||
| minTokensOutA, | ||
| minTokensOutB, | ||
| setToken | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Return calldata for the remove liquidity single asset call | ||
| */ | ||
| function getRemoveLiquiditySingleAssetCalldata( | ||
| address /* _setToken */, | ||
| address /*_pool*/, | ||
| address /*_component*/, | ||
| uint256 /*_minTokenOut*/, | ||
| uint256 /*_liquidity*/ | ||
| ) | ||
| external | ||
| pure | ||
| override | ||
| returns (address /*target*/, uint256 /*value*/, bytes memory /*data*/) | ||
| { | ||
| revert("Arrakis single asset removal is not supported"); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the address of the spender | ||
| */ | ||
| function getSpenderAddress(address /*_pool*/) | ||
| external | ||
| view | ||
| override | ||
| returns (address spender) | ||
| { | ||
| spender = router; | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that this is an Arrakis Vault pool holding valid UniswapV3 position | ||
| * | ||
| * @param _pool Address of liquidity token | ||
| * @param _components Address array of supplied/requested tokens | ||
| */ | ||
| function isValidPool(address _pool, address[] memory _components) | ||
| external | ||
| view | ||
| override | ||
| returns (bool) | ||
| { | ||
| // Attempt to get the tokens of the provided pool | ||
| address token0; | ||
| address token1; | ||
| try IArrakisVaultV1(_pool).token0() returns (address _token0) { | ||
| token0 = _token0; | ||
| } catch { | ||
| return false; | ||
| } | ||
| try IArrakisVaultV1(_pool).token1() returns (address _token1) { | ||
| token1 = _token1; | ||
| } catch { | ||
| return false; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite understand why we can't have this line covered, especially since line 234 seems to be covered. Am I missing anything here ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its not that we couldn't, but having a condition where the token0 external call succeeds and the token1 call fails would have to be done with a mock contract that exhibits this behavior.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see. That explains why we can cover the first call easily but not the second one.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was scared of adding dependencies without upgrading existing dev deps, but I've heard of smock and would love to try it. If I run into an issue with configuring |
||
| } | ||
|
|
||
| // Make sure that components length is two | ||
| if (_components.length != 2) { | ||
| return false; | ||
| } | ||
|
|
||
| // Make sure that _components[0] is either of token0 or token1 | ||
| if (!(_components[0] == token0 || _components[0] == token1) ) { | ||
| return false; | ||
| } | ||
|
|
||
| // Make sure that _components[1] is either of token0 or token1 | ||
| if (!(_components[1] == token0 || _components[1] == token1) ) { | ||
| return false; | ||
| } | ||
|
|
||
| // Make sure the pool address follows IERC20 interface | ||
| try IArrakisVaultV1(_pool).totalSupply() returns (uint256) { | ||
| } catch { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Sorts the amount in order of tokens stored in Arrakis/UniswapV3 Pool | ||
| * | ||
| * @param _token0 Address of token0 | ||
| * @param _token1 Address of token1 | ||
| * @param _amount0 Amount of token0 | ||
| * @param _amount1 Amount of token1 | ||
| */ | ||
| function _getOrderedAmount(address _token0, address _token1, uint256 _amount0, uint256 _amount1) private pure returns(uint256, uint256) { | ||
| return _token0 < _token1 ? (_amount0, _amount1) : (_amount1, _amount0); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong date and Copy Right holder. (Should be 2023 IndexCoop no ? )