-
-
Save carlomazzaferro/fe9bffc84d03c9695b82a8c23fbd66a2 to your computer and use it in GitHub Desktop.
FusePoolLensSecondary.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: UNLICENSED | |
pragma solidity 0.6.12; | |
pragma experimental ABIEncoderV2; | |
import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; | |
import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; | |
import "./external/compound/Comptroller.sol"; | |
import "./external/compound/PriceOracle.sol"; | |
import "./external/compound/CToken.sol"; | |
import "./external/compound/CErc20.sol"; | |
import "./external/compound/RewardsDistributor.sol"; | |
import "./external/uniswap/IUniswapV2Pair.sol"; | |
import "./FusePoolDirectory.sol"; | |
import "./oracles/MasterPriceOracle.sol"; | |
/** | |
* @title FusePoolLensSecondary | |
* @author David Lucid <[email protected]> (https://github.com/davidlucid) | |
* @notice FusePoolLensSecondary returns data on Fuse interest rate pools in mass for viewing by dApps, bots, etc. | |
*/ | |
contract FusePoolLensSecondary is Initializable { | |
using SafeMathUpgradeable for uint256; | |
/** | |
* @notice Constructor to set the `FusePoolDirectory` contract object. | |
*/ | |
function initialize(FusePoolDirectory _directory) public initializer { | |
require(address(_directory) != address(0), "FusePoolDirectory instance cannot be the zero address."); | |
directory = _directory; | |
} | |
/** | |
* @notice `FusePoolDirectory` contract object. | |
*/ | |
FusePoolDirectory public directory; | |
/** | |
* @notice Struct for ownership over a CToken. | |
*/ | |
struct CTokenOwnership { | |
address cToken; | |
address admin; | |
bool adminHasRights; | |
bool fuseAdminHasRights; | |
} | |
/** | |
* @notice Returns the admin, admin rights, Fuse admin (constant), Fuse admin rights, and an array of cTokens with differing properties. | |
* @dev This function is not designed to be called in a transaction: it is too gas-intensive. | |
* Ideally, we can add the `view` modifier, but many cToken functions potentially modify the state. | |
*/ | |
function getPoolOwnership(Comptroller comptroller) external view returns (address, bool, bool, CTokenOwnership[] memory) { | |
// Get pool ownership | |
address comptrollerAdmin = comptroller.admin(); | |
bool comptrollerAdminHasRights = comptroller.adminHasRights(); | |
bool comptrollerFuseAdminHasRights = comptroller.fuseAdminHasRights(); | |
// Get cToken ownership | |
CToken[] memory cTokens = comptroller.getAllMarkets(); | |
uint256 arrayLength = 0; | |
for (uint256 i = 0; i < cTokens.length; i++) { | |
CToken cToken = cTokens[i]; | |
(bool isListed, ) = comptroller.markets(address(cToken)); | |
if (!isListed) continue; | |
address cTokenAdmin; | |
try cToken.admin() returns (address _cTokenAdmin) { | |
cTokenAdmin = _cTokenAdmin; | |
} catch { | |
continue; | |
} | |
bool cTokenAdminHasRights = cToken.adminHasRights(); | |
bool cTokenFuseAdminHasRights = cToken.fuseAdminHasRights(); | |
// If outlier, push to array | |
if (cTokenAdmin != comptrollerAdmin || cTokenAdminHasRights != comptrollerAdminHasRights || cTokenFuseAdminHasRights != comptrollerFuseAdminHasRights) | |
arrayLength++; | |
} | |
CTokenOwnership[] memory outliers = new CTokenOwnership[](arrayLength); | |
uint256 arrayIndex = 0; | |
for (uint256 i = 0; i < cTokens.length; i++) { | |
CToken cToken = cTokens[i]; | |
(bool isListed, ) = comptroller.markets(address(cToken)); | |
if (!isListed) continue; | |
address cTokenAdmin; | |
try cToken.admin() returns (address _cTokenAdmin) { | |
cTokenAdmin = _cTokenAdmin; | |
} catch { | |
continue; | |
} | |
bool cTokenAdminHasRights = cToken.adminHasRights(); | |
bool cTokenFuseAdminHasRights = cToken.fuseAdminHasRights(); | |
// If outlier, push to array and increment array index | |
if (cTokenAdmin != comptrollerAdmin || cTokenAdminHasRights != comptrollerAdminHasRights || cTokenFuseAdminHasRights != comptrollerFuseAdminHasRights) { | |
outliers[arrayIndex] = CTokenOwnership(address(cToken), cTokenAdmin, cTokenAdminHasRights, cTokenFuseAdminHasRights); | |
arrayIndex++; | |
} | |
} | |
return (comptrollerAdmin, comptrollerAdminHasRights, comptrollerFuseAdminHasRights, outliers); | |
} | |
/** | |
* @notice Determine the maximum redeem amount of a cToken. | |
* @param cTokenModify The market to hypothetically redeem in. | |
* @param account The account to determine liquidity for. | |
* @return Maximum redeem amount. | |
*/ | |
function getMaxRedeem(address account, CToken cTokenModify) external returns (uint256) { | |
return getMaxRedeemOrBorrow(account, cTokenModify, false); | |
} | |
/** | |
* @notice Determine the maximum borrow amount of a cToken. | |
* @param cTokenModify The market to hypothetically borrow in. | |
* @param account The account to determine liquidity for. | |
* @return Maximum borrow amount. | |
*/ | |
function getMaxBorrow(address account, CToken cTokenModify) external returns (uint256) { | |
return getMaxRedeemOrBorrow(account, cTokenModify, true); | |
} | |
/** | |
* @dev Internal function to determine the maximum borrow/redeem amount of a cToken. | |
* @param cTokenModify The market to hypothetically borrow/redeem in. | |
* @param account The account to determine liquidity for. | |
* @return Maximum borrow/redeem amount. | |
*/ | |
function getMaxRedeemOrBorrow(address account, CToken cTokenModify, bool isBorrow) internal returns (uint256) { | |
// Accrue interest | |
uint256 balanceOfUnderlying = cTokenModify.balanceOfUnderlying(account); | |
// Get account liquidity | |
Comptroller comptroller = Comptroller(cTokenModify.comptroller()); | |
(uint256 err, uint256 liquidity, uint256 shortfall) = comptroller.getAccountLiquidity(account); | |
require(err == 0, "Comptroller error when calculating account liquidity."); | |
if (shortfall > 0) return 0; // Shortfall, so no more borrow/redeem | |
// Get max borrow/redeem | |
uint256 maxBorrowOrRedeemAmount; | |
if (!isBorrow && !comptroller.checkMembership(msg.sender, cTokenModify)) { | |
// Max redeem = balance of underlying if not used as collateral | |
maxBorrowOrRedeemAmount = balanceOfUnderlying; | |
} else { | |
// Avoid "stack too deep" error by separating this logic | |
maxBorrowOrRedeemAmount = _getMaxRedeemOrBorrow(liquidity, cTokenModify, isBorrow); | |
// Redeem only: max out at underlying balance | |
if (!isBorrow && balanceOfUnderlying < maxBorrowOrRedeemAmount) maxBorrowOrRedeemAmount = balanceOfUnderlying; | |
} | |
// Get max borrow or redeem considering cToken liquidity | |
uint256 cTokenLiquidity = cTokenModify.getCash(); | |
// Return the minimum of the two maximums | |
return maxBorrowOrRedeemAmount <= cTokenLiquidity ? maxBorrowOrRedeemAmount : cTokenLiquidity; | |
} | |
/** | |
* @dev Portion of the logic in `getMaxRedeemOrBorrow` above separated to avoid "stack too deep" errors. | |
*/ | |
function _getMaxRedeemOrBorrow(uint256 liquidity, CToken cTokenModify, bool isBorrow) internal view returns (uint256) { | |
if (liquidity <= 0) return 0; // No available account liquidity, so no more borrow/redeem | |
// Get the normalized price of the asset | |
Comptroller comptroller = Comptroller(cTokenModify.comptroller()); | |
uint256 conversionFactor = comptroller.oracle().getUnderlyingPrice(cTokenModify); | |
require(conversionFactor > 0, "Oracle price error."); | |
// Pre-compute a conversion factor from tokens -> ether (normalized price value) | |
if (!isBorrow) { | |
(, uint256 collateralFactorMantissa) = comptroller.markets(address(cTokenModify)); | |
conversionFactor = collateralFactorMantissa.mul(conversionFactor).div(1e18); | |
} | |
// Get max borrow or redeem considering excess account liquidity | |
return liquidity.mul(1e18).div(conversionFactor); | |
} | |
/** | |
* @notice Returns an array of all markets, an array of all `RewardsDistributor` contracts, an array of reward token addresses for each `RewardsDistributor`, an array of supply speeds for each distributor for each, and their borrow speeds. | |
* @param comptroller The Fuse pool Comptroller to check. | |
*/ | |
function getRewardSpeedsByPool(Comptroller comptroller) public view returns (CToken[] memory, RewardsDistributor[] memory, address[] memory, uint256[][] memory, uint256[][] memory) { | |
CToken[] memory allMarkets = comptroller.getAllMarkets(); | |
RewardsDistributor[] memory distributors; | |
try comptroller.getRewardsDistributors() returns (RewardsDistributor[] memory _distributors) { | |
distributors = _distributors; | |
} catch { | |
distributors = new RewardsDistributor[](0); | |
} | |
address[] memory rewardTokens = new address[](distributors.length); | |
uint256[][] memory supplySpeeds = new uint256[][](allMarkets.length); | |
uint256[][] memory borrowSpeeds = new uint256[][](allMarkets.length); | |
// Get reward tokens for each distributor | |
for (uint256 i = 0; i < distributors.length; i++) rewardTokens[i] = distributors[i].rewardToken(); | |
// Get reward speeds for each market for each distributor | |
for (uint256 i = 0; i < allMarkets.length; i++) { | |
address cToken = address(allMarkets[i]); | |
supplySpeeds[i] = new uint256[](distributors.length); | |
borrowSpeeds[i] = new uint256[](distributors.length); | |
for (uint256 j = 0; j < distributors.length; j++) { | |
RewardsDistributor distributor = distributors[j]; | |
supplySpeeds[i][j] = distributor.compSupplySpeeds(cToken); | |
borrowSpeeds[i][j] = distributor.compBorrowSpeeds(cToken); | |
} | |
} | |
return (allMarkets, distributors, rewardTokens, supplySpeeds, borrowSpeeds); | |
} | |
/** | |
* @notice For each `Comptroller`, returns an array of all markets, an array of all `RewardsDistributor` contracts, an array of reward token addresses for each `RewardsDistributor`, an array of supply speeds for each distributor for each, and their borrow speeds. | |
* @param comptrollers The Fuse pool Comptrollers to check. | |
*/ | |
function getRewardSpeedsByPools(Comptroller[] memory comptrollers) external view returns (CToken[][] memory, RewardsDistributor[][] memory, address[][] memory, uint256[][][] memory, uint256[][][] memory) { | |
CToken[][] memory allMarkets = new CToken[][](comptrollers.length); | |
RewardsDistributor[][] memory distributors = new RewardsDistributor[][](comptrollers.length); | |
address[][] memory rewardTokens = new address[][](comptrollers.length); | |
uint256[][][] memory supplySpeeds = new uint256[][][](comptrollers.length); | |
uint256[][][] memory borrowSpeeds = new uint256[][][](comptrollers.length); | |
for (uint256 i = 0; i < comptrollers.length; i++) (allMarkets[i], distributors[i], rewardTokens[i], supplySpeeds[i], borrowSpeeds[i]) = getRewardSpeedsByPool(comptrollers[i]); | |
return (allMarkets, distributors, rewardTokens, supplySpeeds, borrowSpeeds); | |
} | |
/** | |
* @notice Returns unaccrued rewards by `holder` from `cToken` on `distributor`. | |
* @param holder The address to check. | |
* @param distributor The RewardsDistributor to check. | |
* @param cToken The CToken to check. | |
* @return Unaccrued (unclaimed) supply-side rewards and unaccrued (unclaimed) borrow-side rewards. | |
*/ | |
function getUnaccruedRewards(address holder, RewardsDistributor distributor, CToken cToken) internal returns (uint256, uint256) { | |
// Get unaccrued supply rewards | |
uint256 compAccruedPrior = distributor.compAccrued(holder); | |
distributor.flywheelPreSupplierAction(address(cToken), holder); | |
uint256 supplyRewardsUnaccrued = distributor.compAccrued(holder).sub(compAccruedPrior); | |
// Get unaccrued borrow rewards | |
compAccruedPrior = distributor.compAccrued(holder); | |
distributor.flywheelPreBorrowerAction(address(cToken), holder); | |
uint256 borrowRewardsUnaccrued = distributor.compAccrued(holder).sub(compAccruedPrior); | |
// Return both | |
return (supplyRewardsUnaccrued, borrowRewardsUnaccrued); | |
} | |
/** | |
* @notice Returns all unclaimed rewards accrued by the `holder` on `distributors`. | |
* @param holder The address to check. | |
* @param distributors The `RewardsDistributor` contracts to check. | |
* @return For each of `distributors`: total quantity of unclaimed rewards, array of cTokens, array of unaccrued (unclaimed) supply-side and borrow-side rewards per cToken, and quantity of funds available in the distributor. | |
*/ | |
function getUnclaimedRewardsByDistributors(address holder, RewardsDistributor[] memory distributors) external returns (address[] memory, uint256[] memory, CToken[][] memory, uint256[2][][] memory, uint256[] memory) { | |
address[] memory rewardTokens = new address[](distributors.length); | |
uint256[] memory compUnclaimedTotal = new uint256[](distributors.length); | |
CToken[][] memory allMarkets = new CToken[][](distributors.length); | |
uint256[2][][] memory rewardsUnaccrued = new uint256[2][][](distributors.length); | |
uint256[] memory distributorFunds = new uint256[](distributors.length); | |
for (uint256 i = 0; i < distributors.length; i++) { | |
RewardsDistributor distributor = distributors[i]; | |
rewardTokens[i] = distributor.rewardToken(); | |
allMarkets[i] = distributor.getAllMarkets(); | |
rewardsUnaccrued[i] = new uint256[2][](allMarkets[i].length); | |
for (uint256 j = 0; j < allMarkets[i].length; j++) (rewardsUnaccrued[i][j][0], rewardsUnaccrued[i][j][1]) = getUnaccruedRewards(holder, distributor, allMarkets[i][j]); | |
compUnclaimedTotal[i] = distributor.compAccrued(holder); | |
distributorFunds[i] = IERC20Upgradeable(rewardTokens[i]).balanceOf(address(distributor)); | |
} | |
return (rewardTokens, compUnclaimedTotal, allMarkets, rewardsUnaccrued, distributorFunds); | |
} | |
/** | |
* @notice Returns arrays of indexes, `Comptroller` proxy contracts, and `RewardsDistributor` contracts for Fuse pools supplied to by `account`. | |
* @dev This function is not designed to be called in a transaction: it is too gas-intensive. | |
*/ | |
function getRewardsDistributorsBySupplier(address supplier) external view returns (uint256[] memory, Comptroller[] memory, RewardsDistributor[][] memory) { | |
// Get array length | |
FusePoolDirectory.FusePool[] memory pools = directory.getAllPools(); | |
uint256 arrayLength = 0; | |
for (uint256 i = 0; i < pools.length; i++) { | |
try Comptroller(pools[i].comptroller).suppliers(supplier) returns (bool isSupplier) { | |
if (isSupplier) arrayLength++; | |
} catch {} | |
} | |
// Build array | |
uint256[] memory indexes = new uint256[](arrayLength); | |
Comptroller[] memory comptrollers = new Comptroller[](arrayLength); | |
RewardsDistributor[][] memory distributors = new RewardsDistributor[][](arrayLength); | |
uint256 index = 0; | |
for (uint256 i = 0; i < pools.length; i++) { | |
Comptroller comptroller = Comptroller(pools[i].comptroller); | |
try comptroller.suppliers(supplier) returns (bool isSupplier) { | |
if (isSupplier) { | |
indexes[index] = i; | |
comptrollers[index] = comptroller; | |
try comptroller.getRewardsDistributors() returns (RewardsDistributor[] memory _distributors) { | |
distributors[index] = _distributors; | |
} catch {} | |
index++; | |
} | |
} catch {} | |
} | |
// Return distributors | |
return (indexes, comptrollers, distributors); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment