// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.15; import "./CometMainInterface.sol"; import "./interfaces/IERC20NonStandard.sol"; import "./interfaces/IPriceFeed.sol"; import "./interfaces/IAssetListFactory.sol"; import "./interfaces/IAssetListFactoryHolder.sol"; import "./interfaces/IAssetList.sol"; /** * @title Compound's Comet Contract * @notice An efficient monolithic money market protocol * @author Compound */ contract CometWithExtendedAssetList is CometMainInterface { /** General configuration constants **/ /// @notice The admin of the protocol address public override immutable governor; /// @notice The account which may trigger pauses address public override immutable pauseGuardian; /// @notice The address of the base token contract address public override immutable baseToken; /// @notice The address of the price feed for the base token address public override immutable baseTokenPriceFeed; /// @notice The address of the extension contract delegate address public override immutable extensionDelegate; /// @notice The point in the supply rates separating the low interest rate slope and the high interest rate slope (factor) /// @dev uint64 uint public override immutable supplyKink; /// @notice Per second supply interest rate slope applied when utilization is below kink (factor) /// @dev uint64 uint public override immutable supplyPerSecondInterestRateSlopeLow; /// @notice Per second supply interest rate slope applied when utilization is above kink (factor) /// @dev uint64 uint public override immutable supplyPerSecondInterestRateSlopeHigh; /// @notice Per second supply base interest rate (factor) /// @dev uint64 uint public override immutable supplyPerSecondInterestRateBase; /// @notice The point in the borrow rate separating the low interest rate slope and the high interest rate slope (factor) /// @dev uint64 uint public override immutable borrowKink; /// @notice Per second borrow interest rate slope applied when utilization is below kink (factor) /// @dev uint64 uint public override immutable borrowPerSecondInterestRateSlopeLow; /// @notice Per second borrow interest rate slope applied when utilization is above kink (factor) /// @dev uint64 uint public override immutable borrowPerSecondInterestRateSlopeHigh; /// @notice Per second borrow base interest rate (factor) /// @dev uint64 uint public override immutable borrowPerSecondInterestRateBase; /// @notice The fraction of the liquidation penalty that goes to buyers of collateral instead of the protocol /// @dev uint64 uint public override immutable storeFrontPriceFactor; /// @notice The scale for base token (must be less than 18 decimals) /// @dev uint64 uint public override immutable baseScale; /// @notice The scale for reward tracking /// @dev uint64 uint public override immutable trackingIndexScale; /// @notice The speed at which supply rewards are tracked (in trackingIndexScale) /// @dev uint64 uint public override immutable baseTrackingSupplySpeed; /// @notice The speed at which borrow rewards are tracked (in trackingIndexScale) /// @dev uint64 uint public override immutable baseTrackingBorrowSpeed; /// @notice The minimum amount of base principal wei for rewards to accrue /// @dev This must be large enough so as to prevent division by base wei from overflowing the 64 bit indices /// @dev uint104 uint public override immutable baseMinForRewards; /// @notice The minimum base amount required to initiate a borrow uint public override immutable baseBorrowMin; /// @notice The minimum base token reserves which must be held before collateral is hodled uint public override immutable targetReserves; /// @notice The number of decimals for wrapped base token uint8 public override immutable decimals; /// @notice The number of assets this contract actually supports uint8 public override immutable numAssets; /// @notice Factor to divide by when accruing rewards in order to preserve 6 decimals (i.e. baseScale / 1e6) uint internal immutable accrualDescaleFactor; /// @notice The address of the asset list address immutable public assetList; uint8 internal constant MAX_ASSETS_FOR_ASSET_LIST = 24; /** * @notice Construct a new protocol instance * @param config The mapping of initial/constant parameters **/ constructor(Configuration memory config) { // Sanity checks uint8 decimals_ = IERC20NonStandard(config.baseToken).decimals(); if (decimals_ > MAX_BASE_DECIMALS) revert BadDecimals(); if (config.storeFrontPriceFactor > FACTOR_SCALE) revert BadDiscount(); if (config.assetConfigs.length > MAX_ASSETS_FOR_ASSET_LIST) revert TooManyAssets(); if (config.baseMinForRewards == 0) revert BadMinimum(); if (IPriceFeed(config.baseTokenPriceFeed).decimals() != PRICE_FEED_DECIMALS) revert BadDecimals(); // Copy configuration unchecked { governor = config.governor; pauseGuardian = config.pauseGuardian; baseToken = config.baseToken; baseTokenPriceFeed = config.baseTokenPriceFeed; extensionDelegate = config.extensionDelegate; storeFrontPriceFactor = config.storeFrontPriceFactor; decimals = decimals_; baseScale = uint64(10 ** decimals_); trackingIndexScale = config.trackingIndexScale; if (baseScale < BASE_ACCRUAL_SCALE) revert BadDecimals(); accrualDescaleFactor = baseScale / BASE_ACCRUAL_SCALE; baseMinForRewards = config.baseMinForRewards; baseTrackingSupplySpeed = config.baseTrackingSupplySpeed; baseTrackingBorrowSpeed = config.baseTrackingBorrowSpeed; baseBorrowMin = config.baseBorrowMin; targetReserves = config.targetReserves; } // Set interest rate model configs unchecked { supplyKink = config.supplyKink; supplyPerSecondInterestRateSlopeLow = config.supplyPerYearInterestRateSlopeLow / SECONDS_PER_YEAR; supplyPerSecondInterestRateSlopeHigh = config.supplyPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR; supplyPerSecondInterestRateBase = config.supplyPerYearInterestRateBase / SECONDS_PER_YEAR; borrowKink = config.borrowKink; borrowPerSecondInterestRateSlopeLow = config.borrowPerYearInterestRateSlopeLow / SECONDS_PER_YEAR; borrowPerSecondInterestRateSlopeHigh = config.borrowPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR; borrowPerSecondInterestRateBase = config.borrowPerYearInterestRateBase / SECONDS_PER_YEAR; } // Set asset info numAssets = uint8(config.assetConfigs.length); assetList = IAssetListFactory(IAssetListFactoryHolder(extensionDelegate).assetListFactory()).createAssetList(config.assetConfigs); } /** * @dev Prevents marked functions from being reentered * Note: this restrict contracts from calling comet functions in their hooks. * Doing so will cause the transaction to revert. */ modifier nonReentrant() { nonReentrantBefore(); _; nonReentrantAfter(); } /** * @dev Checks that the reentrancy flag is not set and then sets the flag */ function nonReentrantBefore() internal { bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT; uint256 status; assembly ("memory-safe") { status := sload(slot) } if (status == REENTRANCY_GUARD_ENTERED) revert ReentrantCallBlocked(); assembly ("memory-safe") { sstore(slot, REENTRANCY_GUARD_ENTERED) } } /** * @dev Unsets the reentrancy flag */ function nonReentrantAfter() internal { bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT; uint256 status; assembly ("memory-safe") { sstore(slot, REENTRANCY_GUARD_NOT_ENTERED) } } /** * @notice Initialize storage for the contract * @dev Can be used from constructor or proxy */ function initializeStorage() override external { if (lastAccrualTime != 0) revert AlreadyInitialized(); // Initialize aggregates lastAccrualTime = getNowInternal(); baseSupplyIndex = BASE_INDEX_SCALE; baseBorrowIndex = BASE_INDEX_SCALE; // Implicit initialization (not worth increasing contract size) // trackingSupplyIndex = 0; // trackingBorrowIndex = 0; } /** * @notice Get the i-th asset info, according to the order they were passed in originally * @param i The index of the asset info to get * @return The asset info object */ function getAssetInfo(uint8 i) override public view returns (AssetInfo memory) { return IAssetList(assetList).getAssetInfo(i); } /** * @dev Determine index of asset that matches given address */ function getAssetInfoByAddress(address asset) override public view returns (AssetInfo memory) { for (uint8 i = 0; i < numAssets; ) { AssetInfo memory assetInfo = getAssetInfo(i); if (assetInfo.asset == asset) { return assetInfo; } unchecked { i++; } } revert BadAsset(); } /** * @return The current timestamp **/ function getNowInternal() virtual internal view returns (uint40) { if (block.timestamp >= 2**40) revert TimestampTooLarge(); return uint40(block.timestamp); } /** * @dev Calculate accrued interest indices for base token supply and borrows **/ function accruedInterestIndices(uint timeElapsed) internal view returns (uint64, uint64) { uint64 baseSupplyIndex_ = baseSupplyIndex; uint64 baseBorrowIndex_ = baseBorrowIndex; if (timeElapsed > 0) { uint utilization = getUtilization(); uint supplyRate = getSupplyRate(utilization); uint borrowRate = getBorrowRate(utilization); baseSupplyIndex_ += safe64(mulFactor(baseSupplyIndex_, supplyRate * timeElapsed)); baseBorrowIndex_ += safe64(mulFactor(baseBorrowIndex_, borrowRate * timeElapsed)); } return (baseSupplyIndex_, baseBorrowIndex_); } /** * @dev Accrue interest (and rewards) in base token supply and borrows **/ function accrueInternal() internal { uint40 now_ = getNowInternal(); uint timeElapsed = uint256(now_ - lastAccrualTime); if (timeElapsed > 0) { (baseSupplyIndex, baseBorrowIndex) = accruedInterestIndices(timeElapsed); if (totalSupplyBase >= baseMinForRewards) { trackingSupplyIndex += safe64(divBaseWei(baseTrackingSupplySpeed * timeElapsed, totalSupplyBase)); } if (totalBorrowBase >= baseMinForRewards) { trackingBorrowIndex += safe64(divBaseWei(baseTrackingBorrowSpeed * timeElapsed, totalBorrowBase)); } lastAccrualTime = now_; } } /** * @notice Accrue interest and rewards for an account **/ function accrueAccount(address account) override external { accrueInternal(); UserBasic memory basic = userBasic[account]; updateBasePrincipal(account, basic, basic.principal); } /** * @dev Note: Does not accrue interest first * @param utilization The utilization to check the supply rate for * @return The per second supply rate at `utilization` */ function getSupplyRate(uint utilization) override public view returns (uint64) { if (utilization <= supplyKink) { // interestRateBase + interestRateSlopeLow * utilization return safe64(supplyPerSecondInterestRateBase + mulFactor(supplyPerSecondInterestRateSlopeLow, utilization)); } else { // interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink) return safe64(supplyPerSecondInterestRateBase + mulFactor(supplyPerSecondInterestRateSlopeLow, supplyKink) + mulFactor(supplyPerSecondInterestRateSlopeHigh, (utilization - supplyKink))); } } /** * @dev Note: Does not accrue interest first * @param utilization The utilization to check the borrow rate for * @return The per second borrow rate at `utilization` */ function getBorrowRate(uint utilization) override public view returns (uint64) { if (utilization <= borrowKink) { // interestRateBase + interestRateSlopeLow * utilization return safe64(borrowPerSecondInterestRateBase + mulFactor(borrowPerSecondInterestRateSlopeLow, utilization)); } else { // interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink) return safe64(borrowPerSecondInterestRateBase + mulFactor(borrowPerSecondInterestRateSlopeLow, borrowKink) + mulFactor(borrowPerSecondInterestRateSlopeHigh, (utilization - borrowKink))); } } /** * @dev Note: Does not accrue interest first * @return The utilization rate of the base asset */ function getUtilization() override public view returns (uint) { uint totalSupply_ = presentValueSupply(baseSupplyIndex, totalSupplyBase); uint totalBorrow_ = presentValueBorrow(baseBorrowIndex, totalBorrowBase); if (totalSupply_ == 0) { return 0; } else { return totalBorrow_ * FACTOR_SCALE / totalSupply_; } } /** * @notice Get the current price from a feed * @param priceFeed The address of a price feed * @return The price, scaled by `PRICE_SCALE` */ function getPrice(address priceFeed) override public view returns (uint256) { (, int price, , , ) = IPriceFeed(priceFeed).latestRoundData(); if (price <= 0) revert BadPrice(); return uint256(price); } /** * @notice Gets the total balance of protocol collateral reserves for an asset * @dev Note: Reverts if collateral reserves are somehow negative, which should not be possible * @param asset The collateral asset */ function getCollateralReserves(address asset) override public view returns (uint) { return IERC20NonStandard(asset).balanceOf(address(this)) - totalsCollateral[asset].totalSupplyAsset; } /** * @notice Gets the total amount of protocol reserves of the base asset */ function getReserves() override public view returns (int) { (uint64 baseSupplyIndex_, uint64 baseBorrowIndex_) = accruedInterestIndices(getNowInternal() - lastAccrualTime); uint balance = IERC20NonStandard(baseToken).balanceOf(address(this)); uint totalSupply_ = presentValueSupply(baseSupplyIndex_, totalSupplyBase); uint totalBorrow_ = presentValueBorrow(baseBorrowIndex_, totalBorrowBase); return signed256(balance) - signed256(totalSupply_) + signed256(totalBorrow_); } /** * @notice Check whether an account has enough collateral to borrow * @param account The address to check * @return Whether the account is minimally collateralized enough to borrow */ function isBorrowCollateralized(address account) override public view returns (bool) { int104 principal = userBasic[account].principal; if (principal >= 0) { return true; } uint16 assetsIn = userBasic[account].assetsIn; uint8 _reserved = userBasic[account]._reserved; int liquidity = signedMulPrice( presentValue(principal), getPrice(baseTokenPriceFeed), uint64(baseScale) ); for (uint8 i = 0; i < numAssets; ) { if (isInAsset(assetsIn, i, _reserved)) { if (liquidity >= 0) { return true; } AssetInfo memory asset = getAssetInfo(i); uint newAmount = mulPrice( userCollateral[account][asset.asset].balance, getPrice(asset.priceFeed), asset.scale ); liquidity += signed256(mulFactor( newAmount, asset.borrowCollateralFactor )); } unchecked { i++; } } return liquidity >= 0; } /** * @notice Check whether an account has enough collateral to not be liquidated * @param account The address to check * @return Whether the account is minimally collateralized enough to not be liquidated */ function isLiquidatable(address account) override public view returns (bool) { int104 principal = userBasic[account].principal; if (principal >= 0) { return false; } uint16 assetsIn = userBasic[account].assetsIn; uint8 _reserved = userBasic[account]._reserved; int liquidity = signedMulPrice( presentValue(principal), getPrice(baseTokenPriceFeed), uint64(baseScale) ); for (uint8 i = 0; i < numAssets; ) { if (isInAsset(assetsIn, i, _reserved)) { if (liquidity >= 0) { return false; } AssetInfo memory asset = getAssetInfo(i); uint newAmount = mulPrice( userCollateral[account][asset.asset].balance, getPrice(asset.priceFeed), asset.scale ); liquidity += signed256(mulFactor( newAmount, asset.liquidateCollateralFactor )); } unchecked { i++; } } return liquidity < 0; } /** * @dev The change in principal broken into repay and supply amounts */ function repayAndSupplyAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) { // If the new principal is less than the old principal, then no amount has been repaid or supplied if (newPrincipal < oldPrincipal) return (0, 0); if (newPrincipal <= 0) { return (uint104(newPrincipal - oldPrincipal), 0); } else if (oldPrincipal >= 0) { return (0, uint104(newPrincipal - oldPrincipal)); } else { return (uint104(-oldPrincipal), uint104(newPrincipal)); } } /** * @dev The change in principal broken into withdraw and borrow amounts */ function withdrawAndBorrowAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) { // If the new principal is greater than the old principal, then no amount has been withdrawn or borrowed if (newPrincipal > oldPrincipal) return (0, 0); if (newPrincipal >= 0) { return (uint104(oldPrincipal - newPrincipal), 0); } else if (oldPrincipal <= 0) { return (0, uint104(oldPrincipal - newPrincipal)); } else { return (uint104(oldPrincipal), uint104(-newPrincipal)); } } /** * @notice Pauses different actions within Comet * @param supplyPaused Boolean for pausing supply actions * @param transferPaused Boolean for pausing transfer actions * @param withdrawPaused Boolean for pausing withdraw actions * @param absorbPaused Boolean for pausing absorb actions * @param buyPaused Boolean for pausing buy actions */ function pause( bool supplyPaused, bool transferPaused, bool withdrawPaused, bool absorbPaused, bool buyPaused ) override external { if (msg.sender != governor && msg.sender != pauseGuardian) revert Unauthorized(); pauseFlags = uint8(0) | (toUInt8(supplyPaused) << PAUSE_SUPPLY_OFFSET) | (toUInt8(transferPaused) << PAUSE_TRANSFER_OFFSET) | (toUInt8(withdrawPaused) << PAUSE_WITHDRAW_OFFSET) | (toUInt8(absorbPaused) << PAUSE_ABSORB_OFFSET) | (toUInt8(buyPaused) << PAUSE_BUY_OFFSET); emit PauseAction(supplyPaused, transferPaused, withdrawPaused, absorbPaused, buyPaused); } /** * @return Whether or not supply actions are paused */ function isSupplyPaused() override public view returns (bool) { return toBool(pauseFlags & (uint8(1) << PAUSE_SUPPLY_OFFSET)); } /** * @return Whether or not transfer actions are paused */ function isTransferPaused() override public view returns (bool) { return toBool(pauseFlags & (uint8(1) << PAUSE_TRANSFER_OFFSET)); } /** * @return Whether or not withdraw actions are paused */ function isWithdrawPaused() override public view returns (bool) { return toBool(pauseFlags & (uint8(1) << PAUSE_WITHDRAW_OFFSET)); } /** * @return Whether or not absorb actions are paused */ function isAbsorbPaused() override public view returns (bool) { return toBool(pauseFlags & (uint8(1) << PAUSE_ABSORB_OFFSET)); } /** * @return Whether or not buy actions are paused */ function isBuyPaused() override public view returns (bool) { return toBool(pauseFlags & (uint8(1) << PAUSE_BUY_OFFSET)); } /** * @dev Multiply a number by a factor */ function mulFactor(uint n, uint factor) internal pure returns (uint) { return n * factor / FACTOR_SCALE; } /** * @dev Divide a number by an amount of base */ function divBaseWei(uint n, uint baseWei) internal view returns (uint) { return n * baseScale / baseWei; } /** * @dev Multiply a `fromScale` quantity by a price, returning a common price quantity */ function mulPrice(uint n, uint price, uint64 fromScale) internal pure returns (uint) { return n * price / fromScale; } /** * @dev Multiply a signed `fromScale` quantity by a price, returning a common price quantity */ function signedMulPrice(int n, uint price, uint64 fromScale) internal pure returns (int) { return n * signed256(price) / int256(uint256(fromScale)); } /** * @dev Divide a common price quantity by a price, returning a `toScale` quantity */ function divPrice(uint n, uint price, uint64 toScale) internal pure returns (uint) { return n * toScale / price; } /** * @dev Whether user has a non-zero balance of an asset, given assetsIn flags * @dev _reserved is used to check bits 16-23 of assetsIn */ function isInAsset(uint16 assetsIn, uint8 assetOffset, uint8 _reserved) internal pure returns (bool) { if (assetOffset < 16) { // check bit in assetsIn (for bits 0-15) return (assetsIn & (uint16(1) << assetOffset)) != 0; } else if (assetOffset < 24) { // check bit in reserved (for bits 16-23) return (_reserved & (uint8(1) << (assetOffset - 16))) != 0; } return false; // if assetOffset >= 24 (should not happen) } /** * @dev Update assetsIn bit vector if user has entered or exited an asset */ function updateAssetsIn( address account, AssetInfo memory assetInfo, uint128 initialUserBalance, uint128 finalUserBalance ) internal { if (initialUserBalance == 0 && finalUserBalance != 0) { // set bit for asset if (assetInfo.offset < 16) { // set bit in assetsIn for bits 0-15 userBasic[account].assetsIn |= (uint16(1) << assetInfo.offset); } else if (assetInfo.offset < 24) { // set bit in _reserved for bits 16-23 userBasic[account]._reserved |= (uint8(1) << (assetInfo.offset - 16)); } } else if (initialUserBalance != 0 && finalUserBalance == 0) { // clear bit for asset if (assetInfo.offset < 16) { // clear bit in assetsIn for bits 0-15 userBasic[account].assetsIn &= ~(uint16(1) << assetInfo.offset); } else if (assetInfo.offset < 24) { // clear bit in _reserved for bits 16-23 userBasic[account]._reserved &= ~(uint8(1) << (assetInfo.offset - 16)); } } } /** * @dev Write updated principal to store and tracking participation */ function updateBasePrincipal(address account, UserBasic memory basic, int104 principalNew) internal { int104 principal = basic.principal; basic.principal = principalNew; if (principal >= 0) { uint indexDelta = uint256(trackingSupplyIndex - basic.baseTrackingIndex); basic.baseTrackingAccrued += safe64(uint104(principal) * indexDelta / trackingIndexScale / accrualDescaleFactor); } else { uint indexDelta = uint256(trackingBorrowIndex - basic.baseTrackingIndex); basic.baseTrackingAccrued += safe64(uint104(-principal) * indexDelta / trackingIndexScale / accrualDescaleFactor); } if (principalNew >= 0) { basic.baseTrackingIndex = trackingSupplyIndex; } else { basic.baseTrackingIndex = trackingBorrowIndex; } userBasic[account] = basic; } /** * @dev Safe ERC20 transfer in and returns the final amount transferred (taking into account any fees) * @dev Note: Safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca */ function doTransferIn(address asset, address from, uint amount) internal returns (uint) { uint256 preTransferBalance = IERC20NonStandard(asset).balanceOf(address(this)); IERC20NonStandard(asset).transferFrom(from, address(this), amount); bool success; assembly ("memory-safe") { switch returndatasize() case 0 { // This is a non-standard ERC-20 success := not(0) // set success to true } case 32 { // This is a compliant ERC-20 returndatacopy(0, 0, 32) success := mload(0) // Set `success = returndata` of override external call } default { // This is an excessively non-compliant ERC-20, revert. revert(0, 0) } } if (!success) revert TransferInFailed(); return IERC20NonStandard(asset).balanceOf(address(this)) - preTransferBalance; } /** * @dev Safe ERC20 transfer out * @dev Note: Safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca */ function doTransferOut(address asset, address to, uint amount) internal { IERC20NonStandard(asset).transfer(to, amount); bool success; assembly ("memory-safe") { switch returndatasize() case 0 { // This is a non-standard ERC-20 success := not(0) // set success to true } case 32 { // This is a compliant ERC-20 returndatacopy(0, 0, 32) success := mload(0) // Set `success = returndata` of override external call } default { // This is an excessively non-compliant ERC-20, revert. revert(0, 0) } } if (!success) revert TransferOutFailed(); } /** * @notice Supply an amount of asset to the protocol * @param asset The asset to supply * @param amount The quantity to supply */ function supply(address asset, uint amount) override external { return supplyInternal(msg.sender, msg.sender, msg.sender, asset, amount); } /** * @notice Supply an amount of asset to dst * @param dst The address which will hold the balance * @param asset The asset to supply * @param amount The quantity to supply */ function supplyTo(address dst, address asset, uint amount) override external { return supplyInternal(msg.sender, msg.sender, dst, asset, amount); } /** * @notice Supply an amount of asset from `from` to dst, if allowed * @param from The supplier address * @param dst The address which will hold the balance * @param asset The asset to supply * @param amount The quantity to supply */ function supplyFrom(address from, address dst, address asset, uint amount) override external { return supplyInternal(msg.sender, from, dst, asset, amount); } /** * @dev Supply either collateral or base asset, depending on the asset, if operator is allowed * @dev Note: Specifying an `amount` of uint256.max will repay all of `dst`'s accrued base borrow balance */ function supplyInternal(address operator, address from, address dst, address asset, uint amount) internal nonReentrant { if (isSupplyPaused()) revert Paused(); if (!hasPermission(from, operator)) revert Unauthorized(); if (asset == baseToken) { if (amount == type(uint256).max) { amount = borrowBalanceOf(dst); } return supplyBase(from, dst, amount); } else { return supplyCollateral(from, dst, asset, safe128(amount)); } } /** * @dev Supply an amount of base asset from `from` to dst */ function supplyBase(address from, address dst, uint256 amount) internal { amount = doTransferIn(baseToken, from, amount); accrueInternal(); UserBasic memory dstUser = userBasic[dst]; int104 dstPrincipal = dstUser.principal; int256 dstBalance = presentValue(dstPrincipal) + signed256(amount); int104 dstPrincipalNew = principalValue(dstBalance); (uint104 repayAmount, uint104 supplyAmount) = repayAndSupplyAmount(dstPrincipal, dstPrincipalNew); totalSupplyBase += supplyAmount; totalBorrowBase -= repayAmount; updateBasePrincipal(dst, dstUser, dstPrincipalNew); emit Supply(from, dst, amount); if (supplyAmount > 0) { emit Transfer(address(0), dst, presentValueSupply(baseSupplyIndex, supplyAmount)); } } /** * @dev Supply an amount of collateral asset from `from` to dst */ function supplyCollateral(address from, address dst, address asset, uint128 amount) internal { amount = safe128(doTransferIn(asset, from, amount)); AssetInfo memory assetInfo = getAssetInfoByAddress(asset); TotalsCollateral memory totals = totalsCollateral[asset]; totals.totalSupplyAsset += amount; if (totals.totalSupplyAsset > assetInfo.supplyCap) revert SupplyCapExceeded(); uint128 dstCollateral = userCollateral[dst][asset].balance; uint128 dstCollateralNew = dstCollateral + amount; totalsCollateral[asset] = totals; userCollateral[dst][asset].balance = dstCollateralNew; updateAssetsIn(dst, assetInfo, dstCollateral, dstCollateralNew); emit SupplyCollateral(from, dst, asset, amount); } /** * @notice ERC20 transfer an amount of base token to dst * @param dst The recipient address * @param amount The quantity to transfer * @return true */ function transfer(address dst, uint amount) override external returns (bool) { transferInternal(msg.sender, msg.sender, dst, baseToken, amount); return true; } /** * @notice ERC20 transfer an amount of base token from src to dst, if allowed * @param src The sender address * @param dst The recipient address * @param amount The quantity to transfer * @return true */ function transferFrom(address src, address dst, uint amount) override external returns (bool) { transferInternal(msg.sender, src, dst, baseToken, amount); return true; } /** * @notice Transfer an amount of asset to dst * @param dst The recipient address * @param asset The asset to transfer * @param amount The quantity to transfer */ function transferAsset(address dst, address asset, uint amount) override external { return transferInternal(msg.sender, msg.sender, dst, asset, amount); } /** * @notice Transfer an amount of asset from src to dst, if allowed * @param src The sender address * @param dst The recipient address * @param asset The asset to transfer * @param amount The quantity to transfer */ function transferAssetFrom(address src, address dst, address asset, uint amount) override external { return transferInternal(msg.sender, src, dst, asset, amount); } /** * @dev Transfer either collateral or base asset, depending on the asset, if operator is allowed * @dev Note: Specifying an `amount` of uint256.max will transfer all of `src`'s accrued base balance */ function transferInternal(address operator, address src, address dst, address asset, uint amount) internal nonReentrant { if (isTransferPaused()) revert Paused(); if (!hasPermission(src, operator)) revert Unauthorized(); if (src == dst) revert NoSelfTransfer(); if (asset == baseToken) { if (amount == type(uint256).max) { amount = balanceOf(src); } return transferBase(src, dst, amount); } else { return transferCollateral(src, dst, asset, safe128(amount)); } } /** * @dev Transfer an amount of base asset from src to dst, borrowing if possible/necessary */ function transferBase(address src, address dst, uint256 amount) internal { accrueInternal(); UserBasic memory srcUser = userBasic[src]; UserBasic memory dstUser = userBasic[dst]; int104 srcPrincipal = srcUser.principal; int104 dstPrincipal = dstUser.principal; int256 srcBalance = presentValue(srcPrincipal) - signed256(amount); int256 dstBalance = presentValue(dstPrincipal) + signed256(amount); int104 srcPrincipalNew = principalValue(srcBalance); int104 dstPrincipalNew = principalValue(dstBalance); (uint104 withdrawAmount, uint104 borrowAmount) = withdrawAndBorrowAmount(srcPrincipal, srcPrincipalNew); (uint104 repayAmount, uint104 supplyAmount) = repayAndSupplyAmount(dstPrincipal, dstPrincipalNew); // Note: Instead of `total += addAmount - subAmount` to avoid underflow errors. totalSupplyBase = totalSupplyBase + supplyAmount - withdrawAmount; totalBorrowBase = totalBorrowBase + borrowAmount - repayAmount; updateBasePrincipal(src, srcUser, srcPrincipalNew); updateBasePrincipal(dst, dstUser, dstPrincipalNew); if (srcBalance < 0) { if (uint256(-srcBalance) < baseBorrowMin) revert BorrowTooSmall(); if (!isBorrowCollateralized(src)) revert NotCollateralized(); } if (withdrawAmount > 0) { emit Transfer(src, address(0), presentValueSupply(baseSupplyIndex, withdrawAmount)); } if (supplyAmount > 0) { emit Transfer(address(0), dst, presentValueSupply(baseSupplyIndex, supplyAmount)); } } /** * @dev Transfer an amount of collateral asset from src to dst */ function transferCollateral(address src, address dst, address asset, uint128 amount) internal { uint128 srcCollateral = userCollateral[src][asset].balance; uint128 dstCollateral = userCollateral[dst][asset].balance; uint128 srcCollateralNew = srcCollateral - amount; uint128 dstCollateralNew = dstCollateral + amount; userCollateral[src][asset].balance = srcCollateralNew; userCollateral[dst][asset].balance = dstCollateralNew; AssetInfo memory assetInfo = getAssetInfoByAddress(asset); updateAssetsIn(src, assetInfo, srcCollateral, srcCollateralNew); updateAssetsIn(dst, assetInfo, dstCollateral, dstCollateralNew); // Note: no accrue interest, BorrowCF < LiquidationCF covers small changes if (!isBorrowCollateralized(src)) revert NotCollateralized(); emit TransferCollateral(src, dst, asset, amount); } /** * @notice Withdraw an amount of asset from the protocol * @param asset The asset to withdraw * @param amount The quantity to withdraw */ function withdraw(address asset, uint amount) override external { return withdrawInternal(msg.sender, msg.sender, msg.sender, asset, amount); } /** * @notice Withdraw an amount of asset to `to` * @param to The recipient address * @param asset The asset to withdraw * @param amount The quantity to withdraw */ function withdrawTo(address to, address asset, uint amount) override external { return withdrawInternal(msg.sender, msg.sender, to, asset, amount); } /** * @notice Withdraw an amount of asset from src to `to`, if allowed * @param src The sender address * @param to The recipient address * @param asset The asset to withdraw * @param amount The quantity to withdraw */ function withdrawFrom(address src, address to, address asset, uint amount) override external { return withdrawInternal(msg.sender, src, to, asset, amount); } /** * @dev Withdraw either collateral or base asset, depending on the asset, if operator is allowed * @dev Note: Specifying an `amount` of uint256.max will withdraw all of `src`'s accrued base balance */ function withdrawInternal(address operator, address src, address to, address asset, uint amount) internal nonReentrant { if (isWithdrawPaused()) revert Paused(); if (!hasPermission(src, operator)) revert Unauthorized(); if (asset == baseToken) { if (amount == type(uint256).max) { amount = balanceOf(src); } return withdrawBase(src, to, amount); } else { return withdrawCollateral(src, to, asset, safe128(amount)); } } /** * @dev Withdraw an amount of base asset from src to `to`, borrowing if possible/necessary */ function withdrawBase(address src, address to, uint256 amount) internal { accrueInternal(); UserBasic memory srcUser = userBasic[src]; int104 srcPrincipal = srcUser.principal; int256 srcBalance = presentValue(srcPrincipal) - signed256(amount); int104 srcPrincipalNew = principalValue(srcBalance); (uint104 withdrawAmount, uint104 borrowAmount) = withdrawAndBorrowAmount(srcPrincipal, srcPrincipalNew); totalSupplyBase -= withdrawAmount; totalBorrowBase += borrowAmount; updateBasePrincipal(src, srcUser, srcPrincipalNew); if (srcBalance < 0) { if (uint256(-srcBalance) < baseBorrowMin) revert BorrowTooSmall(); if (!isBorrowCollateralized(src)) revert NotCollateralized(); } doTransferOut(baseToken, to, amount); emit Withdraw(src, to, amount); if (withdrawAmount > 0) { emit Transfer(src, address(0), presentValueSupply(baseSupplyIndex, withdrawAmount)); } } /** * @dev Withdraw an amount of collateral asset from src to `to` */ function withdrawCollateral(address src, address to, address asset, uint128 amount) internal { uint128 srcCollateral = userCollateral[src][asset].balance; uint128 srcCollateralNew = srcCollateral - amount; totalsCollateral[asset].totalSupplyAsset -= amount; userCollateral[src][asset].balance = srcCollateralNew; AssetInfo memory assetInfo = getAssetInfoByAddress(asset); updateAssetsIn(src, assetInfo, srcCollateral, srcCollateralNew); // Note: no accrue interest, BorrowCF < LiquidationCF covers small changes if (!isBorrowCollateralized(src)) revert NotCollateralized(); doTransferOut(asset, to, amount); emit WithdrawCollateral(src, to, asset, amount); } /** * @notice Absorb a list of underwater accounts onto the protocol balance sheet * @param absorber The recipient of the incentive paid to the caller of absorb * @param accounts The list of underwater accounts to absorb */ function absorb(address absorber, address[] calldata accounts) override external { if (isAbsorbPaused()) revert Paused(); uint startGas = gasleft(); accrueInternal(); for (uint i = 0; i < accounts.length; ) { absorbInternal(absorber, accounts[i]); unchecked { i++; } } uint gasUsed = startGas - gasleft(); // Note: liquidator points are an imperfect tool for governance, // to be used while evaluating strategies for incentivizing absorption. // Using gas price instead of base fee would more accurately reflect spend, // but is also subject to abuse if refunds were to be given automatically. LiquidatorPoints memory points = liquidatorPoints[absorber]; points.numAbsorbs++; points.numAbsorbed += safe64(accounts.length); points.approxSpend += safe128(gasUsed * block.basefee); liquidatorPoints[absorber] = points; } /** * @dev Transfer user's collateral and debt to the protocol itself. */ function absorbInternal(address absorber, address account) internal { if (!isLiquidatable(account)) revert NotLiquidatable(); UserBasic memory accountUser = userBasic[account]; int104 oldPrincipal = accountUser.principal; int256 oldBalance = presentValue(oldPrincipal); uint16 assetsIn = accountUser.assetsIn; uint8 _reserved = accountUser._reserved; uint256 basePrice = getPrice(baseTokenPriceFeed); uint256 deltaValue = 0; for (uint8 i = 0; i < numAssets; ) { if (isInAsset(assetsIn, i, _reserved)) { AssetInfo memory assetInfo = getAssetInfo(i); address asset = assetInfo.asset; uint128 seizeAmount = userCollateral[account][asset].balance; userCollateral[account][asset].balance = 0; totalsCollateral[asset].totalSupplyAsset -= seizeAmount; uint256 value = mulPrice(seizeAmount, getPrice(assetInfo.priceFeed), assetInfo.scale); deltaValue += mulFactor(value, assetInfo.liquidationFactor); emit AbsorbCollateral(absorber, account, asset, seizeAmount, value); } unchecked { i++; } } uint256 deltaBalance = divPrice(deltaValue, basePrice, uint64(baseScale)); int256 newBalance = oldBalance + signed256(deltaBalance); // New balance will not be negative, all excess debt absorbed by reserves if (newBalance < 0) { newBalance = 0; } int104 newPrincipal = principalValue(newBalance); updateBasePrincipal(account, accountUser, newPrincipal); // reset assetsIn userBasic[account].assetsIn = 0; userBasic[account]._reserved = 0; (uint104 repayAmount, uint104 supplyAmount) = repayAndSupplyAmount(oldPrincipal, newPrincipal); // Reserves are decreased by increasing total supply and decreasing borrows // the amount of debt repaid by reserves is `newBalance - oldBalance` totalSupplyBase += supplyAmount; totalBorrowBase -= repayAmount; uint256 basePaidOut = unsigned256(newBalance - oldBalance); uint256 valueOfBasePaidOut = mulPrice(basePaidOut, basePrice, uint64(baseScale)); emit AbsorbDebt(absorber, account, basePaidOut, valueOfBasePaidOut); if (newPrincipal > 0) { emit Transfer(address(0), account, presentValueSupply(baseSupplyIndex, unsigned104(newPrincipal))); } } /** * @notice Buy collateral from the protocol using base tokens, increasing protocol reserves A minimum collateral amount should be specified to indicate the maximum slippage acceptable for the buyer. * @param asset The asset to buy * @param minAmount The minimum amount of collateral tokens that should be received by the buyer * @param baseAmount The amount of base tokens used to buy the collateral * @param recipient The recipient address */ function buyCollateral(address asset, uint minAmount, uint baseAmount, address recipient) override external nonReentrant { if (isBuyPaused()) revert Paused(); int reserves = getReserves(); if (reserves >= 0 && uint(reserves) >= targetReserves) revert NotForSale(); // Note: Re-entrancy can skip the reserves check above on a second buyCollateral call. baseAmount = doTransferIn(baseToken, msg.sender, baseAmount); uint collateralAmount = quoteCollateral(asset, baseAmount); if (collateralAmount < minAmount) revert TooMuchSlippage(); if (collateralAmount > getCollateralReserves(asset)) revert InsufficientReserves(); // Note: Pre-transfer hook can re-enter buyCollateral with a stale collateral ERC20 balance. // Assets should not be listed which allow re-entry from pre-transfer now, as too much collateral could be bought. // This is also a problem if quoteCollateral derives its discount from the collateral ERC20 balance. doTransferOut(asset, recipient, safe128(collateralAmount)); emit BuyCollateral(msg.sender, asset, baseAmount, collateralAmount); } /** * @notice Gets the quote for a collateral asset in exchange for an amount of base asset * @param asset The collateral asset to get the quote for * @param baseAmount The amount of the base asset to get the quote for * @return The quote in terms of the collateral asset */ function quoteCollateral(address asset, uint baseAmount) override public view returns (uint) { AssetInfo memory assetInfo = getAssetInfoByAddress(asset); uint256 assetPrice = getPrice(assetInfo.priceFeed); // Store front discount is derived from the collateral asset's liquidationFactor and storeFrontPriceFactor // discount = storeFrontPriceFactor * (1e18 - liquidationFactor) uint256 discountFactor = mulFactor(storeFrontPriceFactor, FACTOR_SCALE - assetInfo.liquidationFactor); uint256 assetPriceDiscounted = mulFactor(assetPrice, FACTOR_SCALE - discountFactor); uint256 basePrice = getPrice(baseTokenPriceFeed); // # of collateral assets // = (TotalValueOfBaseAmount / DiscountedPriceOfCollateralAsset) * assetScale // = ((basePrice * baseAmount / baseScale) / assetPriceDiscounted) * assetScale return basePrice * baseAmount * assetInfo.scale / assetPriceDiscounted / baseScale; } /** * @notice Withdraws base token reserves if called by the governor * @param to An address of the receiver of withdrawn reserves * @param amount The amount of reserves to be withdrawn from the protocol */ function withdrawReserves(address to, uint amount) override external { if (msg.sender != governor) revert Unauthorized(); int reserves = getReserves(); if (reserves < 0 || amount > unsigned256(reserves)) revert InsufficientReserves(); doTransferOut(baseToken, to, amount); emit WithdrawReserves(to, amount); } /** * @notice Sets Comet's ERC20 allowance of an asset for a manager * @dev Only callable by governor * @dev Note: Setting the `asset` as Comet's address will allow the manager * to withdraw from Comet's Comet balance * @dev Note: For USDT, if there is non-zero prior allowance, it must be reset to 0 first before setting a new value in proposal * @param asset The asset that the manager will gain approval of * @param manager The account which will be allowed or disallowed * @param amount The amount of an asset to approve */ function approveThis(address manager, address asset, uint amount) override external { if (msg.sender != governor) revert Unauthorized(); IERC20NonStandard(asset).approve(manager, amount); } /** * @notice Get the total number of tokens in circulation * @dev Note: uses updated interest indices to calculate * @return The supply of tokens **/ function totalSupply() override external view returns (uint256) { (uint64 baseSupplyIndex_, ) = accruedInterestIndices(getNowInternal() - lastAccrualTime); return presentValueSupply(baseSupplyIndex_, totalSupplyBase); } /** * @notice Get the total amount of debt * @dev Note: uses updated interest indices to calculate * @return The amount of debt **/ function totalBorrow() override external view returns (uint256) { (, uint64 baseBorrowIndex_) = accruedInterestIndices(getNowInternal() - lastAccrualTime); return presentValueBorrow(baseBorrowIndex_, totalBorrowBase); } /** * @notice Query the current positive base balance of an account or zero * @dev Note: uses updated interest indices to calculate * @param account The account whose balance to query * @return The present day base balance magnitude of the account, if positive */ function balanceOf(address account) override public view returns (uint256) { (uint64 baseSupplyIndex_, ) = accruedInterestIndices(getNowInternal() - lastAccrualTime); int104 principal = userBasic[account].principal; return principal > 0 ? presentValueSupply(baseSupplyIndex_, unsigned104(principal)) : 0; } /** * @notice Query the current negative base balance of an account or zero * @dev Note: uses updated interest indices to calculate * @param account The account whose balance to query * @return The present day base balance magnitude of the account, if negative */ function borrowBalanceOf(address account) override public view returns (uint256) { (, uint64 baseBorrowIndex_) = accruedInterestIndices(getNowInternal() - lastAccrualTime); int104 principal = userBasic[account].principal; return principal < 0 ? presentValueBorrow(baseBorrowIndex_, unsigned104(-principal)) : 0; } /** * @notice Fallback to calling the extension delegate for everything else */ fallback() external payable { address delegate = extensionDelegate; assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), delegate, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } }