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

Idle provider #81

Open
wants to merge 17 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ coverage.json
abi

config.ts
.env
25 changes: 25 additions & 0 deletions contracts/external-interfaces/idle/IIdleToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity 0.7.6;

interface IIdleToken {
function token() external returns (address underlying);
function govTokens(uint256) external returns (address govToken);
function userAvgPrices(address) external returns (uint256 avgPrice);
function mintIdleToken(uint256 _amount, bool _skipWholeRebalance, address _referral) external returns (uint256 mintedTokens);
function redeemIdleToken(uint256 _amount) external returns (uint256 redeemedTokens);
function redeemInterestBearingTokens(uint256 _amount) external;
function rebalance() external returns (bool);
function tokenPrice() external view returns (uint256 price);
function getAPRs() external view returns (address[] memory addresses, uint256[] memory aprs);
function getAvgAPR() external view returns (uint256 avgApr);
function getGovTokensAmounts(address _usr) external view returns (uint256[] memory _amounts);
function flashLoanFee() external view returns (uint256 fee);
function flashFee(address _token, uint256 _amount) external view returns (uint256);
function maxFlashLoan(address _token) external view returns (uint256);
function getAllocations() external view returns (uint256[] memory);
function getGovTokens() external view returns (address[] memory);
function getAllAvailableTokens() external view returns (address[] memory);
function getProtocolTokenToGov(address _protocolToken) external view returns (address);
function tokenPriceWithFee(address user) external view returns (uint256 priceWFee);
}
11 changes: 11 additions & 0 deletions contracts/external-interfaces/idle/IIdleTokenHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.6;

interface IIdleTokenHelper {
function setIdleTokens(address[] calldata _newIdleTokens) external;
function getAPR(address _idleToken, address _cToken, address _aToken) external view returns (uint256 avgApr);
function getCurrentAllocations(address _idleToken) external view returns (uint256[] memory amounts, uint256 total);
function getAPRs(address _idleToken) external view returns (address[] memory addresses, uint256[] memory aprs);
function sellGovTokens(address _idleToken, uint256[] calldata _minTokenOut) external;
function emergencyWithdrawToken(address _token, address _to) external;
}
159 changes: 159 additions & 0 deletions contracts/mocks/idle/IdleTokenWorldMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.7.6;

// used by the cream provider tests

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

import "./../Erc20Mock.sol";

import "../../external-interfaces/idle/IIdleToken.sol";

contract IdleTokenWorldMock is IIdleToken, ERC20 {
using SafeMath for uint256;
using SafeERC20 for IERC20;

uint256 private constant ONE_18 = 10**18;

// Idle rebalancer current implementation address
address public rebalancer;
// Address collecting underlying fees
address public feeAddress;
// eg. 18 for DAI

uint256 private tokenDecimals;

// Current fee on interest gained
uint256 public fee;
//address public override token;
mapping(address => uint256) public override userAvgPrices;
address[] private _govTokens;

address public _underlying;
uint256 public _tokenPrice;
uint256 public _avgAPR;
// array with last balance recorded for each gov tokens
mapping (address => uint256) public govTokensLastBalances;

// govToken -> user_address -> user_index eg. usersGovTokensIndexes[govTokens[0]][msg.sender] = 1111123;
mapping (address => mapping (address => uint256)) public usersGovTokensIndexes;

// global indices for each gov tokens used as a reference to calculate a fair share for each user
mapping (address => uint256) public govTokensIndexes;

// oracle used for calculating the avgAPR with gov tokens
address public oracle;

uint256 public mintCalled;
uint256 public redeemCalled;
uint256 public redeemUnderlyingCalled;

constructor(uint256 tokenPrice_, uint8 underlyingDecimals_, uint256 avgAPR_)
ERC20("IdleToken mock", "IdleToken") {
_underlying = address(new Erc20Mock("DAI Mock", "DAIMOCK", underlyingDecimals_));
_tokenPrice = tokenPrice_;
_avgAPR = avgAPR_;

}

function mintMock(address to_, uint256 amount_) external {
_mint(to_, amount_);
}

function burnMock(address to_, uint256 amount_) external {
_burn(to_, amount_);
}

function mintIdleToken(uint256 _amount, bool _skipWholeRebalance, address _referral) external override returns (uint256) {
IERC20(_underlying).safeTransferFrom(msg.sender, address(this), _amount);
_mint(msg.sender, _amount/this.tokenPrice());
mintCalled++;
return 0;
}

function redeemIdleToken(uint256 redeemAmount) external override returns (uint256) {
uint256 cTokenAmount = redeemAmount * 1e18 / (this.tokenPrice());
_transfer(msg.sender, address(this), cTokenAmount);
Erc20Mock(_underlying).mintMock(msg.sender, redeemAmount);
_burn(address(this), cTokenAmount);
redeemUnderlyingCalled++;
return 0;
}

function redeemInterestBearingTokens(uint256 _amount) external override {
Erc20Mock(_underlying).mint(msg.sender, _amount);
}

function rebalance() external override returns (bool) {
return false;
}

function tokenPrice() external view override returns (uint256 price) {
return _tokenPrice;
}

function token() external override returns (address underlying) {
return _underlying;
}

function getAPRs() external view override returns (address[] memory, uint256[] memory) {
address[] memory addresses;
uint256[] memory aprs;
return (addresses, aprs);
}

function getAvgAPR() external view override returns (uint256 avgApr) {
return _avgAPR;
}

function getGovTokensAmounts(address _usr) external view override returns (uint256[] memory amounts) {
amounts = new uint256[](3);
amounts[0] = 0;
amounts[1] = 0;
amounts[2] = 0;
}

function flashLoanFee() external override view returns (uint256) {
return 0;
}

function flashFee(address _token, uint256 _amount) external override view returns (uint256) {
return 0;
}

function maxFlashLoan(address _token) external override view returns (uint256) {
return 0;
}

function getAllocations() external override view returns (uint256[] memory) {
uint256[] memory allocs = new uint256[](1);
allocs[0] = 0;
return allocs;
}

function govTokens(uint256) external override returns (address govToken) {
return address(0);

}

function getGovTokens() external override view returns (address[] memory) {
return _govTokens;
}

function getAllAvailableTokens() external override view returns (address[] memory) {
address[] memory availableTokens = new address[](1);
availableTokens[0] = address(0);
return availableTokens;

}

function getProtocolTokenToGov(address _protocolToken) external override view returns (address) {
return address(0);
}

function tokenPriceWithFee(address user) external view override returns (uint256 priceWFee) {
return _tokenPrice;
}
}
9 changes: 9 additions & 0 deletions contracts/providers/IIdleCumulator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.7.6;
pragma abicoder v2;

interface IIdleCumulator {
function _beforeCTokenBalanceChange() external;

function _afterCTokenBalanceChange(uint256 prevCTokenBalance_) external;
}
143 changes: 143 additions & 0 deletions contracts/providers/IdleController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.7.6;
pragma abicoder v2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "./../lib/math/MathUtils.sol";
import "./../external-interfaces/idle/IIdleToken.sol";
import "./IIdleCumulator.sol";
import "../IController.sol";
import "./../oracle/IYieldOracle.sol";
import "./../oracle/IYieldOraclelizable.sol";
import "./IdleProvider.sol";

contract IdleController is IController, IIdleCumulator, IYieldOraclelizable {
using SafeMath for uint256;
using SafeERC20 for IERC20;

uint256 public constant MAX_UINT256 = uint256(-1);
address public uToken;
address public cToken;
address public rewardsCollector;
// last time we cumulated
uint256 public prevCumulationTime;

// exchangeRateStored last time we cumulated
uint256 public prevExchangeRateCurrent;

// cumulative supply rate += ((new underlying) / underlying)
uint256 public cumulativeSupplyRate;

uint256 public underlyingDecimals;

event Harvest(address indexed caller, address[] token, uint256[] rewardTotal, uint256[] rewardSold, uint256 underlyingReward, uint256 harvestCost);

modifier onlyPool {
require(
msg.sender == pool,
"IC: only pool"
);
_;
}

constructor(
address pool_,
address smartYield_,
address bondModel_,
address rewardsCollector_
)
IController()
{
pool = pool_;
smartYield = smartYield_;
rewardsCollector = rewardsCollector_;
uToken = IdleProvider(pool).uToken();
cToken = IdleProvider(pool).cToken();
underlyingDecimals = ERC20(uToken).decimals();
setBondModel(bondModel_);
}

function _beforeCTokenBalanceChange() external override onlyPool {}

function _afterCTokenBalanceChange(uint256 prevCTokenBalance_) external override onlyPool {
updateCumulativesInternal(prevCTokenBalance_, false);
IYieldOracle(oracle).update();
}

function updateCumulativesInternal(uint256 val, bool pingIdle_) internal {
uint256 timeElapsed = block.timestamp - prevCumulationTime;

// only cumulate once per block
if (0 == timeElapsed) {
return;
}

uint256 exchangeRateStoredNow = IIdleToken(cToken).tokenPriceWithFee(address(pool));

if (prevExchangeRateCurrent > 0) {
// cumulate a new supplyRate delta: cumulativeSupplyRate += (cToken.exchangeRateCurrent() - prevExchnageRateCurrent) / prevExchnageRateCurrent
// cumulativeSupplyRate eventually overflows, but that's ok due to the way it's used in the oracle
cumulativeSupplyRate += exchangeRateStoredNow.sub(prevExchangeRateCurrent).mul(EXP_SCALE).div(prevExchangeRateCurrent);
}

prevCumulationTime = block.timestamp;

// exchangeRateStored can increase multiple times per block
prevExchangeRateCurrent = exchangeRateStoredNow;
}

function providerRatePerDay() public override returns (uint256) {
return MathUtils.min(
MathUtils.min(BOND_MAX_RATE_PER_DAY, spotDailyRate()),
IYieldOracle(oracle).consult(1 days)
);
}

function cumulatives() public override returns (uint256) {
uint256 timeElapsed = block.timestamp - prevCumulationTime;
// only cumulate once per block
if (0 == timeElapsed) {
return cumulativeSupplyRate;
}
uint256 cTokenBalance = IdleProvider(pool).cTokenBalance();
updateCumulativesInternal(cTokenBalance, false);
return cumulativeSupplyRate;
}

function cTokensToUnderlying(uint256 cTokens_, uint256 exchangeRate_) public pure returns (uint256) {
return cTokens_.mul(exchangeRate_).div(EXP_SCALE);
}

function harvest(uint256)
public
returns (address[] memory tokens, uint256[] memory rewardAmounts, uint256 underlyingHarvestReward)
{
(address[] memory tokens, uint256[] memory rewardTotal, uint256[] memory rewardSold)
= IdleProvider(pool).claimRewardsTo(MAX_UINT256, rewardsCollector);
emit Harvest(msg.sender, tokens, rewardTotal, rewardSold, 0, HARVEST_COST);
return (tokens, rewardTotal, 0);
}

function spotDailySupplyRateProvider() public view returns (uint256) {
// eg. [5000, 0, 5000, 0] for 50% in compound, 0% fulcrum, 50% aave, 0 dydx. same order of allAvailableTokens
uint256 apr = 0;
uint256[] memory allocations = IIdleToken(cToken).getAllocations();
(address[] memory addresses, uint256[] memory aprs) = IIdleToken(cToken).getAPRs();
for (uint256 i = 0; i<allocations.length; i++) {
apr = apr.add(allocations[i].mul(aprs[i]));
}
return apr.div(36500).div(10000);
}

function spotDailyDistributionRateProvider() public view returns (uint256) {
return 0;
}

function spotDailyRate() public view returns (uint256) {
return spotDailySupplyRateProvider();
}

}
Loading