Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/Comptroller.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause\r
pragma solidity ^0.8.10;\r
\r
import "./CToken.sol";\r
import "./ErrorReporter.sol";\r
import "./PriceOracle.sol";\r
import "./ComptrollerInterface.sol";\r
import "./ComptrollerStorage.sol";\r
import "./Unitroller.sol";\r
\r
// 简化的 Comp 接口,用于替换 import "./Governance/Comp.sol"\r
interface Comp {\r
function balanceOf(address account) external view returns (uint);\r
function transfer(address to, uint amount) external returns (bool);\r
}\r
\r
/**\r
* @title Compound's Comptroller Contract\r
* @author Compound\r
*/\r
contract Comptroller is ComptrollerV7Storage, ComptrollerInterface, ComptrollerErrorReporter, ExponentialNoError {\r
/// @notice Emitted when an admin supports a market\r
event MarketListed(CToken cToken);\r
\r
/// @notice Emitted when an account enters a market\r
event MarketEntered(CToken cToken, address account);\r
\r
/// @notice Emitted when an account exits a market\r
event MarketExited(CToken cToken, address account);\r
\r
/// @notice Emitted when close factor is changed by admin\r
event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa);\r
\r
/// @notice Emitted when a collateral factor is changed by admin\r
event NewCollateralFactor(CToken cToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa);\r
\r
/// @notice Emitted when liquidation incentive is changed by admin\r
event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa);\r
\r
/// @notice Emitted when price oracle is changed\r
event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle);\r
\r
/// @notice Emitted when pause guardian is changed\r
event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian);\r
\r
/// @notice Emitted when an action is paused globally\r
event ActionPaused(string action, bool pauseState);\r
\r
/// @notice Emitted when an action is paused on a market\r
event ActionPaused(CToken cToken, string action, bool pauseState);\r
\r
/// @notice Emitted when a new borrow-side COMP speed is calculated for a market\r
event CompBorrowSpeedUpdated(CToken indexed cToken, uint newSpeed);\r
\r
/// @notice Emitted when a new supply-side COMP speed is calculated for a market\r
event CompSupplySpeedUpdated(CToken indexed cToken, uint newSpeed);\r
\r
/// @notice Emitted when a new COMP speed is set for a contributor\r
event ContributorCompSpeedUpdated(address indexed contributor, uint newSpeed);\r
\r
/// @notice Emitted when COMP is distributed to a supplier\r
event DistributedSupplierComp(CToken indexed cToken, address indexed supplier, uint compDelta, uint compSupplyIndex);\r
\r
/// @notice Emitted when COMP is distributed to a borrower\r
event DistributedBorrowerComp(CToken indexed cToken, address indexed borrower, uint compDelta, uint compBorrowIndex);\r
\r
/// @notice Emitted when borrow cap for a cToken is changed\r
event NewBorrowCap(CToken indexed cToken, uint newBorrowCap);\r
\r
/// @notice Emitted when borrow cap guardian is changed\r
event NewBorrowCapGuardian(address oldBorrowCapGuardian, address newBorrowCapGuardian);\r
\r
/// @notice Emitted when COMP is granted by admin\r
event CompGranted(address recipient, uint amount);\r
\r
/// @notice Emitted when COMP accrued for a user has been manually adjusted.\r
event CompAccruedAdjusted(address indexed user, uint oldCompAccrued, uint newCompAccrued);\r
\r
/// @notice Emitted when COMP receivable for a user has been updated.\r
event CompReceivableUpdated(address indexed user, uint oldCompReceivable, uint newCompReceivable);\r
\r
/// @notice The initial COMP index for a market\r
uint224 public constant compInitialIndex = 1e36;\r
\r
// closeFactorMantissa must be strictly greater than this value\r
uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05\r
\r
// closeFactorMantissa must not exceed this value\r
uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9\r
\r
// No collateralFactorMantissa may exceed this value\r
uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9\r
\r
constructor() {\r
admin = msg.sender;\r
}\r
\r
/*** Assets You Are In ***/\r
\r
/**\r
* @notice Returns the assets an account has entered\r
* @param account The address of the account to pull assets for\r
* @return A dynamic list with the assets the account has entered\r
*/\r
function getAssetsIn(address account) external view returns (CToken[] memory) {\r
CToken[] memory assetsIn = accountAssets[account];\r
\r
return assetsIn;\r
}\r
\r
/**\r
* @notice Returns whether the given account is entered in the given asset\r
* @param account The address of the account to check\r
* @param cToken The cToken to check\r
* @return True if the account is in the asset, otherwise false.\r
*/\r
function checkMembership(address account, CToken cToken) external view returns (bool) {\r
return markets[address(cToken)].accountMembership[account];\r
}\r
\r
/**\r
* @notice Add assets to be included in account liquidity calculation\r
* @param cTokens The list of addresses of the cToken markets to be enabled\r
* @return Success indicator for whether each corresponding market was entered\r
*/\r
function enterMarkets(address[] memory cTokens) override public returns (uint[] memory) {\r
uint len = cTokens.length;\r
\r
uint[] memory results = new uint[](len);\r
for (uint i = 0; i < len; i++) {\r
CToken cToken = CToken(cTokens[i]);\r
\r
results[i] = uint(addToMarketInternal(cToken, msg.sender));\r
}\r
\r
return results;\r
}\r
\r
/**\r
* @notice Add the market to the borrower's "assets in" for liquidity calculations\r
* @param cToken The market to enter\r
* @param borrower The address of the account to modify\r
* @return Success indicator for whether the market was entered\r
*/\r
function addToMarketInternal(CToken cToken, address borrower) internal returns (Error) {\r
Market storage marketToJoin = markets[address(cToken)];\r
\r
if (!marketToJoin.isListed) {\r
// market is not listed, cannot join\r
return Error.MARKET_NOT_LISTED;\r
}\r
\r
if (marketToJoin.accountMembership[borrower] == true) {\r
// already joined\r
return Error.NO_ERROR;\r
}\r
\r
// survived the gauntlet, add to list\r
// NOTE: we store these somewhat redundantly as a significant optimization\r
// this avoids having to iterate through the list for the most common use cases\r
// that is, only when we need to perform liquidity checks\r
// and not whenever we want to check if an account is in a particular market\r
marketToJoin.accountMembership[borrower] = true;\r
accountAssets[borrower].push(cToken);\r
\r
emit MarketEntered(cToken, borrower);\r
\r
return Error.NO_ERROR;\r
}\r
\r
/**\r
* @notice Removes asset from sender's account liquidity calculation\r
* @dev Sender must not have an outstanding borrow balance in the asset,\r
* or be providing necessary collateral for an outstanding borrow.\r
* @param cTokenAddress The address of the asset to be removed\r
* @return Whether or not the account successfully exited the market\r
*/\r
function exitMarket(address cTokenAddress) override external returns (uint) {\r
CToken cToken = CToken(cTokenAddress);\r
/* Get sender tokensHeld and amountOwed underlying from the cToken */\r
(uint oErr, uint tokensHeld, uint amountOwed, ) = cToken.getAccountSnapshot(msg.sender);\r
require(oErr == 0, "exitMarket: getAccountSnapshot failed"); // semi-opaque error code\r
\r
/* Fail if the sender has a borrow balance */\r
if (amountOwed != 0) {\r
return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED);\r
}\r
\r
/* Fail if the sender is not permitted to redeem all of their tokens */\r
uint allowed = redeemAllowedInternal(cTokenAddress, msg.sender, tokensHeld);\r
if (allowed != 0) {\r
return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed);\r
}\r
\r
Market storage marketToExit = markets[address(cToken)];\r
\r
/* Return true if the sender is not already ‘in’ the market */\r
if (!marketToExit.accountMembership[msg.sender]) {\r
return uint(Error.NO_ERROR);\r
}\r
\r
/* Set cToken account membership to false */\r
delete marketToExit.accountMembership[msg.sender];\r
\r
/* Delete cToken from the account’s list of assets */\r
// load into memory for faster iteration\r
CToken[] memory userAssetList = accountAssets[msg.sender];\r
uint len = userAssetList.length;\r
uint assetIndex = len;\r
for (uint i = 0; i < len; i++) {\r
if (userAssetList[i] == cToken) {\r
assetIndex = i;\r
break;\r
}\r
}\r
\r
// We *must* have found the asset in the list or our redundant data structure is broken\r
assert(assetIndex < len);\r
\r
// copy last item in list to location of item to be removed, reduce length by 1\r
CToken[] storage storedList = accountAssets[msg.sender];\r
storedList[assetIndex] = storedList[storedList.length - 1];\r
storedList.pop();\r
\r
emit MarketExited(cToken, msg.sender);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/*** Policy Hooks ***/\r
\r
/**\r
* @notice Checks if the account should be allowed to mint tokens in the given market\r
* @param cToken The market to verify the mint against\r
* @param minter The account which would get the minted tokens\r
* @param mintAmount The amount of underlying being supplied to the market in exchange for tokens\r
* @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol)\r
*/\r
function mintAllowed(address cToken, address minter, uint mintAmount) override external returns (uint) {\r
// Pausing is a very serious situation - we revert to sound the alarms\r
require(!mintGuardianPaused[cToken], "mint is paused");\r
\r
// Shh - currently unused\r
minter;\r
mintAmount;\r
\r
if (!markets[cToken].isListed) {\r
return uint(Error.MARKET_NOT_LISTED);\r
}\r
\r
// Keep the flywheel moving\r
updateCompSupplyIndex(cToken);\r
distributeSupplierComp(cToken, minter);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Validates mint and reverts on rejection. May emit logs.\r
* @param cToken Asset being minted\r
* @param minter The address minting the tokens\r
* @param actualMintAmount The amount of the underlying asset being minted\r
* @param mintTokens The number of tokens being minted\r
*/\r
function mintVerify(address cToken, address minter, uint actualMintAmount, uint mintTokens) override external {\r
// Shh - currently unused\r
cToken;\r
minter;\r
actualMintAmount;\r
mintTokens;\r
\r
// Shh - we don't ever want this hook to be marked pure\r
if (false) {\r
maxAssets = maxAssets;\r
}\r
}\r
\r
/**\r
* @notice Checks if the account should be allowed to redeem tokens in the given market\r
* @param cToken The market to verify the redeem against\r
* @param redeemer The account which would redeem the tokens\r
* @param redeemTokens The number of cTokens to exchange for the underlying asset in the market\r
* @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol)\r
*/\r
function redeemAllowed(address cToken, address redeemer, uint redeemTokens) override external returns (uint) {\r
uint allowed = redeemAllowedInternal(cToken, redeemer, redeemTokens);\r
if (allowed != uint(Error.NO_ERROR)) {\r
return allowed;\r
}\r
\r
// Keep the flywheel moving\r
updateCompSupplyIndex(cToken);\r
distributeSupplierComp(cToken, redeemer);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
function redeemAllowedInternal(address cToken, address redeemer, uint redeemTokens) internal view returns (uint) {\r
if (!markets[cToken].isListed) {\r
return uint(Error.MARKET_NOT_LISTED);\r
}\r
\r
/* If the redeemer is not 'in' the market, then we can bypass the liquidity check */\r
if (!markets[cToken].accountMembership[redeemer]) {\r
return uint(Error.NO_ERROR);\r
}\r
\r
/* Otherwise, perform a hypothetical liquidity check to guard against shortfall */\r
(Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(redeemer, CToken(cToken), redeemTokens, 0);\r
if (err != Error.NO_ERROR) {\r
return uint(err);\r
}\r
if (shortfall > 0) {\r
return uint(Error.INSUFFICIENT_LIQUIDITY);\r
}\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Validates redeem and reverts on rejection. May emit logs.\r
* @param cToken Asset being redeemed\r
* @param redeemer The address redeeming the tokens\r
* @param redeemAmount The amount of the underlying asset being redeemed\r
* @param redeemTokens The number of tokens being redeemed\r
*/\r
function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) override external {\r
// Shh - currently unused\r
cToken;\r
redeemer;\r
\r
// Require tokens is zero or amount is also zero\r
if (redeemTokens == 0 && redeemAmount > 0) {\r
revert("redeemTokens zero");\r
}\r
}\r
\r
/**\r
* @notice Checks if the account should be allowed to borrow the underlying asset of the given market\r
* @param cToken The market to verify the borrow against\r
* @param borrower The account which would borrow the asset\r
* @param borrowAmount The amount of underlying the account would borrow\r
* @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol)\r
*/\r
function borrowAllowed(address cToken, address borrower, uint borrowAmount) override external returns (uint) {\r
// Pausing is a very serious situation - we revert to sound the alarms\r
require(!borrowGuardianPaused[cToken], "borrow is paused");\r
\r
if (!markets[cToken].isListed) {\r
return uint(Error.MARKET_NOT_LISTED);\r
}\r
\r
if (!markets[cToken].accountMembership[borrower]) {\r
// only cTokens may call borrowAllowed if borrower not in market\r
require(msg.sender == cToken, "sender must be cToken");\r
\r
// attempt to add borrower to the market\r
Error err = addToMarketInternal(CToken(msg.sender), borrower);\r
if (err != Error.NO_ERROR) {\r
return uint(err);\r
}\r
\r
// it should be impossible to break the important invariant\r
assert(markets[cToken].accountMembership[borrower]);\r
}\r
\r
if (oracle.getUnderlyingPrice(CToken(cToken)) == 0) {\r
return uint(Error.PRICE_ERROR);\r
}\r
\r
\r
uint borrowCap = borrowCaps[cToken];\r
// Borrow cap of 0 corresponds to unlimited borrowing\r
if (borrowCap != 0) {\r
uint totalBorrows = CToken(cToken).totalBorrows();\r
uint nextTotalBorrows = add_(totalBorrows, borrowAmount);\r
require(nextTotalBorrows < borrowCap, "market borrow cap reached");\r
}\r
\r
(Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, CToken(cToken), 0, borrowAmount);\r
if (err != Error.NO_ERROR) {\r
return uint(err);\r
}\r
if (shortfall > 0) {\r
return uint(Error.INSUFFICIENT_LIQUIDITY);\r
}\r
\r
// Keep the flywheel moving\r
Exp memory borrowIndex = Exp({mantissa: CToken(cToken).borrowIndex()});\r
updateCompBorrowIndex(cToken, borrowIndex);\r
distributeBorrowerComp(cToken, borrower, borrowIndex);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Validates borrow and reverts on rejection. May emit logs.\r
* @param cToken Asset whose underlying is being borrowed\r
* @param borrower The address borrowing the underlying\r
* @param borrowAmount The amount of the underlying asset requested to borrow\r
*/\r
function borrowVerify(address cToken, address borrower, uint borrowAmount) override external {\r
// Shh - currently unused\r
cToken;\r
borrower;\r
borrowAmount;\r
\r
// Shh - we don't ever want this hook to be marked pure\r
if (false) {\r
maxAssets = maxAssets;\r
}\r
}\r
\r
/**\r
* @notice Checks if the account should be allowed to repay a borrow in the given market\r
* @param cToken The market to verify the repay against\r
* @param payer The account which would repay the asset\r
* @param borrower The account which would borrowed the asset\r
* @param repayAmount The amount of the underlying asset the account would repay\r
* @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol)\r
*/\r
function repayBorrowAllowed(\r
address cToken,\r
address payer,\r
address borrower,\r
uint repayAmount) override external returns (uint) {\r
// Shh - currently unused\r
payer;\r
borrower;\r
repayAmount;\r
\r
if (!markets[cToken].isListed) {\r
return uint(Error.MARKET_NOT_LISTED);\r
}\r
\r
// Keep the flywheel moving\r
Exp memory borrowIndex = Exp({mantissa: CToken(cToken).borrowIndex()});\r
updateCompBorrowIndex(cToken, borrowIndex);\r
distributeBorrowerComp(cToken, borrower, borrowIndex);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Validates repayBorrow and reverts on rejection. May emit logs.\r
* @param cToken Asset being repaid\r
* @param payer The address repaying the borrow\r
* @param borrower The address of the borrower\r
* @param actualRepayAmount The amount of underlying being repaid\r
*/\r
function repayBorrowVerify(\r
address cToken,\r
address payer,\r
address borrower,\r
uint actualRepayAmount,\r
uint borrowerIndex) override external {\r
// Shh - currently unused\r
cToken;\r
payer;\r
borrower;\r
actualRepayAmount;\r
borrowerIndex;\r
\r
// Shh - we don't ever want this hook to be marked pure\r
if (false) {\r
maxAssets = maxAssets;\r
}\r
}\r
\r
/**\r
* @notice Checks if the liquidation should be allowed to occur\r
* @param cTokenBorrowed Asset which was borrowed by the borrower\r
* @param cTokenCollateral Asset which was used as collateral and will be seized\r
* @param liquidator The address repaying the borrow and seizing the collateral\r
* @param borrower The address of the borrower\r
* @param repayAmount The amount of underlying being repaid\r
*/\r
function liquidateBorrowAllowed(\r
address cTokenBorrowed,\r
address cTokenCollateral,\r
address liquidator,\r
address borrower,\r
uint repayAmount) override external returns (uint) {\r
// Shh - currently unused\r
liquidator;\r
\r
if (!markets[cTokenBorrowed].isListed || !markets[cTokenCollateral].isListed) {\r
return uint(Error.MARKET_NOT_LISTED);\r
}\r
\r
uint borrowBalance = CToken(cTokenBorrowed).borrowBalanceStored(borrower);\r
\r
/* allow accounts to be liquidated if the market is deprecated */\r
if (isDeprecated(CToken(cTokenBorrowed))) {\r
require(borrowBalance >= repayAmount, "Can not repay more than the total borrow");\r
} else {\r
/* The borrower must have shortfall in order to be liquidatable */\r
(Error err, , uint shortfall) = getAccountLiquidityInternal(borrower);\r
if (err != Error.NO_ERROR) {\r
return uint(err);\r
}\r
\r
if (shortfall == 0) {\r
return uint(Error.INSUFFICIENT_SHORTFALL);\r
}\r
\r
/* The liquidator may not repay more than what is allowed by the closeFactor */\r
uint maxClose = mul_ScalarTruncate(Exp({mantissa: closeFactorMantissa}), borrowBalance);\r
if (repayAmount > maxClose) {\r
return uint(Error.TOO_MUCH_REPAY);\r
}\r
}\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Validates liquidateBorrow and reverts on rejection. May emit logs.\r
* @param cTokenBorrowed Asset which was borrowed by the borrower\r
* @param cTokenCollateral Asset which was used as collateral and will be seized\r
* @param liquidator The address repaying the borrow and seizing the collateral\r
* @param borrower The address of the borrower\r
* @param actualRepayAmount The amount of underlying being repaid\r
*/\r
function liquidateBorrowVerify(\r
address cTokenBorrowed,\r
address cTokenCollateral,\r
address liquidator,\r
address borrower,\r
uint actualRepayAmount,\r
uint seizeTokens) override external {\r
// Shh - currently unused\r
cTokenBorrowed;\r
cTokenCollateral;\r
liquidator;\r
borrower;\r
actualRepayAmount;\r
seizeTokens;\r
\r
// Shh - we don't ever want this hook to be marked pure\r
if (false) {\r
maxAssets = maxAssets;\r
}\r
}\r
\r
/**\r
* @notice Checks if the seizing of assets should be allowed to occur\r
* @param cTokenCollateral Asset which was used as collateral and will be seized\r
* @param cTokenBorrowed Asset which was borrowed by the borrower\r
* @param liquidator The address repaying the borrow and seizing the collateral\r
* @param borrower The address of the borrower\r
* @param seizeTokens The number of collateral tokens to seize\r
*/\r
function seizeAllowed(\r
address cTokenCollateral,\r
address cTokenBorrowed,\r
address liquidator,\r
address borrower,\r
uint seizeTokens) override external returns (uint) {\r
// Pausing is a very serious situation - we revert to sound the alarms\r
require(!seizeGuardianPaused, "seize is paused");\r
\r
// Shh - currently unused\r
seizeTokens;\r
\r
if (!markets[cTokenCollateral].isListed || !markets[cTokenBorrowed].isListed) {\r
return uint(Error.MARKET_NOT_LISTED);\r
}\r
\r
if (CToken(cTokenCollateral).comptroller() != CToken(cTokenBorrowed).comptroller()) {\r
return uint(Error.COMPTROLLER_MISMATCH);\r
}\r
\r
// Keep the flywheel moving\r
updateCompSupplyIndex(cTokenCollateral);\r
distributeSupplierComp(cTokenCollateral, borrower);\r
distributeSupplierComp(cTokenCollateral, liquidator);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Validates seize and reverts on rejection. May emit logs.\r
* @param cTokenCollateral Asset which was used as collateral and will be seized\r
* @param cTokenBorrowed Asset which was borrowed by the borrower\r
* @param liquidator The address repaying the borrow and seizing the collateral\r
* @param borrower The address of the borrower\r
* @param seizeTokens The number of collateral tokens to seize\r
*/\r
function seizeVerify(\r
address cTokenCollateral,\r
address cTokenBorrowed,\r
address liquidator,\r
address borrower,\r
uint seizeTokens) override external {\r
// Shh - currently unused\r
cTokenCollateral;\r
cTokenBorrowed;\r
liquidator;\r
borrower;\r
seizeTokens;\r
\r
// Shh - we don't ever want this hook to be marked pure\r
if (false) {\r
maxAssets = maxAssets;\r
}\r
}\r
\r
/**\r
* @notice Checks if the account should be allowed to transfer tokens in the given market\r
* @param cToken The market to verify the transfer against\r
* @param src The account which sources the tokens\r
* @param dst The account which receives the tokens\r
* @param transferTokens The number of cTokens to transfer\r
* @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol)\r
*/\r
function transferAllowed(address cToken, address src, address dst, uint transferTokens) override external returns (uint) {\r
// Pausing is a very serious situation - we revert to sound the alarms\r
require(!transferGuardianPaused, "transfer is paused");\r
\r
// Currently the only consideration is whether or not\r
// the src is allowed to redeem this many tokens\r
uint allowed = redeemAllowedInternal(cToken, src, transferTokens);\r
if (allowed != uint(Error.NO_ERROR)) {\r
return allowed;\r
}\r
\r
// Keep the flywheel moving\r
updateCompSupplyIndex(cToken);\r
distributeSupplierComp(cToken, src);\r
distributeSupplierComp(cToken, dst);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Validates transfer and reverts on rejection. May emit logs.\r
* @param cToken Asset being transferred\r
* @param src The account which sources the tokens\r
* @param dst The account which receives the tokens\r
* @param transferTokens The number of cTokens to transfer\r
*/\r
function transferVerify(address cToken, address src, address dst, uint transferTokens) override external {\r
// Shh - currently unused\r
cToken;\r
src;\r
dst;\r
transferTokens;\r
\r
// Shh - we don't ever want this hook to be marked pure\r
if (false) {\r
maxAssets = maxAssets;\r
}\r
}\r
\r
/*** Liquidity/Liquidation Calculations ***/\r
\r
/**\r
* @dev Local vars for avoiding stack-depth limits in calculating account liquidity.\r
* Note that `cTokenBalance` is the number of cTokens the account owns in the market,\r
* whereas `borrowBalance` is the amount of underlying that the account has borrowed.\r
*/\r
struct AccountLiquidityLocalVars {\r
uint sumCollateral;\r
uint sumBorrowPlusEffects;\r
uint cTokenBalance;\r
uint borrowBalance;\r
uint exchangeRateMantissa;\r
uint oraclePriceMantissa;\r
Exp collateralFactor;\r
Exp exchangeRate;\r
Exp oraclePrice;\r
Exp tokensToDenom;\r
}\r
\r
/**\r
* @notice Determine the current account liquidity wrt collateral requirements\r
* @return (possible error code (semi-opaque),\r
account liquidity in excess of collateral requirements,\r
* account shortfall below collateral requirements)\r
*/\r
function getAccountLiquidity(address account) public view returns (uint, uint, uint) {\r
(Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, CToken(address(0)), 0, 0);\r
\r
return (uint(err), liquidity, shortfall);\r
}\r
\r
/**\r
* @notice Determine the current account liquidity wrt collateral requirements\r
* @return (possible error code,\r
account liquidity in excess of collateral requirements,\r
* account shortfall below collateral requirements)\r
*/\r
function getAccountLiquidityInternal(address account) internal view returns (Error, uint, uint) {\r
return getHypotheticalAccountLiquidityInternal(account, CToken(address(0)), 0, 0);\r
}\r
\r
/**\r
* @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed\r
* @param cTokenModify The market to hypothetically redeem/borrow in\r
* @param account The account to determine liquidity for\r
* @param redeemTokens The number of tokens to hypothetically redeem\r
* @param borrowAmount The amount of underlying to hypothetically borrow\r
* @return (possible error code (semi-opaque),\r
hypothetical account liquidity in excess of collateral requirements,\r
* hypothetical account shortfall below collateral requirements)\r
*/\r
function getHypotheticalAccountLiquidity(\r
address account,\r
address cTokenModify,\r
uint redeemTokens,\r
uint borrowAmount) public view returns (uint, uint, uint) {\r
(Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, CToken(cTokenModify), redeemTokens, borrowAmount);\r
return (uint(err), liquidity, shortfall);\r
}\r
\r
/**\r
* @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed\r
* @param cTokenModify The market to hypothetically redeem/borrow in\r
* @param account The account to determine liquidity for\r
* @param redeemTokens The number of tokens to hypothetically redeem\r
* @param borrowAmount The amount of underlying to hypothetically borrow\r
* @dev Note that we calculate the exchangeRateStored for each collateral cToken using stored data,\r
* without calculating accumulated interest.\r
* @return (possible error code,\r
hypothetical account liquidity in excess of collateral requirements,\r
* hypothetical account shortfall below collateral requirements)\r
*/\r
function getHypotheticalAccountLiquidityInternal(\r
address account,\r
CToken cTokenModify,\r
uint redeemTokens,\r
uint borrowAmount) internal view returns (Error, uint, uint) {\r
\r
AccountLiquidityLocalVars memory vars; // Holds all our calculation results\r
uint oErr;\r
\r
// For each asset the account is in\r
CToken[] memory assets = accountAssets[account];\r
for (uint i = 0; i < assets.length; i++) {\r
CToken asset = assets[i];\r
\r
// Read the balances and exchange rate from the cToken\r
(oErr, vars.cTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot(account);\r
if (oErr != 0) { // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades\r
return (Error.SNAPSHOT_ERROR, 0, 0);\r
}\r
vars.collateralFactor = Exp({mantissa: markets[address(asset)].collateralFactorMantissa});\r
vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa});\r
\r
// Get the normalized price of the asset\r
vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset);\r
if (vars.oraclePriceMantissa == 0) {\r
return (Error.PRICE_ERROR, 0, 0);\r
}\r
vars.oraclePrice = Exp({mantissa: vars.oraclePriceMantissa});\r
\r
// Pre-compute a conversion factor from tokens -> ether (normalized price value)\r
vars.tokensToDenom = mul_(mul_(vars.collateralFactor, vars.exchangeRate), vars.oraclePrice);\r
\r
// sumCollateral += tokensToDenom * cTokenBalance\r
vars.sumCollateral = mul_ScalarTruncateAddUInt(vars.tokensToDenom, vars.cTokenBalance, vars.sumCollateral);\r
\r
// sumBorrowPlusEffects += oraclePrice * borrowBalance\r
vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.oraclePrice, vars.borrowBalance, vars.sumBorrowPlusEffects);\r
\r
// Calculate effects of interacting with cTokenModify\r
if (asset == cTokenModify) {\r
// redeem effect\r
// sumBorrowPlusEffects += tokensToDenom * redeemTokens\r
vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.tokensToDenom, redeemTokens, vars.sumBorrowPlusEffects);\r
\r
// borrow effect\r
// sumBorrowPlusEffects += oraclePrice * borrowAmount\r
vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.oraclePrice, borrowAmount, vars.sumBorrowPlusEffects);\r
}\r
}\r
\r
// These are safe, as the underflow condition is checked first\r
if (vars.sumCollateral > vars.sumBorrowPlusEffects) {\r
return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0);\r
} else {\r
return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral);\r
}\r
}\r
\r
/**\r
* @notice Calculate number of tokens of collateral asset to seize given an underlying amount\r
* @dev Used in liquidation (called in cToken.liquidateBorrowFresh)\r
* @param cTokenBorrowed The address of the borrowed cToken\r
* @param cTokenCollateral The address of the collateral cToken\r
* @param actualRepayAmount The amount of cTokenBorrowed underlying to convert into cTokenCollateral tokens\r
* @return (errorCode, number of cTokenCollateral tokens to be seized in a liquidation)\r
*/\r
function liquidateCalculateSeizeTokens(address cTokenBorrowed, address cTokenCollateral, uint actualRepayAmount) override external view returns (uint, uint) {\r
/* Read oracle prices for borrowed and collateral markets */\r
uint priceBorrowedMantissa = oracle.getUnderlyingPrice(CToken(cTokenBorrowed));\r
uint priceCollateralMantissa = oracle.getUnderlyingPrice(CToken(cTokenCollateral));\r
if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) {\r
return (uint(Error.PRICE_ERROR), 0);\r
}\r
\r
/*\r
* Get the exchange rate and calculate the number of collateral tokens to seize:\r
* seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral\r
* seizeTokens = seizeAmount / exchangeRate\r
* = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate)\r
*/\r
uint exchangeRateMantissa = CToken(cTokenCollateral).exchangeRateStored(); // Note: reverts on error\r
uint seizeTokens;\r
Exp memory numerator;\r
Exp memory denominator;\r
Exp memory ratio;\r
\r
numerator = mul_(Exp({mantissa: liquidationIncentiveMantissa}), Exp({mantissa: priceBorrowedMantissa}));\r
denominator = mul_(Exp({mantissa: priceCollateralMantissa}), Exp({mantissa: exchangeRateMantissa}));\r
ratio = div_(numerator, denominator);\r
\r
seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount);\r
\r
return (uint(Error.NO_ERROR), seizeTokens);\r
}\r
\r
/*** Admin Functions ***/\r
\r
/**\r
* @notice Sets a new price oracle for the comptroller\r
* @dev Admin function to set a new price oracle\r
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)\r
*/\r
function _setPriceOracle(PriceOracle newOracle) public returns (uint) {\r
// Check caller is admin\r
if (msg.sender != admin) {\r
return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK);\r
}\r
\r
// Track the old oracle for the comptroller\r
PriceOracle oldOracle = oracle;\r
\r
// Set comptroller's oracle to newOracle\r
oracle = newOracle;\r
\r
// Emit NewPriceOracle(oldOracle, newOracle)\r
emit NewPriceOracle(oldOracle, newOracle);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Sets the closeFactor used when liquidating borrows\r
* @dev Admin function to set closeFactor\r
* @param newCloseFactorMantissa New close factor, scaled by 1e18\r
* @return uint 0=success, otherwise a failure\r
*/\r
function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) {\r
// Check caller is admin\r
require(msg.sender == admin, "only admin can set close factor");\r
\r
uint oldCloseFactorMantissa = closeFactorMantissa;\r
closeFactorMantissa = newCloseFactorMantissa;\r
emit NewCloseFactor(oldCloseFactorMantissa, closeFactorMantissa);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Sets the collateralFactor for a market\r
* @dev Admin function to set per-market collateralFactor\r
* @param cToken The market to set the factor on\r
* @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18\r
* @return uint 0=success, otherwise a failure. (See ErrorReporter for details)\r
*/\r
function _setCollateralFactor(CToken cToken, uint newCollateralFactorMantissa) external returns (uint) {\r
// Check caller is admin\r
if (msg.sender != admin) {\r
return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK);\r
}\r
\r
// Verify market is listed\r
Market storage market = markets[address(cToken)];\r
if (!market.isListed) {\r
return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS);\r
}\r
\r
Exp memory newCollateralFactorExp = Exp({mantissa: newCollateralFactorMantissa});\r
\r
// Check collateral factor <= 0.9\r
Exp memory highLimit = Exp({mantissa: collateralFactorMaxMantissa});\r
if (lessThanExp(highLimit, newCollateralFactorExp)) {\r
return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION);\r
}\r
\r
// If collateral factor != 0, fail if price == 0\r
if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(cToken) == 0) {\r
return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE);\r
}\r
\r
// Set market's collateral factor to new collateral factor, remember old value\r
uint oldCollateralFactorMantissa = market.collateralFactorMantissa;\r
market.collateralFactorMantissa = newCollateralFactorMantissa;\r
\r
// Emit event with asset, old collateral factor, and new collateral factor\r
emit NewCollateralFactor(cToken, oldCollateralFactorMantissa, newCollateralFactorMantissa);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Sets liquidationIncentive\r
* @dev Admin function to set liquidationIncentive\r
* @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18\r
* @return uint 0=success, otherwise a failure. (See ErrorReporter for details)\r
*/\r
function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) {\r
// Check caller is admin\r
if (msg.sender != admin) {\r
return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK);\r
}\r
\r
// Save current value for use in log\r
uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa;\r
\r
// Set liquidation incentive to new incentive\r
liquidationIncentiveMantissa = newLiquidationIncentiveMantissa;\r
\r
// Emit event with old incentive, new incentive\r
emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
/**\r
* @notice Add the market to the markets mapping and set it as listed\r
* @dev Admin function to set isListed and add support for the market\r
* @param cToken The address of the market (token) to list\r
* @return uint 0=success, otherwise a failure. (See enum Error for details)\r
*/\r
function _supportMarket(CToken cToken) external returns (uint) {\r
if (msg.sender != admin) {\r
return fail(Error.UNAUTHORIZED, FailureInfo.SUPPORT_MARKET_OWNER_CHECK);\r
}\r
\r
if (markets[address(cToken)].isListed) {\r
return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS);\r
}\r
\r
cToken.isCToken(); // Sanity check to make sure its really a CToken\r
\r
// Note that isComped is not in active use anymore\r
Market storage newMarket = markets[address(cToken)];\r
newMarket.isListed = true;\r
newMarket.isComped = false;\r
newMarket.collateralFactorMantissa = 0;\r
\r
_addMarketInternal(address(cToken));\r
_initializeMarket(address(cToken));\r
\r
emit MarketListed(cToken);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
function _addMarketInternal(address cToken) internal {\r
for (uint i = 0; i < allMarkets.length; i ++) {\r
require(allMarkets[i] != CToken(cToken), "market already added");\r
}\r
allMarkets.push(CToken(cToken));\r
}\r
\r
function _initializeMarket(address cToken) internal {\r
uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits");\r
\r
CompMarketState storage supplyState = compSupplyState[cToken];\r
CompMarketState storage borrowState = compBorrowState[cToken];\r
\r
/*\r
* Update market state indices\r
*/\r
if (supplyState.index == 0) {\r
// Initialize supply state index with default value\r
supplyState.index = compInitialIndex;\r
}\r
\r
if (borrowState.index == 0) {\r
// Initialize borrow state index with default value\r
borrowState.index = compInitialIndex;\r
}\r
\r
/*\r
* Update market state block numbers\r
*/\r
supplyState.block = borrowState.block = blockNumber;\r
}\r
\r
\r
/**\r
* @notice Set the given borrow caps for the given cToken markets. Borrowing that brings total borrows to or above borrow cap will revert.\r
* @dev Admin or borrowCapGuardian function to set the borrow caps. A borrow cap of 0 corresponds to unlimited borrowing.\r
* @param cTokens The addresses of the markets (tokens) to change the borrow caps for\r
* @param newBorrowCaps The new borrow cap values in underlying to be set. A value of 0 corresponds to unlimited borrowing.\r
*/\r
function _setMarketBorrowCaps(CToken[] calldata cTokens, uint[] calldata newBorrowCaps) external {\r
require(msg.sender == admin || msg.sender == borrowCapGuardian, "only admin or borrow cap guardian can set borrow caps");\r
\r
uint numMarkets = cTokens.length;\r
uint numBorrowCaps = newBorrowCaps.length;\r
\r
require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input");\r
\r
for(uint i = 0; i < numMarkets; i++) {\r
borrowCaps[address(cTokens[i])] = newBorrowCaps[i];\r
emit NewBorrowCap(cTokens[i], newBorrowCaps[i]);\r
}\r
}\r
\r
/**\r
* @notice Admin function to change the Borrow Cap Guardian\r
* @param newBorrowCapGuardian The address of the new Borrow Cap Guardian\r
*/\r
function _setBorrowCapGuardian(address newBorrowCapGuardian) external {\r
require(msg.sender == admin, "only admin can set borrow cap guardian");\r
\r
// Save current value for inclusion in log\r
address oldBorrowCapGuardian = borrowCapGuardian;\r
\r
// Store borrowCapGuardian with value newBorrowCapGuardian\r
borrowCapGuardian = newBorrowCapGuardian;\r
\r
// Emit NewBorrowCapGuardian(OldBorrowCapGuardian, NewBorrowCapGuardian)\r
emit NewBorrowCapGuardian(oldBorrowCapGuardian, newBorrowCapGuardian);\r
}\r
\r
/**\r
* @notice Admin function to change the Pause Guardian\r
* @param newPauseGuardian The address of the new Pause Guardian\r
* @return uint 0=success, otherwise a failure. (See enum Error for details)\r
*/\r
function _setPauseGuardian(address newPauseGuardian) public returns (uint) {\r
if (msg.sender != admin) {\r
return fail(Error.UNAUTHORIZED, FailureInfo.SET_PAUSE_GUARDIAN_OWNER_CHECK);\r
}\r
\r
// Save current value for inclusion in log\r
address oldPauseGuardian = pauseGuardian;\r
\r
// Store pauseGuardian with value newPauseGuardian\r
pauseGuardian = newPauseGuardian;\r
\r
// Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian)\r
emit NewPauseGuardian(oldPauseGuardian, pauseGuardian);\r
\r
return uint(Error.NO_ERROR);\r
}\r
\r
function _setMintPaused(CToken cToken, bool state) public returns (bool) {\r
require(markets[address(cToken)].isListed, "cannot pause a market that is not listed");\r
require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can pause");\r
require(msg.sender == admin || state == true, "only admin can unpause");\r
\r
mintGuardianPaused[address(cToken)] = state;\r
emit ActionPaused(cToken, "Mint", state);\r
return state;\r
}\r
\r
function _setBorrowPaused(CToken cToken, bool state) public returns (bool) {\r
require(markets[address(cToken)].isListed, "cannot pause a market that is not listed");\r
require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can pause");\r
require(msg.sender == admin || state == true, "only admin can unpause");\r
\r
borrowGuardianPaused[address(cToken)] = state;\r
emit ActionPaused(cToken, "Borrow", state);\r
return state;\r
}\r
\r
function _setTransferPaused(bool state) public returns (bool) {\r
require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can pause");\r
require(msg.sender == admin || state == true, "only admin can unpause");\r
\r
transferGuardianPaused = state;\r
emit ActionPaused("Transfer", state);\r
return state;\r
}\r
\r
function _setSeizePaused(bool state) public returns (bool) {\r
require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can pause");\r
require(msg.sender == admin || state == true, "only admin can unpause");\r
\r
seizeGuardianPaused = state;\r
emit ActionPaused("Seize", state);\r
return state;\r
}\r
\r
function _become(Unitroller unitroller) public {\r
require(msg.sender == unitroller.admin(), "only unitroller admin can change brains");\r
require(unitroller._acceptImplementation() == 0, "change not authorized");\r
}\r
\r
/// @notice Delete this function after proposal 65 is executed\r
function fixBadAccruals(address[] calldata affectedUsers, uint[] calldata amounts) external {\r
require(msg.sender == admin, "Only admin can call this function"); // Only the timelock can call this function\r
require(!proposal65FixExecuted, "Already executed this one-off function"); // Require that this function is only called once\r
require(affectedUsers.length == amounts.length, "Invalid input");\r
\r
// Loop variables\r
address user;\r
uint currentAccrual;\r
uint amountToSubtract;\r
uint newAccrual;\r
\r
// Iterate through all affected users\r
for (uint i = 0; i < affectedUsers.length; ++i) {\r
user = affectedUsers[i];\r
currentAccrual = compAccrued[user];\r
\r
amountToSubtract = amounts[i];\r
\r
// The case where the user has claimed and received an incorrect amount of COMP.\r
// The user has less currently accrued than the amount they incorrectly received.\r
if (amountToSubtract > currentAccrual) {\r
// Amount of COMP the user owes the protocol\r
uint accountReceivable = amountToSubtract - currentAccrual; // Underflow safe since amountToSubtract > currentAccrual\r
\r
uint oldReceivable = compReceivable[user];\r
uint newReceivable = add_(oldReceivable, accountReceivable);\r
\r
// Accounting: record the COMP debt for the user\r
compReceivable[user] = newReceivable;\r
\r
emit CompReceivableUpdated(user, oldReceivable, newReceivable);\r
\r
amountToSubtract = currentAccrual;\r
}\r
\r
if (amountToSubtract > 0) {\r
// Subtract the bad accrual amount from what they have accrued.\r
// Users will keep whatever they have correctly accrued.\r
compAccrued[user] = newAccrual = sub_(currentAccrual, amountToSubtract);\r
\r
emit CompAccruedAdjusted(user, currentAccrual, newAccrual);\r
}\r
}\r
\r
proposal65FixExecuted = true; // Makes it so that this function cannot be called again\r
}\r
\r
/**\r
* @notice Checks caller is admin, or this contract is becoming the new implementation\r
*/\r
function adminOrInitializing() internal view returns (bool) {\r
return msg.sender == admin || msg.sender == comptrollerImplementation;\r
}\r
\r
/*** Comp Distribution ***/\r
\r
/**\r
* @notice Set COMP speed for a single market\r
* @param cToken The market whose COMP speed to update\r
* @param supplySpeed New supply-side COMP speed for market\r
* @param borrowSpeed New borrow-side COMP speed for market\r
*/\r
function setCompSpeedInternal(CToken cToken, uint supplySpeed, uint borrowSpeed) internal {\r
Market storage market = markets[address(cToken)];\r
require(market.isListed, "comp market is not listed");\r
\r
if (compSupplySpeeds[address(cToken)] != supplySpeed) {\r
// Supply speed updated so let's update supply state to ensure that\r
// 1. COMP accrued properly for the old speed, and\r
// 2. COMP accrued at the new speed starts after this block.\r
updateCompSupplyIndex(address(cToken));\r
\r
// Update speed and emit event\r
compSupplySpeeds[address(cToken)] = supplySpeed;\r
emit CompSupplySpeedUpdated(cToken, supplySpeed);\r
}\r
\r
if (compBorrowSpeeds[address(cToken)] != borrowSpeed) {\r
// Borrow speed updated so let's update borrow state to ensure that\r
// 1. COMP accrued properly for the old speed, and\r
// 2. COMP accrued at the new speed starts after this block.\r
Exp memory borrowIndex = Exp({mantissa: cToken.borrowIndex()});\r
updateCompBorrowIndex(address(cToken), borrowIndex);\r
\r
// Update speed and emit event\r
compBorrowSpeeds[address(cToken)] = borrowSpeed;\r
emit CompBorrowSpeedUpdated(cToken, borrowSpeed);\r
}\r
}\r
\r
/**\r
* @notice Accrue COMP to the market by updating the supply index\r
* @param cToken The market whose supply index to update\r
* @dev Index is a cumulative sum of the COMP per cToken accrued.\r
*/\r
function updateCompSupplyIndex(address cToken) internal {\r
CompMarketState storage supplyState = compSupplyState[cToken];\r
uint supplySpeed = compSupplySpeeds[cToken];\r
uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits");\r
uint deltaBlocks = sub_(uint(blockNumber), uint(supplyState.block));\r
if (deltaBlocks > 0 && supplySpeed > 0) {\r
uint supplyTokens = CToken(cToken).totalSupply();\r
uint compAccrued = mul_(deltaBlocks, supplySpeed);\r
Double memory ratio = supplyTokens > 0 ? fraction(compAccrued, supplyTokens) : Double({mantissa: 0});\r
supplyState.index = safe224(add_(Double({mantissa: supplyState.index}), ratio).mantissa, "new index exceeds 224 bits");\r
supplyState.block = blockNumber;\r
} else if (deltaBlocks > 0) {\r
supplyState.block = blockNumber;\r
}\r
}\r
\r
/**\r
* @notice Accrue COMP to the market by updating the borrow index\r
* @param cToken The market whose borrow index to update\r
* @dev Index is a cumulative sum of the COMP per cToken accrued.\r
*/\r
function updateCompBorrowIndex(address cToken, Exp memory marketBorrowIndex) internal {\r
CompMarketState storage borrowState = compBorrowState[cToken];\r
uint borrowSpeed = compBorrowSpeeds[cToken];\r
uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits");\r
uint deltaBlocks = sub_(uint(blockNumber), uint(borrowState.block));\r
if (deltaBlocks > 0 && borrowSpeed > 0) {\r
uint borrowAmount = div_(CToken(cToken).totalBorrows(), marketBorrowIndex);\r
uint compAccrued = mul_(deltaBlocks, borrowSpeed);\r
Double memory ratio = borrowAmount > 0 ? fraction(compAccrued, borrowAmount) : Double({mantissa: 0});\r
borrowState.index = safe224(add_(Double({mantissa: borrowState.index}), ratio).mantissa, "new index exceeds 224 bits");\r
borrowState.block = blockNumber;\r
} else if (deltaBlocks > 0) {\r
borrowState.block = blockNumber;\r
}\r
}\r
\r
/**\r
* @notice Calculate COMP accrued by a supplier and possibly transfer it to them\r
* @param cToken The market in which the supplier is interacting\r
* @param supplier The address of the supplier to distribute COMP to\r
*/\r
function distributeSupplierComp(address cToken, address supplier) internal {\r
// TODO: Don't distribute supplier COMP if the user is not in the supplier market.\r
// This check should be as gas efficient as possible as distributeSupplierComp is called in many places.\r
// - We really don't want to call an external contract as that's quite expensive.\r
\r
CompMarketState storage supplyState = compSupplyState[cToken];\r
uint supplyIndex = supplyState.index;\r
uint supplierIndex = compSupplierIndex[cToken][supplier];\r
\r
// Update supplier's index to the current index since we are distributing accrued COMP\r
compSupplierIndex[cToken][supplier] = supplyIndex;\r
\r
if (supplierIndex == 0 && supplyIndex >= compInitialIndex) {\r
// Covers the case where users supplied tokens before the market's supply state index was set.\r
// Rewards the user with COMP accrued from the start of when supplier rewards were first\r
// set for the market.\r
supplierIndex = compInitialIndex;\r
}\r
\r
// Calculate change in the cumulative sum of the COMP per cToken accrued\r
Double memory deltaIndex = Double({mantissa: sub_(supplyIndex, supplierIndex)});\r
\r
uint supplierTokens = CToken(cToken).balanceOf(supplier);\r
\r
// Calculate COMP accrued: cTokenAmount * accruedPerCToken\r
uint supplierDelta = mul_(supplierTokens, deltaIndex);\r
\r
uint supplierAccrued = add_(compAccrued[supplier], supplierDelta);\r
compAccrued[supplier] = supplierAccrued;\r
\r
emit DistributedSupplierComp(CToken(cToken), supplier, supplierDelta, supplyIndex);\r
}\r
\r
/**\r
* @notice Calculate COMP accrued by a borrower and possibly transfer it to them\r
* @dev Borrowers will not begin to accrue until after the first interaction with the protocol.\r
* @param cToken The market in which the borrower is interacting\r
* @param borrower The address of the borrower to distribute COMP to\r
*/\r
function distributeBorrowerComp(address cToken, address borrower, Exp memory marketBorrowIndex) internal {\r
// TODO: Don't distribute supplier COMP if the user is not in the borrower market.\r
// This check should be as gas efficient as possible as distributeBorrowerComp is called in many places.\r
// - We really don't want to call an external contract as that's quite expensive.\r
\r
CompMarketState storage borrowState = compBorrowState[cToken];\r
uint borrowIndex = borrowState.index;\r
uint borrowerIndex = compBorrowerIndex[cToken][borrower];\r
\r
// Update borrowers's index to the current index since we are distributing accrued COMP\r
compBorrowerIndex[cToken][borrower] = borrowIndex;\r
\r
if (borrowerIndex == 0 && borrowIndex >= compInitialIndex) {\r
// Covers the case where users borrowed tokens before the market's borrow state index was set.\r
// Rewards the user with COMP accrued from the start of when borrower rewards were first\r
// set for the market.\r
borrowerIndex = compInitialIndex;\r
}\r
\r
// Calculate change in the cumulative sum of the COMP per borrowed unit accrued\r
Double memory deltaIndex = Double({mantissa: sub_(borrowIndex, borrowerIndex)});\r
\r
uint borrowerAmount = div_(CToken(cToken).borrowBalanceStored(borrower), marketBorrowIndex);\r
\r
// Calculate COMP accrued: cTokenAmount * accruedPerBorrowedUnit\r
uint borrowerDelta = mul_(borrowerAmount, deltaIndex);\r
\r
uint borrowerAccrued = add_(compAccrued[borrower], borrowerDelta);\r
compAccrued[borrower] = borrowerAccrued;\r
\r
emit DistributedBorrowerComp(CToken(cToken), borrower, borrowerDelta, borrowIndex);\r
}\r
\r
/**\r
* @notice Calculate additional accrued COMP for a contributor since last accrual\r
* @param contributor The address to calculate contributor rewards for\r
*/\r
function updateContributorRewards(address contributor) public {\r
uint compSpeed = compContributorSpeeds[contributor];\r
uint blockNumber = getBlockNumber();\r
uint deltaBlocks = sub_(blockNumber, lastContributorBlock[contributor]);\r
if (deltaBlocks > 0 && compSpeed > 0) {\r
uint newAccrued = mul_(deltaBlocks, compSpeed);\r
uint contributorAccrued = add_(compAccrued[contributor], newAccrued);\r
\r
compAccrued[contributor] = contributorAccrued;\r
lastContributorBlock[contributor] = blockNumber;\r
}\r
}\r
\r
/**\r
* @notice Claim all the comp accrued by holder in all markets\r
* @param holder The address to claim COMP for\r
*/\r
function claimComp(address holder) public {\r
return claimComp(holder, allMarkets);\r
}\r
\r
/**\r
* @notice Claim all the comp accrued by holder in the specified markets\r
* @param holder The address to claim COMP for\r
* @param cTokens The list of markets to claim COMP in\r
*/\r
function claimComp(address holder, CToken[] memory cTokens) public {\r
address[] memory holders = new address[](1);\r
holders[0] = holder;\r
claimComp(holders, cTokens, true, true);\r
}\r
\r
/**\r
* @notice Claim all comp accrued by the holders\r
* @param holders The addresses to claim COMP for\r
* @param cTokens The list of markets to claim COMP in\r
* @param borrowers Whether or not to claim COMP earned by borrowing\r
* @param suppliers Whether or not to claim COMP earned by supplying\r
*/\r
function claimComp(address[] memory holders, CToken[] memory cTokens, bool borrowers, bool suppliers) public {\r
for (uint i = 0; i < cTokens.length; i++) {\r
CToken cToken = cTokens[i];\r
require(markets[address(cToken)].isListed, "market must be listed");\r
if (borrowers == true) {\r
Exp memory borrowIndex = Exp({mantissa: cToken.borrowIndex()});\r
updateCompBorrowIndex(address(cToken), borrowIndex);\r
for (uint j = 0; j < holders.length; j++) {\r
distributeBorrowerComp(address(cToken), holders[j], borrowIndex);\r
}\r
}\r
if (suppliers == true) {\r
updateCompSupplyIndex(address(cToken));\r
for (uint j = 0; j < holders.length; j++) {\r
distributeSupplierComp(address(cToken), holders[j]);\r
}\r
}\r
}\r
for (uint j = 0; j < holders.length; j++) {\r
compAccrued[holders[j]] = grantCompInternal(holders[j], compAccrued[holders[j]]);\r
}\r
}\r
\r
/**\r
* @notice Transfer COMP to the user\r
* @dev Note: If there is not enough COMP, we do not perform the transfer all.\r
* @param user The address of the user to transfer COMP to\r
* @param amount The amount of COMP to (possibly) transfer\r
* @return The amount of COMP which was NOT transferred to the user\r
*/\r
function grantCompInternal(address user, uint amount) internal returns (uint) {\r
Comp comp = Comp(getCompAddress());\r
uint compRemaining = comp.balanceOf(address(this));\r
if (amount > 0 && amount <= compRemaining) {\r
comp.transfer(user, amount);\r
return 0;\r
}\r
return amount;\r
}\r
\r
/*** Comp Distribution Admin ***/\r
\r
/**\r
* @notice Transfer COMP to the recipient\r
* @dev Note: If there is not enough COMP, we do not perform the transfer all.\r
* @param recipient The address of the recipient to transfer COMP to\r
* @param amount The amount of COMP to (possibly) transfer\r
*/\r
function _grantComp(address recipient, uint amount) public {\r
require(adminOrInitializing(), "only admin can grant comp");\r
uint amountLeft = grantCompInternal(recipient, amount);\r
require(amountLeft == 0, "insufficient comp for grant");\r
emit CompGranted(recipient, amount);\r
}\r
\r
/**\r
* @notice Set COMP borrow and supply speeds for the specified markets.\r
* @param cTokens The markets whose COMP speed to update.\r
* @param supplySpeeds New supply-side COMP speed for the corresponding market.\r
* @param borrowSpeeds New borrow-side COMP speed for the corresponding market.\r
*/\r
function _setCompSpeeds(CToken[] memory cTokens, uint[] memory supplySpeeds, uint[] memory borrowSpeeds) public {\r
require(adminOrInitializing(), "only admin can set comp speed");\r
\r
uint numTokens = cTokens.length;\r
require(numTokens == supplySpeeds.length && numTokens == borrowSpeeds.length, "Comptroller::_setCompSpeeds invalid input");\r
\r
for (uint i = 0; i < numTokens; ++i) {\r
setCompSpeedInternal(cTokens[i], supplySpeeds[i], borrowSpeeds[i]);\r
}\r
}\r
\r
/**\r
* @notice Set COMP speed for a single contributor\r
* @param contributor The contributor whose COMP speed to update\r
* @param compSpeed New COMP speed for contributor\r
*/\r
function _setContributorCompSpeed(address contributor, uint compSpeed) public {\r
Submitted on: 2025-11-05 13:29:35
Comments
Log in to comment.
No comments yet.