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

add CurveStrategy #229

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "./CurveStrategy.sol";

contract CurveDynamicAmountsStrategy is CurveStrategy {
constructor(
CurveStrategySettings memory _curveStrategySettings,
BaseStrategySettings memory _baseStrategySettings,
StrategySettings memory _strategySettings
) CurveStrategy(_curveStrategySettings, _baseStrategySettings, _strategySettings) {}

function _addLiquidity(uint256 _amountIn) internal override returns (uint256 amountOut) {
uint256[] memory amounts = new uint256[](lpTokenCount);
amounts[lpTokenInIndex] = _amountIn;

return IPool(address(depositToken)).add_liquidity(amounts, 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "./CurveStrategy.sol";

contract CurveFixedAmountsStrategy is CurveStrategy {
constructor(
CurveStrategySettings memory _curveStrategySettings,
BaseStrategySettings memory _baseStrategySettings,
StrategySettings memory _strategySettings
) CurveStrategy(_curveStrategySettings, _baseStrategySettings, _strategySettings) {
require(lpTokenCount > 1 && lpTokenCount < 5, "CurveStrategy::Unsupported LP token");
}

function _addLiquidity(uint256 _amountIn) internal override returns (uint256 amountOut) {
if (lpTokenCount == 2) {
uint256[2] memory amounts;
amounts[lpTokenInIndex] = _amountIn;

return IPool(address(depositToken)).add_liquidity(amounts, 0);
} else if (lpTokenCount == 3) {
uint256[3] memory amounts;
amounts[lpTokenInIndex] = _amountIn;

return IPool(address(depositToken)).add_liquidity(amounts, 0);
} else if (lpTokenCount == 4) {
uint256[4] memory amounts;
amounts[lpTokenInIndex] = _amountIn;

return IPool(address(depositToken)).add_liquidity(amounts, 0);
}
}
}
138 changes: 138 additions & 0 deletions contracts/strategies/crosschain/curve/CurveStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "../../BaseStrategy.sol";

import "./interfaces/IGauge.sol";
import "./interfaces/IFactory.sol";
import "./interfaces/IPool.sol";

abstract contract CurveStrategy is BaseStrategy {
using SafeERC20 for IERC20;

struct CurveStrategySettings {
address gauge;
address lpTokenIn;
uint256 lpTokenCount;
address crv;
}

IGauge public immutable stakingContract;
address public immutable lpTokenIn;
IFactory public immutable factory;

uint256 immutable lpTokenCount;
uint256 immutable lpTokenInIndex;
address immutable CRV;

constructor(
CurveStrategySettings memory _curveStrategySettings,
BaseStrategySettings memory _settings,
StrategySettings memory _strategySettings
) BaseStrategy(_settings, _strategySettings) {
stakingContract = IGauge(_curveStrategySettings.gauge);
CRV = _curveStrategySettings.crv;
factory = IFactory(stakingContract.factory());
lpTokenIn = _curveStrategySettings.lpTokenIn;
lpTokenCount = _curveStrategySettings.lpTokenCount;
uint256 tokenIndex = lpTokenCount;
for (uint256 i = 0; i < lpTokenCount; i++) {
if (lpTokenIn == IPool(address(depositToken)).coins(i)) {
tokenIndex = i;
}
}
require(tokenIndex < lpTokenCount, "CurveStrategy::Unsupported pool or lpTokenIn");
lpTokenInIndex = tokenIndex;
}

function _depositToStakingContract(uint256 _amount, uint256) internal override {
depositToken.approve(address(stakingContract), _amount);
stakingContract.deposit(_amount);
}

function _withdrawFromStakingContract(uint256 _amount) internal override returns (uint256 withdrawAmount) {
stakingContract.withdraw(_amount);
return _amount;
}

function _pendingRewards() internal view override returns (Reward[] memory) {
Reward[] memory pendingRewards = new Reward[](stakingContract.reward_count() + 1);
for (uint256 i = 0; i < pendingRewards.length - 1; i++) {
address rewardToken = stakingContract.reward_tokens(i);
uint256 amount = stakingContract.claimable_reward(address(this), rewardToken);
pendingRewards[i] = Reward({reward: rewardToken, amount: amount});
}
pendingRewards[pendingRewards.length - 1] = Reward({reward: CRV, amount: _pendingCrvRewards()});
return pendingRewards;
}

function _pendingCrvRewards() internal view returns (uint256) {
uint256 period = stakingContract.period();
uint256 periodTime = stakingContract.period_timestamp(period);
uint256 integrateInvSupply = stakingContract.integrate_inv_supply(period);

if (block.timestamp > periodTime) {
uint256 workingSupply = stakingContract.working_supply();
uint256 prevWeekTime = periodTime;
uint256 weekTime = min((periodTime + 1 weeks) / 1 weeks * 1 weeks, block.timestamp);

for (uint256 i = 0; i < type(uint256).max; i++) {
uint256 dt = weekTime - prevWeekTime;

if (workingSupply != 0) {
integrateInvSupply +=
stakingContract.inflation_rate(prevWeekTime / 1 weeks) * 10 ** 18 * dt / workingSupply;
}

if (weekTime == block.timestamp) break;

prevWeekTime = weekTime;
weekTime = min(weekTime + 1 weeks, block.timestamp);
}
}

uint256 userTotal = stakingContract.integrate_fraction(address(this))
+ (
stakingContract.working_balances(address(this))
* (integrateInvSupply - stakingContract.integrate_inv_supply_of(address(this))) / 10 ** 18
);
return userTotal - factory.minted(address(this), address(stakingContract));
}

function min(uint256 _a, uint256 _b) internal pure returns (uint256) {
return _a < _b ? _a : _b;
}

function _getRewards() internal override {
stakingContract.claim_rewards();
if (factory.is_valid_gauge(address(stakingContract))) {
factory.mint(address(stakingContract));
}
}

function totalDeposits() public view override returns (uint256) {
return stakingContract.balanceOf(address(this));
}

function _convertRewardTokenToDepositToken(uint256 _fromAmount)
internal
virtual
override
returns (uint256 toAmount)
{
if (address(rewardToken) != lpTokenIn) {
FormattedOffer memory offer = simpleRouter.query(_fromAmount, address(rewardToken), lpTokenIn);
_fromAmount = _swap(offer);
}

IERC20(lpTokenIn).approve(address(depositToken), _fromAmount);
return _addLiquidity(_fromAmount);
}

function _emergencyWithdraw() internal override {
stakingContract.withdraw(totalDeposits());
depositToken.approve(address(stakingContract), 0);
}

function _addLiquidity(uint256 _amountIn) internal virtual returns (uint256 amountOut);
}
8 changes: 8 additions & 0 deletions contracts/strategies/crosschain/curve/interfaces/IFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

interface IFactory {
function mint(address _gauge) external;
function minted(address _user, address _gauge) external view returns (uint256);
function is_valid_gauge(address _gauge) external view returns (bool);
}
22 changes: 22 additions & 0 deletions contracts/strategies/crosschain/curve/interfaces/IGauge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

interface IGauge {
function reward_tokens(uint256 _index) external view returns (address);
function claimable_reward(address _user, address _token) external view returns (uint256);
function reward_count() external view returns (uint256);
function balanceOf(address _user) external view returns (uint256);
function deposit(uint256 _amount) external;
function withdraw(uint256 _amount) external;
function factory() external view returns (address);
function claim_rewards() external;

function period() external view returns (uint256);
function period_timestamp(uint256 _period) external view returns (uint256);
function integrate_inv_supply(uint256 _period) external view returns (uint256);
function integrate_inv_supply_of(address _user) external view returns (uint256);
function working_supply() external view returns (uint256);
function working_balances(address _user) external view returns (uint256);
function inflation_rate(uint256 _period) external view returns (uint256);
function integrate_fraction(address _user) external view returns (uint256);
}
11 changes: 11 additions & 0 deletions contracts/strategies/crosschain/curve/interfaces/IPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

interface IPool {
function get_balances() external view returns (uint256[] memory);
function coins(uint256 _index) external view returns (address);
function add_liquidity(uint256[] memory _amounts, uint256 _minAmountOut) external returns (uint256);
function add_liquidity(uint256[2] memory _amounts, uint256 _minAmountOut) external returns (uint256);
function add_liquidity(uint256[3] memory _amounts, uint256 _minAmountOut) external returns (uint256);
function add_liquidity(uint256[4] memory _amounts, uint256 _minAmountOut) external returns (uint256);
}