-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
connectors: implement curve 2crv liquidity connector
- Loading branch information
1 parent
75ca506
commit f7f5f5d
Showing
3 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
104 changes: 104 additions & 0 deletions
104
packages/connectors/contracts/liquidity/curve/Curve2CrvConnector.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||
import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol'; | ||
|
||
import '@mimic-fi/v3-helpers/contracts/math/FixedPoint.sol'; | ||
|
||
import './I2CrvPool.sol'; | ||
|
||
/** | ||
* @title Curve2CrvConnector | ||
*/ | ||
contract Curve2CrvConnector { | ||
using FixedPoint for uint256; | ||
|
||
// 2CRV pool address | ||
I2CrvPool public immutable pool; | ||
|
||
/** | ||
* @dev Creates a new Curve 2CRV connector | ||
*/ | ||
constructor(I2CrvPool _pool) { | ||
pool = _pool; | ||
} | ||
|
||
/** | ||
* @dev Adds liquidity to the 2CRV pool | ||
* @param tokenIn Address of the token to join the 2CRV pool | ||
* @param amountIn Amount of tokens to join the 2CRV pool | ||
* @param slippage Slippage value to be used to compute the desired min amount out of pool tokens | ||
*/ | ||
function join(address tokenIn, uint256 amountIn, uint256 slippage) external returns (uint256) { | ||
if (amountIn == 0) return 0; | ||
require(slippage <= FixedPoint.ONE, '2CRV_SLIPPAGE_ABOVE_ONE'); | ||
(uint256 tokenIndex, uint256 tokenScale) = _findTokenInfo(tokenIn); | ||
|
||
// Compute min amount out | ||
uint256 expectedAmountOut = (amountIn * tokenScale).divUp(pool.get_virtual_price()); | ||
uint256 minAmountOut = expectedAmountOut.mulUp(FixedPoint.ONE - slippage); | ||
|
||
// Join pool | ||
uint256 initialPoolTokenBalance = pool.balanceOf(address(this)); | ||
IERC20(tokenIn).approve(address(pool), amountIn); | ||
uint256[2] memory amounts; | ||
amounts[tokenIndex] = amountIn; | ||
pool.add_liquidity(amounts, minAmountOut); | ||
uint256 finalPoolTokenBalance = pool.balanceOf(address(this)); | ||
return finalPoolTokenBalance - initialPoolTokenBalance; | ||
} | ||
|
||
/** | ||
* @dev Removes liquidity from 2CRV pool | ||
* @param amountIn Amount of pool tokens to exit from the 2CRV pool | ||
* @param tokenOut Address of the token to exit the pool | ||
* @param slippage Slippage value to be used to compute the desired min amount out of tokens | ||
*/ | ||
function exit(uint256 amountIn, address tokenOut, uint256 slippage) external returns (uint256 amountOut) { | ||
if (amountIn == 0) return 0; | ||
require(slippage <= FixedPoint.ONE, '2CRV_INVALID_SLIPPAGE'); | ||
(uint256 tokenIndex, uint256 tokenScale) = _findTokenInfo(tokenOut); | ||
|
||
// Compute min amount out | ||
uint256 expectedAmountOut = amountIn.mulUp(pool.get_virtual_price()) / tokenScale; | ||
uint256 minAmountOut = expectedAmountOut.mulUp(FixedPoint.ONE - slippage); | ||
|
||
// Exit pool | ||
uint256 initialTokenOutBalance = IERC20(tokenOut).balanceOf(address(this)); | ||
pool.remove_liquidity_one_coin(amountIn, int128(int256(tokenIndex)), minAmountOut); | ||
uint256 finalTokenOutBalance = IERC20(tokenOut).balanceOf(address(this)); | ||
return finalTokenOutBalance - initialTokenOutBalance; | ||
} | ||
|
||
/** | ||
* @dev Finds the index and scale factor of the entry token in the 2CRV pool | ||
*/ | ||
function _findTokenInfo(address token) internal view returns (uint256 index, uint256 scale) { | ||
for (uint256 i = 0; true; i++) { | ||
try pool.coins(i) returns (address coin) { | ||
if (token == coin) { | ||
uint256 decimals = IERC20Metadata(token).decimals(); | ||
require(decimals <= 18, '2CRV_TOKEN_ABOVE_18_DECIMALS'); | ||
return (i, 10**(18 - decimals)); | ||
} | ||
} catch { | ||
revert('2CRV_TOKEN_NOT_FOUND'); | ||
} | ||
} | ||
revert('2CRV_TOKEN_NOT_FOUND'); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/connectors/contracts/liquidity/curve/I2CrvPool.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||
|
||
// solhint-disable func-name-mixedcase | ||
|
||
interface I2CrvPool is IERC20 { | ||
function get_virtual_price() external view returns (uint256); | ||
|
||
function coins(uint256 index) external view returns (address); | ||
|
||
function exchange(int128 i, int128 j, uint256 dx, uint256 minDy) external; | ||
|
||
function add_liquidity(uint256[2] memory amountsIn, uint256 minAmountOut) external returns (uint256); | ||
|
||
function remove_liquidity_one_coin(uint256 amountIn, int128 index, uint256 minAmountOut) external returns (uint256); | ||
} |
66 changes: 66 additions & 0 deletions
66
packages/connectors/test/liquidity/curve/Curve2CrvConnector.arbitrum.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { assertAlmostEqual, deploy, fp, impersonate, instanceAt, toUSDC } from '@mimic-fi/v3-helpers' | ||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address' | ||
import { expect } from 'chai' | ||
import { Contract } from 'ethers' | ||
|
||
/* eslint-disable no-secrets/no-secrets */ | ||
|
||
const USDC = '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8' | ||
const POOL = '0x7f90122BF0700F9E7e1F688fe926940E8839F353' | ||
|
||
const WHALE = '0x62383739d68dd0f844103db8dfb05a7eded5bbe6' | ||
|
||
describe('Curve2CrvConnector - USDC', function () { | ||
let whale: SignerWithAddress | ||
let connector: Contract, pool: Contract, usdc: Contract | ||
|
||
const SLIPPAGE = fp(0.001) | ||
const JOIN_AMOUNT = toUSDC(100) | ||
|
||
before('impersonate whale', async () => { | ||
whale = await impersonate(WHALE, fp(10)) | ||
}) | ||
|
||
before('deploy connector', async () => { | ||
connector = await deploy('Curve2CrvConnector', [POOL]) | ||
usdc = await instanceAt('IERC20', USDC) | ||
pool = await instanceAt('I2CrvPool', POOL) | ||
}) | ||
|
||
it('deploys the connector correctly', async () => { | ||
expect(await connector.pool()).to.be.equal(POOL) | ||
}) | ||
|
||
it('joins curve', async () => { | ||
await usdc.connect(whale).transfer(connector.address, JOIN_AMOUNT) | ||
|
||
const previousUsdcBalance = await usdc.balanceOf(connector.address) | ||
const previousPoolBalance = await pool.balanceOf(connector.address) | ||
|
||
await connector.join(USDC, JOIN_AMOUNT, SLIPPAGE) | ||
|
||
const currentUsdcBalance = await usdc.balanceOf(connector.address) | ||
expect(currentUsdcBalance).to.be.equal(previousUsdcBalance.sub(JOIN_AMOUNT)) | ||
|
||
const poolTokenPrice = await pool.get_virtual_price() | ||
const currentPoolBalance = await pool.balanceOf(connector.address) | ||
const expectedPoolAmount = JOIN_AMOUNT.mul(1e12).mul(fp(1)).div(poolTokenPrice) | ||
assertAlmostEqual(expectedPoolAmount, currentPoolBalance.sub(previousPoolBalance), 0.0005) | ||
}) | ||
|
||
it('exits with a 50%', async () => { | ||
const previousUsdcBalance = await usdc.balanceOf(connector.address) | ||
const previousPoolBalance = await pool.balanceOf(connector.address) | ||
|
||
const amountIn = previousPoolBalance.div(2) | ||
await connector.exit(amountIn, USDC, SLIPPAGE) | ||
|
||
const currentPoolBalance = await pool.balanceOf(connector.address) | ||
expect(currentPoolBalance).to.be.equal(previousPoolBalance.sub(amountIn)) | ||
|
||
const poolTokenPrice = await pool.get_virtual_price() | ||
const currentUsdcBalance = await usdc.balanceOf(connector.address) | ||
const expectedUsdcBalance = amountIn.mul(poolTokenPrice).div(fp(1)).div(1e12) | ||
assertAlmostEqual(expectedUsdcBalance, currentUsdcBalance.sub(previousUsdcBalance), 0.0005) | ||
}) | ||
}) |