Skip to content

Commit

Permalink
connectors: implement convex liquidity connector
Browse files Browse the repository at this point in the history
  • Loading branch information
facuspagnuolo committed Jun 26, 2023
1 parent a6136b7 commit cd58bbd
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 0 deletions.
104 changes: 104 additions & 0 deletions packages/connectors/contracts/liquidity/convex/ConvexConnector.sol
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 packages/connectors/contracts/liquidity/convex/ICvxBooster.sol
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 packages/connectors/contracts/liquidity/convex/ICvxPool.sol
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);
}
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))
})
})

0 comments on commit cd58bbd

Please sign in to comment.