-
Notifications
You must be signed in to change notification settings - Fork 95
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
ArrakisUniswapV3AmmAdapter #260
base: master
Are you sure you want to change the base?
Changes from 12 commits
ce0add3
10cde65
b02754f
3aa13c7
6f096a1
831a0b1
4193b04
0b475d5
c88ecd5
44a940a
755137f
375cd81
206a476
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,86 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
/* | ||
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 License, Version 2.0 | ||
*/ | ||
|
||
pragma solidity 0.6.10; | ||
|
||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; | ||
|
||
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 (IERC20); | ||
|
||
function token1() external view returns (IERC20); | ||
|
||
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,281 @@ | ||
/* | ||
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 License, Version 2.0 | ||
*/ | ||
|
||
pragma solidity 0.6.10; | ||
|
||
import "@openzeppelin/contracts/math/SafeMath.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
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 | ||
* @author Zishan Sami | ||
* | ||
* Adapter for Arrakis Vault representing Uniswap V3 liquidity position that encodes adding and removing liquidty | ||
*/ | ||
contract ArrakisUniswapV3AmmAdapter is IAmmAdapter { | ||
using SafeMath for uint256; | ||
|
||
/* ============ 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)"; | ||
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. Just checking, we don't include |
||
// 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) public { | ||
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; | ||
|
||
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; | ||
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 using (i.e. encoding for) the |
||
value = 0; | ||
data = abi.encodeWithSignature( | ||
ADD_LIQUIDITY, | ||
arrakisVaultPool, | ||
maxTokensInA, | ||
maxTokensInB, | ||
amountAMin, | ||
amountBMin, | ||
setToken | ||
); | ||
} | ||
|
||
/** | ||
* Return calldata for the add liquidity call for a single asset | ||
*/ | ||
function getProvideLiquiditySingleAssetCalldata( | ||
address /*_setToken*/, | ||
address /*_pool*/, | ||
address /*_component*/, | ||
uint256 /*_maxTokenIn*/, | ||
uint256 /*_minLiquidity*/ | ||
) | ||
external | ||
view | ||
override | ||
returns (address /*target*/, uint256 /*value*/, bytes memory /*data*/) | ||
{ | ||
revert("Arrakis single asset addition is not supported"); | ||
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. Couldn't this be implemented using the |
||
} | ||
|
||
/** | ||
* 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); | ||
|
||
// 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( | ||
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 | ||
view | ||
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 (IERC20 _token0) { | ||
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. Try catch statements like these don't catch the case when See this so question that I posted regarding this topic: |
||
token0 = address(_token0); | ||
} catch { | ||
return false; | ||
} | ||
try IArrakisVaultV1(_pool).token1() returns (IERC20 _token1) { | ||
token1 = address(_token1); | ||
} catch { | ||
return false; | ||
} | ||
|
||
// 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 _token0 Address of token1 | ||
* @param _amount0 Amount of token0 | ||
* @param _token1 Amount of token1 | ||
zishansami102 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
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.
To remove unused variable