-
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 convex liquidity connector
- Loading branch information
1 parent
6e442b8
commit 07a7c9a
Showing
4 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
104 changes: 104 additions & 0 deletions
104
packages/connectors/contracts/liquidity/convex/ConvexConnector.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 './ICvxPool.sol'; | ||
import './ICvxBooster.sol'; | ||
|
||
/** | ||
* @title ConvexConnector | ||
*/ | ||
contract ConvexConnector { | ||
using FixedPoint for uint256; | ||
|
||
// Convex booster | ||
ICvxBooster public immutable booster; | ||
|
||
/** | ||
* @dev Creates a new Convex connector | ||
*/ | ||
constructor(ICvxBooster _booster) { | ||
booster = _booster; | ||
} | ||
|
||
/** | ||
* @dev Claims Convex pool rewards for a Curve pool | ||
*/ | ||
function claim(address pool) external returns (address[] memory tokens, uint256[] memory amounts) { | ||
(, ICvxPool cvxPool) = _findCvxPoolInfo(pool); | ||
IERC20 crv = IERC20(cvxPool.crv()); | ||
|
||
uint256 initialCrvBalance = crv.balanceOf(address(this)); | ||
cvxPool.getReward(address(this)); | ||
uint256 finalCrvBalance = crv.balanceOf(address(this)); | ||
|
||
amounts = new uint256[](1); | ||
amounts[0] = finalCrvBalance - initialCrvBalance; | ||
|
||
tokens = new address[](1); | ||
tokens[0] = address(crv); | ||
} | ||
|
||
/** | ||
* @dev Deposits Curve pool tokens into Convex | ||
* @param pool Address of the Curve pool to join Convex | ||
* @param amount Amount of Curve pool tokens to be deposited into Convex | ||
*/ | ||
function join(address pool, uint256 amount) external returns (uint256) { | ||
if (amount == 0) return 0; | ||
(uint256 poolId, ICvxPool cvxPool) = _findCvxPoolInfo(pool); | ||
|
||
// Stake in Convex | ||
uint256 initialCvxPoolTokenBalance = cvxPool.balanceOf(address(this)); | ||
IERC20(pool).approve(address(booster), amount); | ||
require(booster.deposit(poolId, amount), 'CONVEX_BOOSTER_DEPOSIT_FAILED'); | ||
uint256 finalCvxPoolTokenBalance = cvxPool.balanceOf(address(this)); | ||
return finalCvxPoolTokenBalance - initialCvxPoolTokenBalance; | ||
} | ||
|
||
/** | ||
* @dev Withdraws Curve pool tokens from Convex | ||
* @param pool Address of the Curve pool to exit from Convex | ||
* @param amount Amount of Convex tokens to be withdrawn | ||
*/ | ||
function exit(address pool, uint256 amount) external returns (uint256) { | ||
if (amount == 0) return 0; | ||
(, ICvxPool cvxPool) = _findCvxPoolInfo(pool); | ||
|
||
// Unstake from Convex | ||
uint256 initialPoolTokenBalance = IERC20(pool).balanceOf(address(this)); | ||
require(cvxPool.withdraw(amount, true), 'CONVEX_CVX_POOL_WITHDRAW_FAILED'); | ||
uint256 finalPoolTokenBalance = IERC20(pool).balanceOf(address(this)); | ||
return finalPoolTokenBalance - initialPoolTokenBalance; | ||
} | ||
|
||
/** | ||
* @dev Finds the Convex pool information associated to the given Curve pool | ||
*/ | ||
function _findCvxPoolInfo(address pool) internal view returns (uint256 poolId, ICvxPool cvxPool) { | ||
for (uint256 i = 0; i < booster.poolLength(); i++) { | ||
(address lp, , address rewards, bool shutdown, ) = booster.poolInfo(i); | ||
if (lp == pool && !shutdown) { | ||
return (i, ICvxPool(rewards)); | ||
} | ||
} | ||
revert('CONVEX_CVX_POOL_NOT_FOUND'); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
packages/connectors/contracts/liquidity/convex/ICvxBooster.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,26 @@ | ||
// 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; | ||
|
||
interface ICvxBooster { | ||
function poolLength() external view returns (uint256); | ||
|
||
function poolInfo(uint256 i) | ||
external | ||
view | ||
returns (address lpToken, address gauge, address rewards, bool shutdown, address factory); | ||
|
||
function deposit(uint256 pid, uint256 amount) external returns (bool); | ||
} |
25 changes: 25 additions & 0 deletions
25
packages/connectors/contracts/liquidity/convex/ICvxPool.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,25 @@ | ||
// 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'; | ||
|
||
interface ICvxPool is IERC20 { | ||
function crv() external view returns (address); | ||
|
||
function getReward(address account) external; | ||
|
||
function withdraw(uint256 amount, bool claim) external returns (bool); | ||
} |
74 changes: 74 additions & 0 deletions
74
packages/connectors/test/liquidity/convex/ConvexConnector.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,74 @@ | ||
import { advanceTime, deploy, fp, impersonate, instanceAt, MONTH, 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 CRV = '0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978' | ||
const POOL = '0x7f90122BF0700F9E7e1F688fe926940E8839F353' | ||
const CVX_POOL = '0x971E732B5c91A59AEa8aa5B0c763E6d648362CF8' | ||
const BOOSTER = '0xF403C135812408BFbE8713b5A23a04b3D48AAE31' | ||
|
||
const WHALE = '0xf403c135812408bfbe8713b5a23a04b3d48aae31' | ||
|
||
describe('ConvexConnector - 2CRV', function () { | ||
let whale: SignerWithAddress | ||
let connector: Contract, pool: Contract, cvxPool: Contract, crv: Contract | ||
|
||
const JOIN_AMOUNT = toUSDC(100) | ||
|
||
before('impersonate whale', async () => { | ||
whale = await impersonate(WHALE, fp(10)) | ||
}) | ||
|
||
before('deploy connector', async () => { | ||
connector = await deploy('ConvexConnector', [BOOSTER]) | ||
crv = await instanceAt('IERC20', CRV) | ||
pool = await instanceAt('I2CrvPool', POOL) | ||
cvxPool = await instanceAt('ICvxPool', CVX_POOL) | ||
}) | ||
|
||
it('deploys the connector correctly', async () => { | ||
expect(await connector.booster()).to.be.equal(BOOSTER) | ||
}) | ||
|
||
it('joins the connector', async () => { | ||
await pool.connect(whale).transfer(connector.address, JOIN_AMOUNT) | ||
|
||
const previousPoolBalance = await pool.balanceOf(connector.address) | ||
const previousCvxPoolBalance = await cvxPool.balanceOf(connector.address) | ||
|
||
await connector.join(POOL, JOIN_AMOUNT) | ||
|
||
const currentPoolBalance = await pool.balanceOf(connector.address) | ||
expect(currentPoolBalance).to.be.equal(previousPoolBalance.sub(JOIN_AMOUNT)) | ||
|
||
const currentCvxPoolBalance = await cvxPool.balanceOf(connector.address) | ||
expect(currentCvxPoolBalance).to.be.equal(previousCvxPoolBalance.add(JOIN_AMOUNT)) | ||
}) | ||
|
||
it('accrues rewards over time', async () => { | ||
const previousCrvBalance = await crv.balanceOf(connector.address) | ||
|
||
await advanceTime(MONTH) | ||
await connector.claim(POOL) | ||
|
||
const currentCrvBalance = await crv.balanceOf(connector.address) | ||
expect(currentCrvBalance).to.be.gt(previousCrvBalance) | ||
}) | ||
|
||
it('exits with a 50%', async () => { | ||
const previousPoolBalance = await pool.balanceOf(connector.address) | ||
const previousCvxPoolBalance = await cvxPool.balanceOf(connector.address) | ||
|
||
const amountIn = previousCvxPoolBalance.div(2) | ||
await connector.exit(POOL, amountIn) | ||
|
||
const currentCvxPoolBalance = await cvxPool.balanceOf(connector.address) | ||
expect(currentCvxPoolBalance).to.be.equal(previousCvxPoolBalance.sub(amountIn)) | ||
|
||
const currentPoolBalance = await pool.balanceOf(connector.address) | ||
expect(currentPoolBalance).to.be.equal(previousPoolBalance.add(amountIn)) | ||
}) | ||
}) |