Skip to content
Closed
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ce0add3
init files
zishansami102 Jul 12, 2022
10cde65
arrakis uniV3 adapter contract draft-1
zishansami102 Jul 15, 2022
b02754f
contract typos fix
zishansami102 Jul 17, 2022
3aa13c7
add arrakis abis, fixtures, and constructor tests
zishansami102 Jul 17, 2022
6f096a1
add test for getSpenderAddress
zishansami102 Jul 17, 2022
831a0b1
typo fix
zishansami102 Jul 18, 2022
4193b04
GUniPool to ArrakisVaultV1
zishansami102 Jul 18, 2022
0b475d5
add isValidPool tests
zishansami102 Jul 19, 2022
c88ecd5
add tests for singleAssetCalldata functions
zishansami102 Jul 19, 2022
44a940a
add more unit tests
zishansami102 Jul 19, 2022
755137f
add integration tests
zishansami102 Jul 21, 2022
375cd81
review fixes
zishansami102 Jul 21, 2022
206a476
review fixes
zishansami102 Jul 27, 2022
36f9257
Merge remote-tracking branch 'edward_set2/zishan/uniswapv3-amm-adapte…
snake-poison Feb 6, 2023
3e4cfe1
chore: enabled 0.8.17 solidity compiler
snake-poison Feb 6, 2023
be7d93e
refactor: widens type of IERC20 to address
snake-poison Feb 6, 2023
7c55055
ref: removes unused safemath library.
snake-poison Feb 6, 2023
7d44cf2
style: update feature spdx identifiers.
snake-poison Feb 6, 2023
a016dbb
ref: update feature solidity pragma to 0.8.17
snake-poison Feb 6, 2023
5e51ada
ref: fix warnings for visibility state mutability.
snake-poison Feb 6, 2023
6a7bb40
style: formatted spec file with prettier eslint
snake-poison Feb 8, 2023
18de1cf
feat(test): invalid constructor args.
snake-poison Feb 8, 2023
3a0acb8
test: adds missing branch in isvalid
snake-poison Feb 8, 2023
2b65b92
chore: adds hardhat-network-helpers as dev dep.
snake-poison Feb 9, 2023
ba5e4ce
test: reset fork to blocknumber in config
snake-poison Feb 9, 2023
aa6728f
chore: upgraded some devdeps
snake-poison Feb 11, 2023
5f789f3
test: more strict revert strings
snake-poison Feb 11, 2023
6aadea0
chore: setup smock dep dev.
snake-poison Feb 13, 2023
f2a1145
test: added case for an invalid pool
snake-poison Feb 13, 2023
570b65f
style: doc string edits.
snake-poison Feb 13, 2023
6982692
chore: adds ext interface for arrakis v1 router.
snake-poison Feb 13, 2023
4663019
refactor: no copy to memory and encodewithcall
snake-poison Feb 13, 2023
1df82d0
chore: compiling viaIR for 0.8 contracts.
snake-poison Feb 13, 2023
de8d270
test: sets expected calldata to match v1 router.
snake-poison Mar 13, 2023
bfb9d68
fix: fix import line to grab all exports.
snake-poison Mar 13, 2023
eec32a1
chore: setup hardhat to emi yul properly
snake-poison Mar 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contracts/interfaces/IAmmAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache License, Version 2.0
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.10;
pragma solidity >=0.6.10 < 0.9;


/**
Expand Down
83 changes: 83 additions & 0 deletions contracts/interfaces/external/IArrakisVaultV1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright 2022 Set Labs Inc.

Copy link
Copy Markdown
Collaborator

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 ? )

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;
}
277 changes: 277 additions & 0 deletions contracts/protocol/integration/amm/ArrakisUniswapV3AmmAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/*
Copyright 2022 Set Labs Inc.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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

@ckoopmann ckoopmann Feb 9, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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(

@ckoopmann ckoopmann Feb 9, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could do something like:
abi.encodeWithSelector(IArrakis.removeLiquidity.selector ... here no ?

Advantages:

  1. No need of signature string constant
  2. Less error prone
  3. Maybe more efficient if the selector is precomputed at compile time

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;

@ckoopmann ckoopmann Feb 9, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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 ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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.
I still think adding this little mock contract might be worth it if it helps us to just keep it at a nice 100% coverage.
Also might be worth looking into other mock options such as smock ( or whatever the cool kids are using nowadays)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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 smock, I'll just do a small mock contract to get this to 100% which I agree is nice and why I added a constructor test :)

}

// 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);
}
}
Loading