Skip to content

Instantly share code, notes, and snippets.

@combattardigrade
Created February 23, 2021 01:55
Show Gist options
  • Save combattardigrade/f189c3015535b38bc195577be904c9b1 to your computer and use it in GitHub Desktop.
Save combattardigrade/f189c3015535b38bc195577be904c9b1 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=
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;
}
}
{
/**
* @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);
}
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);
}
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;
}
// -- 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 return (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;
}
// -- Events --
event MarketListed(CToken cToken);
event MarketEntered(CToken cToken, address account);
event NewMaxAssets(uint256 newMaxAssets);
event NewPriceOracle(PriceOracle newPriceOracle);
}
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
if(_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;
}
// 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) {}
}
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