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

Ribbon Finance Strategy #35

Open
wants to merge 3 commits 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
8 changes: 8 additions & 0 deletions contracts/interfaces/IWETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,12 @@ interface IWETH {
function deposit() external payable;

function withdraw(uint256 amount) external;

function approve(address spender, uint256 amount) external returns (bool);

function transfer(address recipient, uint256 amount) external returns (bool);

function totalSupply() external view returns (uint256);

function balanceOf(address account) external view returns (uint256);
}
44 changes: 44 additions & 0 deletions contracts/interfaces/ribbon/IChainlinkOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;

interface IChainlinkOracle {
function isLockingPeriodOver(address _asset, uint256 _expiryTimestamp) external view returns (bool);

function isDisputePeriodOver(address _asset, uint256 _expiryTimestamp) external view returns (bool);

function getExpiryPrice(address _asset, uint256 _expiryTimestamp) external view returns (uint256, bool);

function getDisputer() external view returns (address);

function getPricer(address _asset) external view returns (address);

function getPrice(address _asset) external view returns (uint256);

function getPricerLockingPeriod(address _pricer) external view returns (uint256);

function getPricerDisputePeriod(address _pricer) external view returns (uint256);

function getChainlinkRoundData(address _asset, uint80 _roundId) external view returns (uint256, uint256);

// Non-view function

function setAssetPricer(address _asset, address _pricer) external;

function setLockingPeriod(address _pricer, uint256 _lockingPeriod) external;

function setDisputePeriod(address _pricer, uint256 _disputePeriod) external;

function setExpiryPrice(
address _asset,
uint256 _expiryTimestamp,
uint256 _price
) external;

function disputeExpiryPrice(
address _asset,
uint256 _expiryTimestamp,
uint256 _price
) external;

function setDisputer(address _disputer) external;
}
12 changes: 12 additions & 0 deletions contracts/interfaces/ribbon/IDeltaStrikeSelection.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.10;

interface IDeltaStrikeSelection {

function getStrikePrice(uint256 expiryTimestamp, bool isPut) external view returns (uint256 newStrikePrice, uint256 newDelta);
function getStrikePriceWithVol(uint256 expiryTimestamp, bool isPut, uint256 annualizedVol) external view returns (uint256 newStrikePrice, uint256 newDelta);

function setDelta(uint256 newDelta) external;
function setStep(uint256 newStep) external;

}
16 changes: 16 additions & 0 deletions contracts/interfaces/ribbon/IOtoken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;

interface IOtoken {
function underlyingAsset() external view returns (address);

function strikeAsset() external view returns (address);

function collateralAsset() external view returns (address);

function strikePrice() external view returns (uint256);

function expiryTimestamp() external view returns (uint256);

function isPut() external view returns (bool);
}
72 changes: 72 additions & 0 deletions contracts/interfaces/ribbon/IRibbonVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;

library Vault {
struct Withdrawal {
// Maximum of 65535 rounds. Assuming 1 round is 7 days, maximum is 1256 years.
uint16 round;
// Number of shares withdrawn
uint128 shares;
}
struct DepositReceipt {
// Maximum of 65535 rounds. Assuming 1 round is 7 days, maximum is 1256 years.
uint16 round;
// Deposit amount, max 20,282,409,603,651 or 20 trillion ETH deposit
uint104 amount;
// Unredeemed shares balance
uint128 unredeemedShares;
}
struct VaultState {
// 32 byte slot 1
// Current round number. `round` represents the number of `period`s elapsed.
uint16 round;
// Amount that is currently locked for selling options
uint104 lockedAmount;
// Amount that was locked for selling options in the previous round
// used for calculating performance fee deduction
uint104 lastLockedAmount;
// 32 byte slot 2
// Stores the total tally of how much of `asset` there is
// to be used to mint rTHETA tokens
uint128 totalPending;
// Total amount of queued withdrawal shares from previous rounds (doesn't include the current round)
uint128 queuedWithdrawShares;
}
}

interface IRibbonVault {
function WETH() external view returns (address);
function STETH() external view returns (address);
function keeper() external view returns (address);
function strikeSelection() external view returns (address);
function currentOption() external view returns (address);
function balanceOf(address account) external view returns (uint256);
function depositETH() external payable;
function closeRound() external;
function commitAndClose() external;
function commitNextOption() external;
function rollToNextOption() external;
function transfer(address receiptient, uint256 amount) external;
function initiateWithdraw(uint256 amount) external;
function completeWithdraw() external;
function deposit(uint256 amount) external;
function depositYieldToken(uint256 amount) external;
function withdrawInstantly(uint256 amount, uint256) external;
function withdrawInstantly(uint256 amount) external;

function pricePerShare() external view returns (uint256);
function accountVaultBalance(address account) external view returns (uint256);
function shareBalances(address account) external view returns (uint256);
function withdrawals(address account) external view returns (Vault.Withdrawal memory);

function depositReceipts(address acount) external view returns(Vault.DepositReceipt memory);
function shares(address acount) external view returns(uint256);
function setMinPrice(uint256 minPrice) external;
function nextOptionReadyAt() external view returns (uint256);
function GNOSIS_EASY_AUCTION() external view returns(address);
function vaultState() external view returns (Vault.VaultState memory);
function roundPricePerShare(uint256) external view returns (uint256);
function cap() external view returns(uint256);
function setCap(uint256 amount) external;
function owner() external view returns(address);
}
8 changes: 8 additions & 0 deletions contracts/interfaces/ribbon/IYearn.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

interface IYearnPricer {
function setExpiryPriceInOracle(uint256 _expiryTimestamp) external;
function getPrice() external view returns (uint256);
function underlying() external view returns (address);
}
222 changes: 222 additions & 0 deletions contracts/strategies/ribbon/IdleRibbonStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;

import "../../interfaces/IWETH.sol";
import "../../interfaces/IIdleCDOStrategy.sol";
import "../../interfaces/IERC20Detailed.sol";

import "../../interfaces/ribbon/IRibbonVault.sol";

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

import "hardhat/console.sol";

/// @author LiveDuo.
/// @title IdleRibbonStrategy
/// @notice IIdleCDOStrategy to deploy funds in Idle Finance
/// @dev This contract should not have any funds at the end of each tx.
contract IdleRibbonStrategy is Initializable, OwnableUpgradeable, ERC20Upgradeable, ReentrancyGuardUpgradeable, IIdleCDOStrategy {
using SafeERC20Upgradeable for IERC20Detailed;

/// @notice underlying token address
address public override token;

/// @notice address of the strategy used
address public override strategyToken;

/// @notice decimals of the underlying asset
uint256 public override tokenDecimals;

/// @notice one underlying token
uint256 public override oneToken;

/// @notice underlying ERC20 token contract
IERC20Detailed public underlyingToken;

/* ------------Extra declarations ---------------- */
/// @notice vault
IRibbonVault public vault;

/// @notice address of the IdleCDO
address public idleCDO;

/// @notice 100000 => 100%
uint32 constant MAX_APR_PERC = 100000;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
token = address(1);
}

/// @notice Can be called only once
/// @dev Initialize the upgradable contract
/// @param _strategyToken address of the strategy token.
/// @param _underlyingToken address of the token deposited.
function initialize(
address _strategyToken,
address _underlyingToken,
address _vault,
address _owner
) public initializer {
require(token == address(0), "Token is already initialized");

// initialize owner
OwnableUpgradeable.__Ownable_init();
transferOwnership(_owner);
ReentrancyGuardUpgradeable.__ReentrancyGuard_init();

// underlying token
token = _underlyingToken;
underlyingToken = IERC20Detailed(token);
tokenDecimals = underlyingToken.decimals();
oneToken = 10**(tokenDecimals);

// initialize strategy token
string memory params = string(abi.encodePacked("idleRB", underlyingToken.symbol()));
ERC20Upgradeable.__ERC20_init("Idle Ribbon Strategy Token", params);

// strategy token
strategyToken = address(this);

// ribbon vault
vault = IRibbonVault(_vault);
}

/* -------- write functions ------------- */

/// @notice Deposit the underlying token to vault
/// @param _amount number of tokens to deposit
/// @return minted number of reward tokens minted
function deposit(uint256 _amount) external override onlyIdleCDO returns (uint256 minted) {
minted = _deposit(_amount);
}

/// @notice Redeem Tokens
/// @param _amount number of tokens to redeem
/// @return minted number of reward tokens minted
function redeem(uint256 _amount) external override onlyIdleCDO returns (uint256) {
vault.initiateWithdraw(_amount);
return _amount;
}

/// @notice Complete redeem Tokens
/// @return Amount of underlying tokens received
function completeRedeem() external onlyIdleCDO returns (uint256) {
uint256 _amount = vault.accountVaultBalance(address(this));
return _redeem(_amount);
}

/// @notice Redeem Tokens
/// @param _amount amount of underlying tokens to redeem
/// @return Amount of underlying tokens received
function redeemUnderlying(uint256 _amount) external override onlyIdleCDO returns (uint256) {
uint256 _price = price();
uint256 _strategyTokens = (_amount * oneToken) / _price;
return _redeem(_strategyTokens);
}

/// @notice allow to update whitelisted address
function setWhitelistedCDO(address _cdo) external onlyOwner {
require(_cdo != address(0), "IS_0");
idleCDO = _cdo;
}

/* -------- read functions ------------- */

/// @notice Approximate APR
/// @return apr
function getApr() external view override returns (uint256 apr) {
uint16 round = vault.vaultState().round;
if (round < 2) {
return 0;
}

uint256 previousWeekStartAmount = vault.roundPricePerShare(round - 2);
uint256 previousWeekEndAmount = vault.roundPricePerShare(round - 1);
uint256 weekApr = (previousWeekEndAmount * MAX_APR_PERC / previousWeekStartAmount) - MAX_APR_PERC;
return weekApr * 52;
}

/// @notice net price in underlyings of 1 strategyToken
/// @return _price
function price() public view override returns (uint256 _price) {
return vault.pricePerShare();
}

/* -------- internal functions ------------- */

/// @notice Internal function to deposit the underlying tokens to the vault
/// @param _amount amount of tokens to deposit
/// @return _minted number of reward tokens minted
function _deposit(uint256 _amount) internal returns (uint256 _minted) {

if (_amount > 0) {
underlyingToken.transferFrom(msg.sender, address(this), _amount);

if(address(underlyingToken) == vault.WETH()) {
IWETH(token).withdraw(_amount);
vault.depositETH{value: _amount}();
} else {
underlyingToken.approve(address(vault), _amount);

try vault.STETH() returns (address v) {
vault.depositYieldToken(_amount);
} catch (bytes memory) {
vault.deposit(_amount);
}

}

uint256 _minted = _amount * oneToken;
_mint(msg.sender, _minted);
}

}

/// @notice Internal function to redeem the underlying tokens
/// @param _amount Amount of strategy tokens
/// @return massetReceived Amount of underlying tokens received
function _redeem(uint256 _amount) internal returns (uint256 massetReceived) {

_burn(msg.sender, _amount);

vault.completeWithdraw();

uint256 currentBalance;

if(address(underlyingToken) == vault.WETH()) {
currentBalance = address(this).balance;
IWETH(address(underlyingToken)).deposit{value : currentBalance}();
} else {
currentBalance = underlyingToken.balanceOf(address(this));
underlyingToken.approve(address(this), currentBalance);
underlyingToken.approve(msg.sender, currentBalance);
}

underlyingToken.transferFrom(address(this), msg.sender, currentBalance);
}

/// @notice fallback functions to allow receiving eth
fallback() external payable {}

/// @notice Modifier to make sure that caller os only the idleCDO contract
modifier onlyIdleCDO() {
require(idleCDO == msg.sender, "Only IdleCDO can call");
_;
}

/* -------- unused functions ------------- */

/// @notice unused in Ribbon Strategy
function redeemRewards(bytes calldata _extraData) external override onlyIdleCDO returns (uint256[] memory rewards) {}

/// @notice unused in Ribbon Strategy
function pullStkAAVE() external pure override returns (uint256) {}

/// @notice unused in Ribbon Strategy
function getRewardTokens() external pure override returns (address[] memory _rewards) {}

}
Loading