Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
45 changes: 45 additions & 0 deletions contracts/CErc20.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
pragma solidity ^0.5.16;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests for this

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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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)))
}
}
239 changes: 239 additions & 0 deletions contracts/RibbonVaultAssetOracle.sol
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";

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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)

@kenchangh kenchangh Apr 11, 2022

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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));
}
}
33 changes: 33 additions & 0 deletions contracts/interfaces/IAggregatorV3Interface.sol
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
);
}
21 changes: 21 additions & 0 deletions contracts/interfaces/IBasePriceOracle.sol
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);
}
15 changes: 15 additions & 0 deletions contracts/interfaces/ICErc20.sol
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);
}
Loading