-
Notifications
You must be signed in to change notification settings - Fork 0
Ribbon fuse pool #1
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
base: fuse-final
Are you sure you want to change the base?
Changes from 1 commit
23922b7
3c5bfe4
0b20ee0
89fe5c3
c1ed18e
05e9e12
3cede01
cd396bc
99db425
89b6d6f
688aac2
4684929
b753fc8
3fe0665
f45c116
31b9857
a9926a6
378a91f
55b258b
5645f70
7f63e3d
3ddbab1
a0640a2
50beda2
eedf42c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,31 @@ | ||
| pragma solidity ^0.5.16; | ||
|
|
||
| import "./CToken.sol"; | ||
| import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; | ||
|
|
||
| interface CompLike { | ||
| function delegate(address delegatee) external; | ||
| } | ||
|
|
||
| interface RibbonMinter { | ||
| function mint(address gauge_addr) external; | ||
| } | ||
|
|
||
| /** | ||
| * @title Compound's CErc20 Contract | ||
| * @notice CTokens which wrap an EIP-20 underlying | ||
| * @dev This contract should not to be deployed on its own; instead, deploy `CErc20Delegator` (proxy contract) and `CErc20Delegate` (logic/implementation contract). | ||
| * @author Compound | ||
| */ | ||
| contract CErc20 is CToken, CErc20Interface { | ||
| // Minter contract for rbn gauge emissions | ||
| RibbonMinter public constant rbnMinter = RibbonMinter(0x5B0655F938A72052c46d2e94D206ccB6FF625A3A); | ||
| // RBN token | ||
| IERC20Upgradeable public constant RBN = IERC20Upgradeable(0x6123b0049f904d730db3c36a31167d9d4121fa6b); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can use IERC20 because we're not touching any of the upgradeable functionality |
||
| // Rewards distributor | ||
| // https://github.com/Rari-Capital/compound-protocol/blob/fuse-final/contracts/RewardsDistributorDelegator.sol | ||
| address public rewardsDistributor; | ||
|
|
||
| /** | ||
| * @notice Initialize the new money market | ||
| * @param underlying_ The address of the underlying asset | ||
|
|
@@ -178,4 +191,36 @@ contract CErc20 is CToken, CErc20Interface { | |
| require(hasAdminRights(), "only the admin may set the comp-like delegate"); | ||
| CompLike(underlying).delegate(compLikeDelegatee); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Admin call to set rewards distributor | ||
| * @param _rewardsDistributor The rewards contract | ||
| */ | ||
| function _setRewardsDistributor(address _rewardsDistributor) external { | ||
| require(hasAdminRights(), "only the admin may set the comp-like delegate"); | ||
| require(_rewardsDistributor != address(0), "rewards distributor must be set"); | ||
|
|
||
| rewardsDistributor = _rewardsDistributor; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Anyone can claim gauge rewards for collateralized gauge tokens. | ||
| */ | ||
| function claimGaugeRewards() external { | ||
| require(rewardsDistributor != address(0), "rewards distributor must be set"); | ||
|
|
||
| // Underlying is the gauge token like rETH-THETA-gauge | ||
| rbnMinter.mint(underlying) | ||
|
|
||
| /* | ||
| * Transfer rewards to reward distributor which will distribute rewards | ||
| * to those who supply / borrow. The reason we need to do this way is | ||
| * once individuals transfer the collateral (gauge tokens) to the cToken | ||
| * contract, they forfeit their rewards and now the cToken starts accumulating | ||
| * rewards. We want to redistribute some of it back to those supplying | ||
| * gauge tokens as collateral who 'should' be getting those rewards, and some | ||
| * to DAI / USDC suppliers | ||
| */ | ||
| RBN.transfer(rewardsDistributor, RBN.balanceOf(address(this))) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,239 @@ | ||
| // SPDX-License-Identifier: GPL-3.0 | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol"; | ||
| import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; | ||
|
|
||
| import "./interfaces/IPriceOracle.sol"; | ||
| import "./interfaces/IBasePriceOracle.sol"; | ||
| import "./interfaces/ICToken.sol"; | ||
| import "./interfaces/ICErc20.sol"; | ||
| import "./interfaces/IAggregatorV3Interface.sol"; | ||
| import "./interfaces/ILiquidityGauge.sol"; | ||
| import "./interfaces/IRibbonVault.sol"; | ||
| import {DSMath} from "./libraries/DSMath.sol"; | ||
|
|
||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tests for this |
||
| /** | ||
| * @title VaultPriceOracle | ||
| * @notice Returns prices from Chainlink. | ||
| * @dev Implements `PriceOracle`. | ||
| * @author David Lucid <david@rari.capital> (https://github.com/davidlucid) | ||
| */ | ||
| contract VaultVaultAssetOracle is IPriceOracle, IBasePriceOracle { | ||
| using SafeMathUpgradeable for uint256; | ||
|
|
||
| /** | ||
| * @notice Maps ERC20 token addresses to ETH-based Chainlink price feed contracts. | ||
| */ | ||
| mapping(address => IAggregatorV3Interface) public priceFeeds; | ||
|
|
||
| /** | ||
| * @notice Maps ERC20 token addresses to enums indicating the base currency of the feed. | ||
| */ | ||
| mapping(address => FeedBaseCurrency) public feedBaseCurrencies; | ||
|
|
||
| /** | ||
| * @notice Enum indicating the base currency of a Chainlink price feed. | ||
| */ | ||
| enum FeedBaseCurrency { | ||
| ETH, | ||
| USD | ||
| } | ||
|
|
||
| /** | ||
| * @notice Chainlink ETH/USD price feed contracts. | ||
| */ | ||
| IAggregatorV3Interface public constant ETH_USD_PRICE_FEED = | ||
| IAggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); | ||
|
|
||
| /** | ||
| * @notice Chainlink ETH/ETH price feed. | ||
| */ | ||
| address public constant ETH_ETH_PRICE_FEED = address(1); | ||
|
|
||
| /** | ||
| * @dev The administrator of this `MasterPriceOracle`. | ||
| */ | ||
| address public admin; | ||
|
|
||
| /** | ||
| * @dev Controls if `admin` can overwrite existing assignments of oracles to underlying tokens. | ||
| */ | ||
| bool public canAdminOverwrite; | ||
|
|
||
| /** | ||
| * @dev Constructor to set admin and canAdminOverwrite. | ||
| */ | ||
| constructor(address _admin, bool _canAdminOverwrite) public { | ||
| admin = _admin; | ||
| canAdminOverwrite = _canAdminOverwrite; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Changes the admin and emits an event. | ||
| */ | ||
| function changeAdmin(address newAdmin) external onlyAdmin { | ||
| address oldAdmin = admin; | ||
| admin = newAdmin; | ||
| emit NewAdmin(oldAdmin, newAdmin); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Event emitted when `admin` is changed. | ||
| */ | ||
| event NewAdmin(address oldAdmin, address newAdmin); | ||
|
|
||
| /** | ||
| * @dev Modifier that checks if `msg.sender == admin`. | ||
| */ | ||
| modifier onlyAdmin() { | ||
| require(msg.sender == admin, "Sender is not the admin."); | ||
| _; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Admin-only function to set price feeds. | ||
| * @param underlyings Underlying token addresses for which to set price feeds. (gauge token) | ||
| * @param feeds The Chainlink price feed contract addresses for each of `underlyings`. (underlying of gauge's vault token) | ||
| * @param baseCurrency The currency in which `feeds` are based. | ||
| */ | ||
| function setPriceFeeds( | ||
| address[] calldata underlyings, | ||
| IAggregatorV3Interface[] calldata feeds, | ||
| FeedBaseCurrency baseCurrency | ||
| ) external onlyAdmin { | ||
| // Input validation | ||
| require( | ||
| underlyings.length > 0 && underlyings.length == feeds.length, | ||
| "Lengths of both arrays must be equal and greater than 0." | ||
| ); | ||
|
|
||
| // For each token/feed | ||
| for (uint256 i = 0; i < underlyings.length; i++) { | ||
| address underlying = underlyings[i]; | ||
|
|
||
| // Check for existing oracle if !canAdminOverwrite | ||
| if (!canAdminOverwrite) | ||
| require( | ||
| address(priceFeeds[underlying]) == address(0), | ||
| "Admin cannot overwrite existing assignments of price feeds to underlying tokens." | ||
| ); | ||
|
|
||
| // Set feed and base currency | ||
| priceFeeds[underlying] = feeds[i]; | ||
| feedBaseCurrencies[underlying] = baseCurrency; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev Internal function returning the price in ETH of `underlying`. | ||
| */ | ||
| function _price(address underlying) internal view returns (uint256) { | ||
| // Get token/ETH price from Chainlink | ||
| IAggregatorV3Interface feed = priceFeeds[underlying]; | ||
| require( | ||
| address(feed) != address(0), | ||
| "No Chainlink price feed found for this underlying ERC20 token." | ||
| ); | ||
| FeedBaseCurrency baseCurrency = feedBaseCurrencies[underlying]; | ||
|
|
||
| IRibbonVault vault = IRibbonVault(ILiquidityGauge(underlying).lp_token()); | ||
| uint256 rVaultDecimals = vault.decimals(); | ||
| uint256 rVaultToAssetExchangeRate = vault.pricePerShare(); // (ex: rETH-THETA -> ETH, rBTC-THETA -> BTC) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we not have to lookup the Uniswap v3 pool for this price? |
||
|
|
||
| // underlying = rETH-THETA-gauge | ||
| // vault = rETH-THETA | ||
| // feed = ETH (underlying asset of vault rETH-THETA) | ||
| // underlying price = feed * (vault token to asset of vault exchange rate) | ||
|
|
||
| // rETH-THETA-gauge -> rETH-THETA -> ETH | ||
|
|
||
| if (baseCurrency == FeedBaseCurrency.ETH) { | ||
| // If ETH or stETH vault gauge | ||
| if (address(feed) == ETH_ETH_PRICE_FEED) { | ||
| return rVaultToAssetExchangeRate; | ||
| } | ||
|
|
||
| int256 tokenEthPrice = _feedPrice(feed); | ||
|
|
||
| return | ||
| tokenEthPrice >= 0 | ||
| ? DSMath.wmul( | ||
| uint256(tokenEthPrice), | ||
| rVaultToAssetExchangeRate.mul(10**(18 - rVaultDecimals)) | ||
| ) | ||
| : 0; | ||
| } else if (baseCurrency == FeedBaseCurrency.USD) { | ||
| int256 ethUsdPrice = _feedPrice(ETH_USD_PRICE_FEED); | ||
| if (ethUsdPrice <= 0) return 0; | ||
| int256 tokenUsdPrice = _feedPrice(feed); | ||
| if (tokenUsdPrice < 0) return 0; | ||
|
|
||
| uint256 tokenUsdPriceInAsset = DSMath.wmul( | ||
| uint256(tokenUsdPrice).mul(10**(18 - feed.decimals())), | ||
| rVaultToAssetExchangeRate.mul(10**(18 - rVaultDecimals)) | ||
| ); | ||
| return tokenUsdPriceInAsset.div(uint256(ethUsdPrice)); | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Returns the chainlink oracle price from the feed | ||
| */ | ||
| function _feedPrice(IAggregatorV3Interface feed) | ||
| internal | ||
| view | ||
| returns (int256) | ||
| { | ||
| ( | ||
| uint80 roundID, | ||
| int256 price, | ||
| , | ||
| uint256 timeStamp, | ||
| uint80 answeredInRound | ||
| ) = feed.latestRoundData(); | ||
|
|
||
| require(answeredInRound >= roundID, "Stale oracle price"); | ||
| require(timeStamp != 0, "!timeStamp"); | ||
| return price; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Returns the price in ETH of `underlying` (implements `BasePriceOracle`). | ||
| */ | ||
| function price(address underlying) external view override returns (uint256) { | ||
| return _price(underlying); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Returns the price in ETH of the token underlying `cToken`. | ||
| * @dev Implements the `PriceOracle` interface for Fuse pools (and Compound v2). | ||
| * @return Price in ETH of the token underlying `cToken`, scaled by `10 ** (36 - underlyingDecimals)`. | ||
| */ | ||
| function getUnderlyingPrice(ICToken cToken) | ||
| external | ||
| view | ||
| override | ||
| returns (uint256) | ||
| { | ||
| // Return 1e18 for ETH | ||
| if (cToken.isCEther()) return 1e18; | ||
|
|
||
| // Get underlying token address | ||
| address underlying = ICErc20(address(cToken)).underlying(); | ||
|
|
||
| // Get price | ||
| uint256 chainlinkPrice = _price(underlying); | ||
|
|
||
| // Format and return price | ||
| uint256 underlyingDecimals = uint256( | ||
| ERC20Upgradeable(underlying).decimals() | ||
| ); | ||
| return | ||
| underlyingDecimals <= 18 | ||
| ? uint256(chainlinkPrice).mul(10**(18 - underlyingDecimals)) | ||
| : uint256(chainlinkPrice).div(10**(underlyingDecimals - 18)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity >=0.7.2; | ||
| pragma experimental ABIEncoderV2; | ||
|
|
||
| interface IAggregatorV3Interface { | ||
| function decimals() external view returns (uint8); | ||
| function description() external view returns (string memory); | ||
| function version() external view returns (uint256); | ||
|
|
||
| // getRoundData and latestRoundData should both raise "No data present" | ||
| // if they do not have data to report, instead of returning unset values | ||
| // which could be misinterpreted as actual reported values. | ||
| function getRoundData(uint80 _roundId) | ||
| external | ||
| view | ||
| returns ( | ||
| uint80 roundId, | ||
| int256 answer, | ||
| uint256 startedAt, | ||
| uint256 updatedAt, | ||
| uint80 answeredInRound | ||
| ); | ||
| function latestRoundData() | ||
| external | ||
| view | ||
| returns ( | ||
| uint80 roundId, | ||
| int256 answer, | ||
| uint256 startedAt, | ||
| uint256 updatedAt, | ||
| uint80 answeredInRound | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity >=0.7.2; | ||
| pragma experimental ABIEncoderV2; | ||
|
|
||
| import "./IPriceOracle.sol"; | ||
|
|
||
| /** | ||
| * @title BasePriceOracle | ||
| * @notice Returns prices of underlying tokens directly without the caller having to specify a cToken address. | ||
| * @dev Implements the `PriceOracle` interface. | ||
| * @author David Lucid <david@rari.capital> (https://github.com/davidlucid) | ||
| */ | ||
| interface IBasePriceOracle is IPriceOracle { | ||
| /** | ||
| * @notice Get the price of an underlying asset. | ||
| * @param underlying The underlying asset to get the price of. | ||
| * @return The underlying asset price in ETH as a mantissa (scaled by 1e18). | ||
| * Zero means the price is unavailable. | ||
| */ | ||
| function price(address underlying) external view returns (uint); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
| pragma solidity >=0.7.2; | ||
| pragma experimental ABIEncoderV2; | ||
|
|
||
| import "./ICToken.sol"; | ||
|
|
||
| /** | ||
| * @title Compound's CErc20 Contract | ||
| * @notice CTokens which wrap an EIP-20 underlying | ||
| * @author Compound | ||
| */ | ||
| interface ICErc20 is ICToken { | ||
| function underlying() external view returns (address); | ||
| function liquidateBorrow(address borrower, uint repayAmount, ICToken cTokenCollateral) external returns (uint); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests for this