Skip to content
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

Curve and Convex adaptors #74

Open
wants to merge 53 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
e7869b2
Convex adaptor initialization
Oct 29, 2022
8a66a48
rename test faucet
Oct 29, 2022
f60172b
Curve3PoolAdaptor
Oct 29, 2022
a282be7
change location of curve 3 pool adaptor
Oct 29, 2022
f362087
add mock tests for curve pool
Oct 29, 2022
2f024d7
new methods and test start
Oct 29, 2022
a9cddbd
Curve3PoolAdaptor balanceOf
federava Oct 29, 2022
9b713cb
Merge branch 'main' of https://github.com/cookiesanddudes/cellar-cont…
federava Oct 29, 2022
e6b4ece
add interface, fix balanceOf()
Oct 29, 2022
3ce7ac8
fix interface
Oct 29, 2022
90a6479
updates on the adaptor
Oct 29, 2022
b006317
fix balance of usdc
Oct 29, 2022
87c91f0
simplify test, allow deposits
Oct 29, 2022
938a96a
failed test
Oct 29, 2022
6805c83
remove 42
Oct 29, 2022
ecc8a76
fix interfaces and test
Oct 29, 2022
e023138
fix: ICurvePool
federava Oct 29, 2022
a9c663e
added assertEq
Oct 29, 2022
c81d814
Merge branch 'main' of github-dude:cookiesanddudes/cellar-contracts
Oct 29, 2022
778b848
added test condition
Oct 29, 2022
e12c57f
remove position in one coin
Oct 29, 2022
c8cefcf
disallow deposits and withdrawals()
Oct 29, 2022
359b187
add close and take
Oct 29, 2022
0f203be
test: withdrawableFrom
federava Oct 29, 2022
375dd3e
add position and take position functions
Oct 29, 2022
f54534b
Merge branch 'main' of github-dude:cookiesanddudes/cellar-contracts
Oct 29, 2022
09228bc
comment unimplemented function
Oct 29, 2022
a97ef86
remove infinite for loop
Oct 29, 2022
7bec56b
test adding and taking from curve
Oct 29, 2022
36e1329
convex setup
Oct 29, 2022
b482e38
update tests
Oct 29, 2022
d9b8c82
Convex strat
Oct 29, 2022
5473a8e
complete IBooster
federava Oct 30, 2022
42304b3
withdrawableFrom
federava Oct 30, 2022
c527277
convex adaptor
Oct 30, 2022
7c1b8e5
NatSpec Curve3PoolAdaptor
federava Oct 30, 2022
c168644
Merge branch 'main' of https://github.com/cookiesanddudes/cellar-cont…
federava Oct 30, 2022
d8af9b7
test: revert Curve3Pool
federava Oct 30, 2022
920f4c8
convex test start
Oct 30, 2022
8989751
Merge branch 'main' of github-dude:cookiesanddudes/cellar-contracts
Oct 30, 2022
cf2788b
add convex test... working
Oct 30, 2022
bba8b2b
add open and close test
Oct 30, 2022
2a655ac
convex tests
Oct 30, 2022
ae3d366
rewards!
Oct 30, 2022
d15ed49
fix comment
Oct 30, 2022
e12ad24
adaptors
Oct 30, 2022
76f4ec4
linting
Oct 30, 2022
f492862
comments
Oct 30, 2022
af3079b
feature: Curve2Pool
federava Oct 30, 2022
ae78a81
Merge branch 'main' of https://github.com/cookiesanddudes/cellar-cont…
federava Oct 30, 2022
ebeb533
add authors
Oct 30, 2022
d5991bc
remove unused variables
Oct 30, 2022
f4d4ff4
final linting
Oct 30, 2022
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
19 changes: 19 additions & 0 deletions src/interfaces/external/IBooster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

// Convex IBooster interface
interface IBooster {
function owner() external view returns(address);
function poolLength() external view returns (uint256);
function poolInfo(uint256 _pid) external view returns(address, address, address, address, address, bool);

function deposit(uint256 _pid, uint256 _amount, bool _stake) external returns(bool);
function depositAll(uint256 _pid, bool _stake) external returns(bool);
function withdraw(uint256 _pid, uint256 _amount) external returns(bool);
function withdrawTo(uint256 _pid, uint256 _amount, address _to) external returns(bool);
function withdrawAll(uint256 _pid) external returns(bool);
function claimRewards(uint256 _pid, address _gauge) external returns(bool);
function vote(uint256 _voteId, address _votingAddress, bool _support) external returns(bool);
function voteGaugeWeight(address[] calldata _gauge, uint256[] calldata _weight ) external returns(bool);
function setVoteDelegate(address _voteDelegate) external;
}
16 changes: 16 additions & 0 deletions src/interfaces/external/ICurve2Pool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

// ICurvePool interface
interface ICurvePool {
function coins(uint256) external view returns (address);
function balances(uint256) external view returns (uint256);
function get_virtual_price() external view returns (uint256);
function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256);

function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amount, address _receiver) external returns (uint256);
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external;
function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external;
function remove_liquidity_imbalance(uint256[2] calldata amounts, uint256 max_burn_amount) external;
function remove_liquidity_one_coin(uint256 _burn_amount, int128 i, uint256 _min_received, address _receiver) external returns (uint256);
}
16 changes: 16 additions & 0 deletions src/interfaces/external/ICurvePool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

// ICurvePool interface
interface ICurvePool {
function coins(uint256) external view returns (address);
function balances(uint256) external view returns (uint256);
function get_virtual_price() external view returns (uint256);
function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256);

function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external;
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external;
function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external;
function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external;
function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external;
}
15 changes: 15 additions & 0 deletions src/interfaces/external/IRewardPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';

// Convex IRewardPool interface
interface IRewardPool is IERC20 {
function getReward() external returns(bool);
function getReward(address _account, bool _claimExtras) external returns(bool);
function withdrawAllAndUnwrap(bool claim) external;
function withdraw(uint256 amount, bool claim) external;
function withdrawAndUnwrap(uint256 amount, bool claim) external;
function stake(uint256 _amount) external;
function rewardPerToken() external view returns (uint256);
}
216 changes: 216 additions & 0 deletions src/modules/adaptors/Convex/ConvexAdaptor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;

import { BaseAdaptor, ERC20, SafeERC20, Cellar, PriceRouter, Registry, Math } from "src/modules/adaptors/BaseAdaptor.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IBooster } from "src/interfaces/external/IBooster.sol";
import { IRewardPool } from "src/interfaces/external/IRewardPool.sol";
import { ICurvePool } from "src/interfaces/external/ICurvePool.sol";

/**
* @title Convex Adaptor
* @notice Allows Cellars to interact with Convex Positions.
* @author cookiesanddudes, federava
*/
contract ConvexAdaptor is BaseAdaptor {
using SafeERC20 for ERC20;
using Math for uint256;
using SafeCast for uint256;
using Address for address;

//==================== Adaptor Data Specification ====================
// adaptorData = abi.encode(uint256 pid, ERC20 lpToken, ICurvePool pool)
// Where:
// - pid is the pool id of the convex pool
// - lpToken is the lp token concerned by the pool
// - ICurvePool is the curve pool where the lp token was minted
//====================================================================

//============================================ Global Functions ===========================================
/**
* @dev Identifier unique to this adaptor for a shared registry.
* Normally the identifier would just be the address of this contract, but this
* Identifier is needed during Cellar Delegate Call Operations, so getting the address
* of the adaptor is more difficult.
*/
function identifier() public pure override returns (bytes32) {
return keccak256(abi.encode("Convex Adaptor V 0.0"));
}

/**
* @notice The Booster contract on Ethereum Mainnet where all deposits happen in Convex
*/
function booster() internal pure returns (IBooster) {
return IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31);
}

//============================================ Implement Base Functions ===========================================

/**
* @notice User deposits are NOT allowed into this position.
*/
function deposit(
uint256,
bytes memory,
bytes memory
) public pure override {
revert BaseAdaptor__UserDepositsNotAllowed();
}

/**
* @notice User withdraws are NOT allowed from this position.
*/
function withdraw(
uint256,
address,
bytes memory,
bytes memory
) public pure override {
revert BaseAdaptor__UserWithdrawsNotAllowed();
}

/**
* @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom.
*/
function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) {
return 0;
}

/**
* @notice Calculates this positions LP tokens underlying worth in terms of `token0`.
* @dev Takes into account Cellar LP balance and also staked LP balance
* @dev The unit is the token0 of the curve pool where the LP was minted. See `assetOf()`
*/
function balanceOf(bytes memory adaptorData) public view override returns (uint256) {
(uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool));

// get reward pool where the LP are staked
(, , , address rewardPool, , ) = (booster()).poolInfo(pid);
uint256 stakedLpBalance = IRewardPool(rewardPool).balanceOf(msg.sender);

// get amount of LP owned
uint256 lpBalance = lpToken.balanceOf(msg.sender);

// calculate lp owned value
uint256 lpValue;
if (lpBalance != 0) {
lpValue = pool.calc_withdraw_one_coin(lpBalance, 0);
}

// calculate stakedLp Value
if (stakedLpBalance == 0) return lpValue;
uint256 stakedValue = pool.calc_withdraw_one_coin(stakedLpBalance, 0);

return stakedValue + lpValue;
}

/**
* @notice Returns `coins(0)`
*/
function assetOf(bytes memory adaptorData) public view override returns (ERC20) {
(, , ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool));
return ERC20(pool.coins(0));
}

//============================================ Strategist Functions ===========================================

/**
@notice Attempted to deposit into convex but failed
*/
error ConvexAdaptor_DepositFailed();

/**
* @notice Allows strategist to open a Convex position.
* @param pid convex pool id
* @param amount of LP to stake
* @param lpToken the corresponding LP token
*/
function openPosition(
uint256 pid,
uint256 amount,
ERC20 lpToken
) public {
_addToPosition(pid, amount, lpToken);
}

/**
* @notice Allows strategist to add liquidity to a Convex position.
* @param pid convex pool id
* @param amount of LP to stake
* @param lpToken the corresponding LP token
*/
function addToPosition(
uint256 pid,
uint256 amount,
ERC20 lpToken
) public {
_addToPosition(pid, amount, lpToken);
}

function _addToPosition(
uint256 pid,
uint256 amount,
ERC20 lpToken
) internal {
lpToken.safeApprove(address(booster()), amount);

// always assume we are staking
if (!(booster()).deposit(pid, amount, true)) {
revert ConvexAdaptor_DepositFailed();
}
}

/**
* @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`,
* but they need to use `closePosition`.
*/
error ConvexAdaptor__CallClosePosition();

/**
* @notice Allows strategist to remove liquidity from a position
* @param pid convex pool id
* @param amount of LP to stake
* @param claim true if rewards should be claimed when withdrawing
*/
function takeFromPosition(
uint256 pid,
uint256 amount,
bool claim
) public {
(, , , address rewardPool, , ) = (booster()).poolInfo(pid);

if (IRewardPool(rewardPool).balanceOf(msg.sender) == amount) revert ConvexAdaptor__CallClosePosition();

IRewardPool(rewardPool).withdrawAndUnwrap(amount, claim);
}

/**
* @notice Allows strategist to close a position
* @param pid convex pool id
* @param claim true if rewards should be claimed when withdrawing
*/
function closePosition(uint256 pid, bool claim) public {
(, , , address rewardPool, , ) = (booster()).poolInfo(pid);

IRewardPool(rewardPool).withdrawAllAndUnwrap(claim);
}

/**
* @notice Attempted to take from convex position but failed
*/
error ConvexAdaptor_CouldNotClaimRewards();

/**
* @notice Allows strategist to claim rewards and extras from convex
* @param pid convex pool id
* TODO: distribute these rewards to timelockERC20 adaptor in feat/timelockERC20 branch (out of scope for the hackathon)
*/
function claimRewards(uint256 pid) public {
(, , , address rewardPool, , ) = (booster()).poolInfo(pid);

if (!IRewardPool(rewardPool).getReward()) {
revert ConvexAdaptor_CouldNotClaimRewards();
}
}
}
Loading