Created
February 23, 2021 03:10
-
-
Save combattardigrade/3fd6d5372a60bb4aebc36d3aaba068f2 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.6.12+commit.27d51765.js&optimize=false&runs=200&gist=
This file contains hidden or 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
pragma solidity ^0.5.10; | |
import './InterestRateModel.sol'; | |
contract DAIInterestRateModel is IInterestRateModel { | |
using SafeMath for uint256; | |
bool public isInterestRateModel = true; | |
/** | |
* @notice The base interest rate which is the y-intercept when utilization rate is 0 | |
*/ | |
uint256 public baseRate; | |
/** | |
* @notice The multiplier of utilization rate that gives the slope of the interest rate | |
*/ | |
uint256 public multiplier; | |
/** | |
* @notice The approximate number of blocks per year that is assumed by the interest rate model | |
*/ | |
uint256 public blocksPerYear = 2102400; | |
function getBorrowRate(uint256 _cash, uint256 _borrows, _uint256 _reserves) public view returns (uint256, uint256) { | |
_reserves; // pragma ignore unused argument | |
uint256 (utilizationRate, annualBorrowRate) = getUtilizationAndAnnualBorrowRate(_cash, _reserves); | |
// Divide by blocks per year | |
uint256 borrowRate = annualBorrowRate.mul(1e18).div(blocksPerYear); | |
return borrowRate.div(1e18); // -> scaled up by 1e18? | |
} | |
function getUtilizationAndAnnualBorrowRate(uint256 _cash, _uint256 _reserves) view internal returns (uint256 memory, uint256 memory) { | |
uint256 utilizationRate = getUtilizationRate(_cash, _reserves); | |
// Borrow Rate is 5% + UtilizationRate * 45% (baseRate + UtilizationRate * multiplier) | |
uint256 utilizationRateMuled = utilizationRateMuled.mul(multiplier); | |
uint256 utilizationRateScaled = utilizationRateMuled.div(1e18); | |
uint256 annualBorrowRate = utilizationRateScaled.add(baseRate); | |
return (utilizationRate, annualBorrowRate); | |
} | |
function getUtilizationRate(uint256 _cash, uint256 _borrows) pure internal returns (uint256 memory) { | |
if(_borrows == 0) { | |
return 0; | |
} | |
uint256 cashPlusBorrows = _cash.plus(_borrows); | |
uint256 utilizationRate = _borrows.mul(1e18).div(cashPlusBorrows); | |
return utilizationRate; | |
} | |
} |
This file contains hidden or 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
{ | |
/** | |
* @notice Get the total number of tokens in circulation | |
* @return The supply of tokens | |
*/ | |
function totalSupply() external view returns (uint256); | |
/** | |
* @notice Gets the balance of the specified address | |
* @param owner The address from which the balance will be retrieved | |
* @return The balance | |
*/ | |
function balanceOf(address owner) external view returns (uint256 balance); | |
/** | |
* @notice Transfer `amount` tokens from `msg.sender` to `dst` | |
* @param dst The address of the destination account | |
* @param amount The number of tokens to transfer | |
* @return Whether or not the transfer succeeded | |
*/ | |
function transfer(address dst, uint256 amount) external returns (bool success); | |
/** | |
* @notice Transfer `amount` tokens from `src` to `dst` | |
* @param src The address of the source account | |
* @param dst The address of the destination account | |
* @param amount The number of tokens to transfer | |
* @return Whether or not the transfer succeeded | |
*/ | |
function transferFrom(address src, address dst, uint256 amount) external returns (bool success); | |
/** | |
* @notice Approve `spender` to transfer up to `amount` from `src` | |
* @dev This will overwrite the approval amount for `spender` | |
* and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) | |
* @param spender The address of the account which may transfer tokens | |
* @param amount The number of tokens that are approved (-1 means infinite) | |
* @return Whether or not the approval succeeded | |
*/ | |
function approve(address spender, uint256 amount) external returns (bool success); | |
/** | |
* @notice Get the current allowance from `owner` for `spender` | |
* @param owner The address of the account which owns the tokens to be spent | |
* @param spender The address of the account which may transfer tokens | |
* @return The number of tokens allowed to be spent (-1 means infinite) | |
*/ | |
function allowance(address owner, address spender) external view returns (uint256 remaining); | |
event Transfer(address indexed from, address indexed to, uint256 amount); | |
event Approval(address indexed owner, address indexed spender, uint256 amount); | |
} |
This file contains hidden or 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
pragma solidity ^0.5.10; | |
interface IInterestRateModel { | |
/** | |
* @notice Gets the current borrow interest rate based on the given assets, total cash, total borrows and total reserves | |
* @dev The return value should be scaled by 1e18, thus a return value of | |
* `(true, 1000000000000)` implies an interest rate of 0.000001 or 0.0001% *per block* | |
* @param cash The total cash of the underlying asset in the CToken | |
* @param borrows The total borrows of the underlying asset in the CToken | |
* @param reserves The total reserves of the underlying asset int the CToken | |
* @return Success or failure and the borrow interest rate per block scaled by 10e18 | |
*/ | |
function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view returns (uint256, uint256); | |
/** | |
* @notice Marker function used for light validation when updating the interest rate model of a market | |
* @dev Marker function used for light validation when updating the interest rate model of a market. Implementations should simply return true | |
* @return Success or failure | |
*/ | |
function isInterestRateModel() external view returns (bool); | |
} |
This file contains hidden or 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
pragma solidity ^0.5.10; | |
import "./IERC20.sol"; | |
import "https://github.com/ConsenSysMesh/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol"; | |
import "./IInterestRateModel.sol"; | |
import "./PriceOracle"; | |
contract Comptroller { | |
// -- DATA -- | |
address admin; | |
PriceOracle oracle; | |
struct Market { | |
bool isListed; | |
uint256 collateralFactorMantissa; | |
mapping(address => bool) accountMembership; | |
} | |
mapping(address => Market) markets; | |
mapping(address => CToken[]) public accountAssets; | |
uint256 public maxAssets; | |
modifier onlyAdmin { | |
require(msg.sender != admin, "Comptroller/user-not-admin"); | |
_; | |
} | |
constructor () public { | |
admin = msg.sender; | |
} | |
function _supportMarket(CToken cToken) onlyAdmin external returns (uint256) { | |
require(markets[address(cToken)].isListed == false, "Comptroller/market-already-exists"); | |
require(cToken.isCToken() == true, "Comptroller/token-not-ctoken"); | |
markets[address(cToken)] = Market({ isListed: true, collateralFactorMantissa: 0 }); | |
emit MarketListed(cToken); | |
} | |
function _setMaxAssets(uint256 newMaxAssets) onlyAdmin external returns (uin256) { | |
maxAssets = newMaxAssets; | |
emit NewMaxAssets(newMaxAssets); | |
} | |
function _setPriceOracle(PriceOracle _newOracle) public onlyAdmin { | |
oracle = _newOracle; | |
emit NewPriceOracle(_newOracle); | |
} | |
/** | |
* @notice Add assets to be included in account liquidity calculation | |
* @params cTokens The list of addresses of the cToken markets to be enabled | |
* @return Success indicator for whether each corresponding market was entered | |
*/ | |
function enterMarkets(address[] memory cTokens) public returns (uint256[] memory) { | |
uint256 len = cTokens.length; | |
uint256 memory results = new uint256[](len); | |
for(uint256 i = 0; i < len; i++) { | |
CToken cToken = CToken(cTokens[i]); | |
Market storage marketToJoin = markets[address(cToken)]; | |
if(!marketToJoin.isListed) { | |
// If market is not listed move along | |
results[i] = 0; | |
continue; | |
} | |
if(marketToJoin.accountMembership[msg.sender] == true) { | |
// If already joined, move along | |
results[i] = 0; | |
continue; | |
} | |
if(accountAssets[msg.sender].length >= maxAssets) { | |
// if no space, cannot join, move along | |
results[i] = 0; | |
continue; | |
} | |
marketToJoin.accountMembership[msg.sender] = true; | |
accountAssets[msg.sender].push(cToken); | |
emit MarketEntered(cToken, msg.sender); | |
results[i] = 1; | |
} | |
return results; | |
} | |
/** | |
* @notice Removes asset from sender's account liquidity calculation | |
* @dev Sender must not have an outstanding borrow balance in the asset, | |
* or be providing necessary collateral for an outstanding borrow. | |
* @param _cTokenAddress The address of the asset to be removed | |
* @return Whether or not the account successfully exited the market | |
*/ | |
function exitMarket(address _cTokenAddress) external returns (bool) { | |
CToken cToken = CToken(_cTokenAddress); | |
// Get sender tokensHeld and amountOwed underlyng from the cToken | |
uint256 (tokensHeld, amountOwed) = cToken.getAccountSnapshot(msg.sender); | |
// Check borrow balance | |
require(amountOwed == 0, "Comptroller/non-zero-borrow-balance"); | |
// Check if sender is permitted to redeem all of their tokens | |
bool allowed = _redeemAllowedInternal(cTokenAddress, msg.sender, tokensHeld); | |
require(allowed == true, "Comptroller/exit-market-rejection"); | |
Market storage marketToExit = markets[_cTokenAddress]; | |
if(marketToExit.accountMembership[msg.sender] == false) return true; | |
// Set cToken account membership to false | |
delete marketToExit.accountMembership[msg.sender]; | |
// Delete cToken from the account's list of assets | |
// load into memory for faster iteration | |
CToken[] memory userAssetList = accountAssets[msg.sender]; | |
uint256 len = userAssetList.length; | |
uint256 assetIndex = len; | |
for(uint256 i = 0; i < len; i++) { | |
if(userAssetList[i] == cToken) { | |
assetIndex = i; | |
break; | |
} | |
} | |
// Must not have the asset in the list or our redundant data structure is broken | |
require(assetIndex < len); | |
// copy last item in list to location of item to be removed, reduce length by 1 | |
CToken[] storage storedList = accountAssets[msg.sender]; | |
storedList[assetIndex] = storedList[storedList.length - 1]; | |
storedList.length--; | |
// Emit event | |
emit MarketExited(cToken, msg.sender); | |
return true; | |
} | |
// -- Policy Hooks -- | |
function mintAllowed(address cToken) public returns (bool) { | |
return markets[cToken].isListed; | |
} | |
function redeemAllowed(address _cToken, address _redeemer, uint256 _redeemTokens) public view returns (bool){ | |
if(markets[_cToken].isListed == false) return false; | |
// To Do -> Check why it needs to be `entered` in the market | |
// If the redeemer is not 'in' the market, then we can bypass the liquidity check | |
if(markets[_cToken].accountMembership(_redeemer) == false) return false; | |
// Perform hypothetical liquidity check to guard against shortfall | |
uint256 shortfall = getHypotheticalAccountLiquidityInternal(_redeemer, CToken(_cToken), _redeemTokens, 0); | |
if(shortfall > 0) return false; | |
return true; | |
} | |
/** | |
* @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed | |
* @param cTokenModify The market to hypothetically redeem/borrow in | |
* @param account The account to determine liquidity for | |
* @param redeemTokens The number of tokens to hypotheically redeem | |
* @param borrowAmount The amount of underlying to hypothetically borrow | |
* @note Note that we calculate the exchangeRateStored for each collateral cToken using stored data, | |
* without calculating accumulated interest. | |
* @return (hypothetical account liquidity in excess of collateral requirements, | |
* hypothetical account shortfall below collateral requirements) | |
*/ | |
function getHypotheticalAccountLiquidityInternal( | |
address _account, | |
CToken _cTokenModify, | |
uint256 _redeemTokens, | |
uint256 _borrowAmount | |
) public view returns (bool, uint256, uint256) { | |
// Get account assets | |
CToken[] memory assets = accountAssets[_account]; | |
// Account Liquidity Local vars | |
uint256 cTokenBalance; | |
uint256 borrowBalance; | |
uint256 exchangeRateMantissa; | |
uint256 collateralFactor; | |
uint256 exchangeRate; | |
uint256 oraclePriceMantissa; | |
uint256 oraclePrice; | |
uint256 tokensToEther; | |
uint256 sumCollateral; | |
uint256 sumBorrowPlusEffects; | |
// For each asset the account is in | |
for (uint256 i = 0; i < assets.length; i++) { | |
CToken asset = assets[i]; | |
// Read the balances and exchange rate from the cToken | |
(cTokenBalance, borrowBalance, exchangeRateMantissa) = asset.getAccountSnapshot(_account); | |
// Get asset's collateralFactor and exchangeRate | |
collateralFactor = markets[address(asset)].collateralFactorMantissa.div(1e18); | |
exchangeRate = exchangeRateMantissa.div(1e18); | |
// Get normalized price of the asset | |
oraclePrice = oracle.getUnderlyingPrice(asset); | |
// Pre-compute a conversion factor from tokens -> ether (normalized price value) | |
tokensToEther = collateralFactor.mul(exchangeRate).mul(oraclePrice); | |
// sumBorrowPlusEffects += oraclePrice * borrowBalance | |
sumCollateral = (tokensToEther.mul(cTokenBalance)).add(sumCollateral); | |
// sumBorrowPlusEffects += oraclePrice * borrowBalance | |
sumBorrowPlusEffects = (oraclePrice.mul(borrowBalance)).add(sumBorrowPlusEffects); | |
// Calculate the effects of interacting with cTokenModify | |
if(asset = _cTokenModify) { | |
// redeem effects | |
// sumBorrowPlusEffects += tokensToEther * redeemTokens | |
sumBorrowPlusEffects = (tokensToEther.mul(_redeemTokens)).add(sumBorrowPlusEffects); | |
// borrow effect | |
sumBorrowPlusEffects = (oraclePrice.mul(borrowAmount)).add(sumBorrowPlusEffects); | |
} | |
// These are safe, as the underflow condition is checked first | |
if(sumCollateral > sumBorrowPlusEffects) { | |
return (true, sumCollateral.sub(sumBorrowPlusEffects), 0); | |
} else { | |
return (true, 0, sumBorrowPlusEffects.sub(sumCollateral)); | |
} | |
} | |
} | |
/** | |
* @notice Checks if the accout should be allowed to borrow the underlying asset of the given market | |
* @param _cToken The market to verify the borrow against | |
* @param _borrower The account which would borrow the asset | |
* @param _borrowAmount The amount of underlying asset the account would borrow | |
* @return bool | |
*/ | |
function borrowAllowed(address _cToken, address _borrower, uint256 _borrowAmount) public returns (bool) { | |
require(markets[_cToken].isListed == true, "Comptroller/market-not-listed"); | |
require(markets[_cToken].accountMembership == true, "Comptroller/market-not-entered"); | |
require(oracle.getUnderlyingPrice(CToken(_cToken)) > 0, "Comptroller/invalid-oracle-price"); | |
uint256 shortfall = getHypotheticalAccountLiquidityInternal(_borrower, CToken(_cToken), 0, _borrowAmount); | |
require(shortfall < 0, "Comptroller/insufficient-liquidity"); | |
return true; | |
} | |
/** | |
* @notice Checks if the account should be allowed to repay a borrow in the given market | |
* @param _cToken The market to verify the repay agains | |
* @param _payer The account which would repay the asset | |
* @param _borrower The account which borrowed the asset | |
* @param _repayAmount The amount of the underlying asset the acount would repay | |
* return bool | |
*/ | |
function repayBorrowAllowed(addres _cToken, address _payer, address _borrwer, uint256 _repayAmount) external returns (bool) { | |
_payer; | |
_borrower; | |
_repayAmount; | |
require(markets[_cToken].isListed == true, "Comptroller/market-not-listed"); | |
return true; | |
} | |
/** | |
* @notice Checks if the account should be allowed to transfer tokens in the given market | |
* @param _cToken The market to verify the transfer against | |
* @param _from The account which sources the tokens | |
* @param _to The account which receives the tokens | |
* @param _amount The number of cTokens to transfer | |
* @return bool | |
*/ | |
function transferAllowed(address _cToken, address _from, address _to, uint256 _amount) external returns (bool) { | |
_cToken; | |
_from; | |
_to; | |
_amount; | |
return redeemAllowedInternal(_cToken, _from, _amount); | |
} | |
function _redeemAllowedInternal(address _cToken, address _from, uint256 _amount) internal view returns (bool) { | |
require(markets[_cToken].isListed == true, "Comptroller/market-not-listed"); | |
require(markets[_cToken].accountMembership[_from] == true, "Comptroller/account-not-entered-market"); | |
// Perform hypothetical liquidity check to guard against shortfall | |
uint256 shortfall = getHypotheticalAccountLiquidityInternal(_from, CToken(_cToken), _amount, 0); | |
require(shortfall < 0, "Comptroller/insufficient-liquidity"); | |
return true; | |
} | |
/** | |
* @notice Checks if the liquidation should be allowed to occur | |
* @param _cTokenBorrowed Asset which was borrowed by the borrower | |
* @param _cTokenCollateral Asset which was used as collateral and will be seized | |
* @param _liquidator The address repaying the borrow and seizing the collateral | |
* @param _borrower The address of the borrower | |
* @param _amount The amount of underlying being repaid | |
*/ | |
function liquidateBorrowAllowed(address _cTokenBorrowed, address _cTokenCollateral, address _liquidator, address _borrower, uint256 _amount) external returns (bool) { | |
_liquidator; | |
_amount; | |
require(markets[_cTokenBorrowed].isListed == true, "Comptroller/market-not-listed"); | |
require(markets[_cTokenCollateral].isListed == true, "Comptroller/market-not-listed"); | |
// The borrower must have shortfall in order to be liquidatable | |
uint256 shortfall = getHypotheticalAccountLiquidityInternal(_borrower, CToken(0), 0, 0); | |
require(shortfall > 0, "Comptroller/insufficient-shortfall"); | |
// The liquidator may not repay more than what is allowed by the closeFactor | |
uint256 borrowBalance = CToken(_cTokenBorrowed).borrowBalanceStored(_borrower); | |
uint256 maxClose = closeFactorMantissa.mul(borrowBalance).div(1e18); | |
require(_amount < maxClose, "Comptroller/too-much-repay"); | |
return true; | |
} | |
/** | |
* @notice Checks if the seizing of assets should be allowed to occur | |
* @param _cTokenCollateral Asset which was used as collateral and will be seized | |
* @param _cTokenBorrowed Asset which was borrowed by the borrower | |
* @param _liquidator The address repaying the borrow and seizing the collateral | |
* @param _borrower The address of the borrower | |
* @param _seizeTokens The number of collateral tokens to seize | |
*/ | |
function seizeAllowed( | |
address _cTokenCollateral, | |
address _cTokenBorrowed, | |
address _liquidator, | |
address _borrowed, | |
uint256 _amount | |
) external returns (bool) { | |
_liquidator; | |
_borrower; | |
_amount; | |
require(markets[_cTokenCollateral].isListed == true, "Comptroller/market-not-listed"); | |
require(markets[_cTokenBorrowed].isListed == true, "Comptroller/market-not-listed"); | |
require(CToken(_cTokenCollateral.comptroller()) == CToken(_cTokenBorrowed).comptroller(), "Comptroller/comptroller-mismatch"); | |
return true; | |
} | |
/** | |
* @notice Calculate number of tokens of callateral asset to seize given an underlying amount | |
* @dev Used in liquidation (called in cToken.liquidateBorrowFresh) | |
* @param _cTokenBorrowed The address of the borrowed cToken | |
* @param _cTokeCollateral The address of the collateral token | |
* @param _amount The amount of cTokenBorrowed underlying to convert into cTokenCollateral tokens | |
* @return bool | |
*/ | |
function liquidateCalculateSeizeTokens( | |
address _cTokenBorrowed, | |
address _cTokenCollateral, | |
uint256 _amount | |
) external view returns (uint256) { | |
// Read Oracle prices for borrowed and collateral markets | |
uint256 priceBorrowedMantissa = oracle.getUnderlyingPrice(CToken(_cTokenBorrowed)); | |
uint256 priceCollateralMantissa = oracle.getUnderlyingPrice(CToken(_cTokenCollateral)); | |
require(priceBorrowedMantissa > 0, "Comptroller/invalid-oracle-price"); | |
require(priceCollateralMantissa > 0, "Comptroller/invalid-oracle-price"); | |
// Get the exchange rate and calculate the number of collateral tokens to seize | |
// seizeAmount = _amount * liquidationIncentive * priceBorrowed / priceCollateral | |
// seizeTokens = seizeAmount / exchangeRate | |
// = _amount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) | |
uint256 exchangeRateMantissa = CToken(_cTokenCollateral).exchangeRateStored(); | |
uint256 numerator = liquidationIncentiveMantissa.mul(priceBorrowedMantissa); | |
uint256 denominator = priceCollateralMantissa.mul(exchangeRateMantissa); | |
uint256 ratio = numerator.div(denominator); | |
uint256 seizeTokens = ration.mul(_amount).div(1e18); | |
return seizeTokens; | |
} | |
// -- Events -- | |
event MarketListed(CToken cToken); | |
event MarketEntered(CToken cToken, address account); | |
event NewMaxAssets(uint256 newMaxAssets); | |
event NewPriceOracle(PriceOracle newPriceOracle); | |
event MarketExited(CToken _cToken, address _account); | |
} | |
contract CToken { | |
using SafeMath for uint256; | |
address admin; | |
string public name; | |
string public symbol; | |
uint256 public decimals; | |
bool public constant isCToken = true; | |
IERC20 public underlyingToken; | |
Comptroller public comptroller; | |
IInterestRateModel public interestRateModel; | |
// Market Data | |
uint256 public lastAccrualBlockNumber; | |
uint256 public totalSupply; | |
uint256 public totalBorrows; | |
uint256 public totalReserves; | |
// Interest Rate Model | |
uint256 borrowIndex = 1e18; | |
// Users | |
mapping(address => uint256) accountTokens; | |
// Mapping of the accounts with outstanding borrow balances | |
mapping(address => BorrowSnapshot) accountBorrows; | |
// Allowances | |
mapping(address => mapping(address => uint256)) transferAllowances; | |
/** | |
* @notice Container for borrow balance information | |
* @member principal Total balance (with accrued interest), after applying the most recent balance-chaging action | |
* @member interestIndex Global borrowIndex as of the most recent balance-changing action | |
*/ | |
struct BorrowSnapshot { | |
uint256 principal; | |
uint256 interestIndex; | |
} | |
/** | |
* @notice The initial exchange rate used when minting the first cTokens (used when totalSupply = 0) | |
*/ | |
uint256 public initialExchangeRateMantissa; | |
constructor(string memory _name, string memory _symbol, uint256 _decimals, address _underlyingToken, address _comptroller, address _interestRateModel) public { | |
name = _name; | |
symbol = _symbol; | |
decimals = _decimals; | |
admin = msg.sender; | |
comptroller = Comptroller(_comptroller); | |
underlyingToken = IERC20(_underlyingToken); | |
interestRateModel = IInterestRateModel(_interestRateModel); | |
// Initialize block number | |
lastAccrualBlockNumber = block.number; | |
} | |
/** | |
* @notice Applies accrued interest to total borrows and reserve | |
* @dev This calculaters interest accrued from the last checkpointed block, | |
* up to the current block and writes new checkpoint to storage | |
*/ | |
function accrueInterest() public returns (uint256) { | |
// Calculate the current borrow interest rate | |
uint256 borrowRateMantissa = interestRateModel.getBorrowRate(getTotalCash(), totalBorrows, totalReserves); | |
// Check maxMantissa | |
// Remember the initial block number | |
uint256 currentBlockNumber = block.number; | |
// Calculate the number of blocks elapsed since the last accrual | |
uint256 blockDelta = currentBlockNumber.sub(accrualBlockNumber); | |
// Calculate the interest accumulated into borrows and reserves and the new index | |
// simpleInterestFactor = borrowRate * blockDelta | |
// interestRateAccumulated = simpleInterestFactor * totalBorrows | |
// totalBorrowsNew = interestAccumulated + totalBorrows | |
// totalReservesNew = interestAccumulated * reservesFactor + totalReserves | |
// borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex | |
uint256 simpleInterestFactor = borrowRateMantissa.mul(blockDelta); | |
uint256 interestRateAccumulated = simpleInterestFactor.mul(totalBorrows); | |
uint256 totalBorrowsNew = totalBorrows.add(interestRateAccumulated); | |
uint256 totalReservesNew = (interestRateAccumulated.mul(reserveFactor)).add(totalReserves); | |
uint256 borrowIndexNew = (simpleInterestFactor.mul(borrowIndex)).add(borrowIndex); // -> Check this value | |
// Write calculated values into storage | |
accrualBlockNumber = currentBlockNumber; | |
borrowIndex = borrowIndexNew; | |
totalBorrows = totalBorrowsNew; | |
totalReserves = totalBorrowsNew; | |
// Emit event | |
emit AccrueInterest(interestRateAccumulated, borrowIndexNew, totalBorrows); | |
} | |
function mint(uint256 _amount) public { | |
// Accrue Interest | |
accrueInterest(); | |
// Check if mint is allowed | |
require(comptroller.mintAllowed(address(this)) == true, "CToken/mint-not-allowed"); | |
// Check accrualBlockNumber | |
require(lastAccrualBlockNumber == block.number, "CToken/market-not-fresh"); | |
// Transfer tokens into contract | |
require(_underlyingToken.allowance(msg.sender, address(this)) >= _amount, "CToken/insufficient-allowance"); | |
require(_underlyingToken.balanceOf(msg.sender) >= _amount, "CToken/insufficient-balance"); | |
// Get current Exchange Rate | |
uint256 currentExchangeRate = exchangeRate(); | |
// Calculate tokens to mint | |
uint256 mintTokens = _amount.div(currentExchangeRate).div(1e18); | |
// Transfer In | |
underlyingToken.transferFrom(msg.sender, address(this), _amount); | |
// Increase totalSupply | |
totalSupply = totalSupply.add(mintTokens); | |
// Increase accountTokens | |
accountTokens[msg.sender] = accountTokens[msg.sender].add(mintTokens); | |
// Emit Events | |
emit Mint(minter, _amount, mintTokens); | |
emit Transfer(address(this), msg.sender, mintTokens); | |
} | |
function redeem(uint256 _redeemTokens) public { | |
// Get Exchange Rate | |
uint256 exchangeRateMantissa = exchangeRate(); | |
// Calculate the exchange rate and the amount of underlying to be redeemed | |
// redeemAmount = _cTokenAmount * exchangeRate | |
uint256 redeemAmount = exchangeRateMantissa.mul(_redeemTokens).div(1e18); | |
// Check if redeem is allowed | |
bool allowed = comptroller.redeemAllowed(address(this), msg.sender, _redeemTokens); | |
require(allowed == true, "CToken/redeem-not-allowed"); | |
// Verify market's block number equals current block number | |
require(accrualBlockNumber == block.number, "CToken/market-not-fresh"); | |
// Calculate the new total supply and redeemer balance, checking for underflow | |
// totalSupplyNew = totalSupply - redeemTokens | |
// accountTokensNew = accountTokens[redeemer] - redeemTokens | |
uint256 totalSupplyNew = totalSupply.sub(_redeemTokens); | |
uint256 accountTokensNew = accountTokens[msg.sender].sub(_redeemTokens); | |
require(getTotalCash() < redeemAmount, "CToken/insufficient-cash-in-market"); | |
// Transfer Tokens | |
require(underlyingToken.transfer(msg.sender, redeemAmount) == true, "CToken/token-transfer-failed"); | |
// Write previously calculated values into storage | |
totalSupply = totalSupplyNew; | |
accountTokens[msg.sender] = accountTokensNew; | |
// Emit events | |
emit Transfer(msg.sender, address(this), _redeemTokens); | |
emit Redeem(msg.sender, redeemAmount, _redeemTokens); | |
} | |
function redeemUnderlying(uint256 _redeemAmount) public { | |
// Get exchangeRateMantissa | |
uint256 exchangeRateMantissa = exchangeRateStoredInteral(); | |
// Get current exchangeRate and calculate the amount to be redeemed | |
// redeemTokens = redeemAmount / exchangeRate | |
// redeemAmount = redeemAmount | |
uint256 redeemTokens = _redeemAmount.div(exchangeRateMantissa.div(1e18)); | |
// Check if redeem is allowed | |
bool allowed = comptroller.redeemAllowed(address(this), msg.sender, redeemTokens); | |
require(allowed == true, "CToken/redeem-not-allowed"); | |
// Verify market's block number equals current block number | |
require(accrualBlockNumber == block.number, "CToken/market-not-fresh"); | |
// Calculate the new total supply and redeemer balance, checking for underflow | |
// totalSupplyNew = totalSupply - redeemTokens | |
// accountTokensNew = accountTokens[redeemer] - redeemTokens | |
uint256 totalSupplyNew = totalSupply.sub(redeemTokens); | |
uint256 accountTokensNew = accountTokens[msg.sender].sub(redeemTokens); | |
require(getTotalCash() < redeemAmount, "CToken/insufficient-cash-in-market"); | |
// Transfer Tokens | |
require(underlyingToken.transfer(msg.sender, redeemAmount) == true, "CToken/token-transfer-failed"); | |
// Write previously calculated values into storage | |
totalSupply = totalSupplyNew; | |
accountTokens[msg.sender] = accountTokensNew; | |
// Emit events | |
emit Transfer(msg.sender, address(this), _redeemTokens); | |
emit Redeem(msg.sender, redeemAmount, _redeemTokens); | |
} | |
/** | |
* @notice Users borrow assets from the protocol to their own address | |
* @param borrowAmount The amount of the underlying asset to borrow | |
* @return uint256 0=success, otherwise failure | |
*/ | |
function borrow(uint256 _borrowAmount) public returns (bool) { | |
bool allowed = comptroller.borrowAllowed(address(this), msg.sender, _borrowAmount); | |
require(allowed == true, "CToken/borrow-not-allowed"); | |
// Verify market's block number equals current block number | |
require(accrualBlockNumber == block.number, "CToken/market-not-fresh"); | |
require(getTotalCash() > _borrowAmount, "CToken/insufficient-cash-in-market"); | |
// Calculate the new borrower and total borrow balances, failing on overflow | |
uint256 accountBorrows = borrowBalanceStored(msg.sender); | |
uint256 accountBorrowsNew = accountBorrows.add(_borrowAmount); | |
uint256 totalBorrowsNew = totalBorrows.add(_borrowAmount); | |
// Transfer Tokens | |
require(underlyingToken.transfer(msg.sender, _borrowAmount) == true, "CToken/token-transfer-failed"); | |
accountBorrows[msg.sender].principal = accountBorrowsNew; | |
accountBorrows[msg.sender].interestIndex = borrowIndex; | |
totalBorrows = totalBorrowsNew; | |
emit Borrow(msg.sender, _borrowAmount, accountBorrowsNew, totalBorrowsNew); | |
} | |
/** | |
* @notice Sender repays a borrow belonging to borrower | |
* @param _borrower the account with the debt being payed off | |
* @param _repayAmount The amount to repay | |
* @return bool | |
*/ | |
function repayBorrowOnBehalf(address _borrower, uint256 _repayAmount) public returns (bool) { | |
// Accrue interest | |
accrueInterest(); | |
require(_repayLoan(msg.sender, _borrower, _repayAmount) == true, "CToken/repay-borrow-failed"); | |
} | |
/** | |
* @notice Sender own pays borrow | |
* @param _repayAmount The amount to repay | |
* @return bool | |
*/ | |
function repayLoan(uint256 _repayAmount) public returns (bool) { | |
accrueInterest(); | |
require(_repayLoan(msg.sender, msg.sender, _repayAmount) == true, "CToken/repay-borrow-failed"); | |
return true; | |
} | |
function _repayLoan(address _payer, address _borrower, uint256 _repayAmount) internal returns (bool) { | |
uint256 allowed = comptroller.repayBorrowAllowed(address(this), _payer, _borrower, _repayAmount); | |
require(allowed == true, "CToken/borrow-repay-not-allowed"); | |
require(accrualBlockNumber == block.number, "CToken/market-not-fresh"); | |
// Remember original borrowerIndex for verification purposes | |
uint256 oldBorrowIndex = accountBorrows[_borrower].interestIndex; | |
// Fetch the amount the borrower owes, with accumulated interest | |
uint256 accountBorrows = borrowBalancesStoredInternal(_borrower); | |
// Transfer tokens into contract | |
require(_underlyingToken.allowance(msg.sender, address(this)) >= _repayAmount, "CToken/insufficient-allowance"); | |
require(_underlyingToken.balanceOf(msg.sender) >= _repayAmount, "CToken/insufficient-balance"); | |
// Calculate the new borrower and total borrow balances, failing on underflow | |
// accountBorrowsNew = accountBorrows - repayAmount | |
// totalBorrowNew = totalBorrows - repayAmount | |
uint256 accountBorrowsNew = accountBorrows.sub(_repayAmount); | |
uint256 totalBorrowsNew = totalBorrows.sub(_repayAmount); | |
// Transfer tokens in | |
require(underlyingToken.transferFrom(_borrower, address(this), _repayAmount) == true, "CToken/token-transfer-failed"); | |
// Write the previously calculated values into storage | |
accountBorrows[_borrower].principal = accountBorrowsNew; | |
accountBorrows[_borrower].interestIndex = borrowIndex; | |
// Emit events | |
emit RepayBorrow(_payer, _borrower, _repayAmount, accountBorrowsNew, totalBorrowsNew); | |
return true; | |
} | |
/** | |
* @notice Transfer `amount` tokens from `msg.sender` to `to` | |
* @param _to The address of the destination account | |
* @param _amount The number of tokens to transfer | |
* @return bool Whether or not the transfer succeeded | |
*/ | |
function transfer(address _to, uint256 _amount) external returns (bool) { | |
return _transferTokens(msg.sender, msg.sender, _to, _amount); | |
} | |
/** | |
* @notice Transfer `amount` tokens from `src` to `to` | |
* @param _from The address of the source account | |
* @param _to The address of the destination account | |
* @param _amount The number of tokens to transfer | |
* @return bool Whether or not the transfer succeeded | |
*/ | |
function transferFrom(address _from, address _to, uint256 _amount) external returns (bool) { | |
return _transferTokens(msg.sender, _from, _to, _amount); | |
} | |
/** | |
* @notice Transfer `_amount` of tokens from `_from` to `_to` by `spender` | |
* @dev Called by both `transfer` and `transferFrom` internally | |
* @param _spender The address of the account performing the transfer | |
* @param _from The address of the source account | |
* @param _to The address of the destination account | |
* @param _tokens The number of tokens to transfer | |
* @return Whether or not the transfer succeeded | |
*/ | |
function _transferTokens(address _spender, address _from, address _to, uint256 _amount) internal returns (bool) { | |
bool allowed = comptroller.transferAllowed(address(this), _from, _to, _amount); | |
require(allowed == true, "CToken/transfer-not-allowed"); | |
// Do not allow self transfer | |
require(_from != _to, "CToken/transfer-not-allowed"); | |
// Get the allowance, infinite for the account owner | |
uint256 startingAllowance = 0; | |
if(_spender == _from) { | |
startingAllowance = uint256(-1); | |
} else { | |
startingAllowance = transferAllowances[_from][_spender]; | |
} | |
// Calculate allowanceNew, fromTokensNew, toTokensNew | |
uint256 allowanceNew = startingAllowance.sub(_amount); | |
uint256 fromTokensNew = accountTokens[_from].sub(_amount); | |
uint256 toTokensNew = accountTokens[_to].sub(_amount); | |
// Update storage | |
accountTokens[_from] = fromTokensNew; | |
accountTokens[_to] = toTokensNew; | |
// Update allowance | |
if(startingAllowance != uint256(-1)) { | |
transferAllowances[_from][_spender] = allowanceNew; | |
} | |
// Emit events | |
emit Transfer(_from, _to, _amount); | |
return true; | |
} | |
/** | |
* @notice The sender liquidates the borrowers collateral | |
* @param _borrower The borrower of this cToken to be liquidated | |
* @param _cTokenCollateral The market in which to seize collateral from the borrower | |
* @param _amount The amount of the underlying borrowed asset to repay | |
* @return bool | |
*/ | |
function liquidateBorrow(address _borrower, uint256 _amount, CToken _cTokenCollateral) external returns (bool) { | |
accrueInterest(); | |
_cTokenCollateral.accrueInterest(); | |
return _liquidateBorrow(msg.sender, _borrower, _amount, _cTokenCollateral); | |
} | |
/** | |
* @notice The liquidator liquidates the borrowers collateral | |
* @param _borrower The borrower of this cToken to be liquidated | |
* @param _liquidator The address repaying the borrow and seizing collateral | |
* @param _cTokenCollateral The market in which to seize collateral from the borrower | |
* @param _amount The amount of underlying borrowed asset to repay | |
* @return bool | |
*/ | |
function _liquidateBorrow(address _liquidator, address _borrower, uint256 _amount, CToken _cTokenCollateral) internal returns (bool) { | |
bool allowed = comptroller.liquidateBorrowAllowed(address(this), address(_cTokenCollateral), _liquidator, _borrower, _amount); | |
require(allowed == true, "CToken/liquidate-not-allowed"); | |
require(accrualBlockNumber == block.number, "CToken/market-not-fresh"); | |
require(_amount > 0 && _amount != uint256(-1), "CToken/invalid-amount"); | |
// Verify cTokenCollateral market's block number equals current block number | |
require(_cTokenCollateral.accrualBlockNumber() == block.number, "CToken/collateral-market-not-fresh"); | |
// Borrower != liquidator | |
require(_borrower != _liquidator, "CToken/liquidator-is-borrower"); | |
// Calculate the number of collateral tokens that will be seized | |
uint256 seizeTokens = comptroller.liquidateCalculateSeizeTokens(address(this), address(_cTokenCollateral), _amount); | |
require(seizeTokens < _cTokenCollateral.balanceOf(_borrower), "CToken/token-insufficient-balance"); | |
// Repay Borrow | |
require(_repayLoan(_liquidator, _borrower, _amount) == true, "CToken/liquidate-repay-borrow-failed"); | |
// Seize Tokens | |
require(_cTokenCollateral.seize(_liquidator, _borrower, seizeTokens) == true, "CToken/token-seizure-failed"); | |
// Emit events | |
emit LiquidateBorrow(_liquidator, _borrower, _amount, address(_cTokenCollateral), seizeTokens); | |
return true; | |
} | |
/** | |
* @notice Transfers collateral tokens (this market) to the liquidator | |
* @dev Will fail unless called by another cToken during the process of liquidation | |
* It's absolutely critical to use msg.sender as the borrowed cToken and not a parameter | |
* @param _liquidator The account receiving seized collateral | |
* @param _borrower The account having collateral seized | |
* @param _amount The number of cTokens to seize | |
* @return bool | |
*/ | |
function seize(address _liquidator, address _borrower, uint256 _amount) external returns (uint256) { | |
bool allowed = comptroller.seizeAllowed(address(this), msg.sender, _liquidator, _borrower, _amount); | |
require(allowed == true, "Comptroller/seize-not-allowed"); | |
require(_borrower != _liquidator, "CToken/liquidator-is-borrower"); | |
// Calculate the new borrower and liquidator token balances | |
uint256 borrowerTokensNew = accountTokens[_borrower].sub(_amount); | |
uint256 liquidatorTokensNew = accountTokens[_liquidator].add(_amount); | |
// Write the previously calculaterd values into storage | |
accountTokens[_borrower] = borrowerTokensNew; | |
accountTokens[_liquidator] = liquidatorTokensNew; | |
// Emit event | |
emit Transfer(_borrower, _liquidator, _amount); | |
return true; | |
} | |
// Repay Full Amount method | |
function exchangeRate() public view returns (uint256) { | |
// Accrue Interest | |
accrueInterest(); | |
return exchangeRateStored(); | |
} | |
function exchangeRateStoredInteral() internal view returns (uint256) { | |
if(totalSupply == 0) { | |
return initialExchangeRateMantissa; | |
} | |
// exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply | |
uint256 totalCash = getTotalCash(); | |
uint256 cashPlusBorrowsMinusReserves = (totalCash.add(totalBorrows).minus(totalReserves)).mul(1e18); | |
return cashPlusBorrowsMinusReserves.div(totalSupply); // -> scaled by 1e18 | |
} | |
function getTotalCash() internal view returns (uint256) { | |
return underlyingToken.balanceOf(address(this)); | |
} | |
/** | |
* @notice Get a snapshot of the account's balances, and the cached exchange rate | |
* @dev This is used by comptroller to more efficiently perform liquidity checks. | |
* @params _account Address of the account to snapshot | |
* @return (token balance, borrow balance, exchange rate mantissa) | |
*/ | |
function getAccountSnapshot(address _account) external view returns (uint256, uint256, uint256) { | |
uint256 cTokenBalance = accountTokens[_account]; | |
// Get Borrow Balance | |
uint256 borrowBalance = borrowBalanceStoredInternal(_account); | |
uint256 exchangeRate = exchangeRateStoredInternal(); | |
return (cTokenBalance, borrowBalance, exchangeRate); | |
} | |
/** | |
* @notice Return the borrow balance of account based on stored data | |
* @param _account The address whose balance should be calculated | |
* @return The calculated balance or 0 | |
*/ | |
function borrowBalanceStored(address _account) internal view returns (uint256) { | |
// Get BorrowBalance and borrowIndex | |
BorrowSnapshot storage borrowSnapshot = accountBorrows[_account]; | |
// If borrowBalance = 0 then borrowIndex is likely also 0 | |
if(borrowSnapshot.principal == 0) { | |
return 0; | |
} | |
// Calculate the borrow balance using the interest index | |
// recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex | |
uint256 principalTimesIndex = borrowSnapshot.principal.mul(borrowIndex); | |
uint256 result = principalTimesIndex.div(interestIndex); | |
return result; | |
} | |
// -- Events -- | |
event Mint(address minter, uint256 amount, uint256 mintTokens); | |
event AccrueInterest(uint256 interestRateAccumulated, uint256 borrowIndex, uint256 totalBorrows); | |
event Borrow(address _borrower, uint256 _borrowAmount, uint256 _accountBorrows, uint256 _totalBorrows); | |
event RepayBorrow(address _payer, address _borrower, uint256 _repayAmount, uint256 _accountBorrows, uint256 _totalBorrows); | |
event Transfer(address _from, address _to, uint256 _amount); | |
} | |
contract MoneyMarket is CToken { | |
constructor(string memory _name, string memory _symbol, uint256 _decimals) public CToken(_name, _symbol, _decimals) {} | |
} |
This file contains hidden or 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
pragma solidity ^0.5.10; | |
import './MoneyMarket.sol'; | |
interface PriceOracle { | |
function isPriceOracle() external pure returns (bool); | |
function getUnderlyingPrice(CToken cToken) view returns (uint); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment