diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 511be22f6..306236a7e 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/contracts/Comptroller/Comptroller.sol b/contracts/Comptroller/Comptroller.sol deleted file mode 100644 index a07ff43c2..000000000 --- a/contracts/Comptroller/Comptroller.sol +++ /dev/null @@ -1,1600 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VTokens/VToken.sol"; -import "../Utils/ErrorReporter.sol"; -import "../Tokens/XVS/XVS.sol"; -import "../Tokens/VAI/VAI.sol"; -import "../Governance/IAccessControlManager.sol"; -import "./ComptrollerLensInterface.sol"; -import "./ComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Venus's Comptroller Contract - * @author Venus - */ -contract Comptroller is ComptrollerV12Storage, ComptrollerInterfaceG2, ComptrollerErrorReporter, ExponentialNoError { - /// @notice Emitted when an admin supports a market - event MarketListed(VToken vToken); - - /// @notice Emitted when an account enters a market - event MarketEntered(VToken vToken, address account); - - /// @notice Emitted when an account exits a market - event MarketExited(VToken vToken, address account); - - /// @notice Emitted when close factor is changed by admin - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /// @notice Emitted when a collateral factor is changed by admin - event NewCollateralFactor(VToken vToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /// @notice Emitted when liquidation incentive is changed by admin - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /// @notice Emitted when price oracle is changed - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /// @notice Emitted when VAI Vault info is changed - event NewVAIVaultInfo(address vault_, uint releaseStartBlock_, uint releaseInterval_); - - /// @notice Emitted when pause guardian is changed - event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); - - /// @notice Emitted when an action is paused on a market - event ActionPausedMarket(VToken indexed vToken, Action indexed action, bool pauseState); - - /// @notice Emitted when Venus VAI Vault rate is changed - event NewVenusVAIVaultRate(uint oldVenusVAIVaultRate, uint newVenusVAIVaultRate); - - /// @notice Emitted when a new borrow-side XVS speed is calculated for a market - event VenusBorrowSpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when a new supply-side XVS speed is calculated for a market - event VenusSupplySpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when XVS is distributed to a supplier - event DistributedSupplierVenus( - VToken indexed vToken, - address indexed supplier, - uint venusDelta, - uint venusSupplyIndex - ); - - /// @notice Emitted when XVS is distributed to a borrower - event DistributedBorrowerVenus( - VToken indexed vToken, - address indexed borrower, - uint venusDelta, - uint venusBorrowIndex - ); - - /// @notice Emitted when XVS is distributed to VAI Vault - event DistributedVAIVaultVenus(uint amount); - - /// @notice Emitted when VAIController is changed - event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); - - /// @notice Emitted when VAI mint rate is changed by admin - event NewVAIMintRate(uint oldVAIMintRate, uint newVAIMintRate); - - /// @notice Emitted when protocol state is changed by admin - event ActionProtocolPaused(bool state); - - /// @notice Emitted when borrow cap for a vToken is changed - event NewBorrowCap(VToken indexed vToken, uint newBorrowCap); - - /// @notice Emitted when treasury guardian is changed - event NewTreasuryGuardian(address oldTreasuryGuardian, address newTreasuryGuardian); - - /// @notice Emitted when treasury address is changed - event NewTreasuryAddress(address oldTreasuryAddress, address newTreasuryAddress); - - /// @notice Emitted when treasury percent is changed - event NewTreasuryPercent(uint oldTreasuryPercent, uint newTreasuryPercent); - - // @notice Emitted when liquidator adress is changed - event NewLiquidatorContract(address oldLiquidatorContract, address newLiquidatorContract); - - /// @notice Emitted when Venus is granted by admin - event VenusGranted(address recipient, uint amount); - - /// @notice Emitted whe ComptrollerLens address is changed - event NewComptrollerLens(address oldComptrollerLens, address newComptrollerLens); - - /// @notice Emitted when supply cap for a vToken is changed - event NewSupplyCap(VToken indexed vToken, uint newSupplyCap); - - /// @notice Emitted when access control address is changed by admin - event NewAccessControl(address oldAccessControlAddress, address newAccessControlAddress); - - /// @notice Emitted when the borrowing delegate rights are updated for an account - event DelegateUpdated(address borrower, address delegate, bool allowDelegatedBorrows); - - /// @notice Emitted when forced liquidation is enabled or disabled for a market - event IsForcedLiquidationEnabledUpdated(address indexed vToken, bool enable); - - /// @notice The initial Venus index for a market - uint224 public constant venusInitialIndex = 1e36; - - // closeFactorMantissa must be strictly greater than this value - uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05 - - // closeFactorMantissa must not exceed this value - uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 - - constructor() public { - admin = msg.sender; - } - - /// @notice Reverts if the protocol is paused - function checkProtocolPauseState() private view { - require(!protocolPaused, "protocol is paused"); - } - - /// @notice Reverts if a certain action is paused on a market - function checkActionPauseState(address market, Action action) private view { - require(!actionPaused(market, action), "action is paused"); - } - - /// @notice Reverts if the caller is not admin - function ensureAdmin() private view { - require(msg.sender == admin, "only admin can"); - } - - /// @notice Checks the passed address is nonzero - function ensureNonzeroAddress(address someone) private pure { - require(someone != address(0), "can't be zero address"); - } - - /// @notice Reverts if the market is not listed - function ensureListed(Market storage market) private view { - require(market.isListed, "market not listed"); - } - - /// @notice Reverts if the caller is neither admin nor the passed address - function ensureAdminOr(address privilegedAddress) private view { - require(msg.sender == admin || msg.sender == privilegedAddress, "access denied"); - } - - function ensureAllowed(string memory functionSig) private view { - require(IAccessControlManager(accessControl).isAllowedToCall(msg.sender, functionSig), "access denied"); - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (VToken[] memory) { - return accountAssets[account]; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param vToken The vToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, VToken vToken) external view returns (bool) { - return markets[address(vToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param vTokens The list of addresses of the vToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory) { - uint len = vTokens.length; - - uint[] memory results = new uint[](len); - for (uint i; i < len; ++i) { - results[i] = uint(addToMarketInternal(VToken(vTokens[i]), msg.sender)); - } - - return results; - } - - /** - * @notice Add the market to the borrower's "assets in" for liquidity calculations - * @param vToken The market to enter - * @param borrower The address of the account to modify - * @return Success indicator for whether the market was entered - */ - function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { - checkActionPauseState(address(vToken), Action.ENTER_MARKET); - - Market storage marketToJoin = markets[address(vToken)]; - ensureListed(marketToJoin); - - if (marketToJoin.accountMembership[borrower]) { - // already joined - return Error.NO_ERROR; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[borrower] = true; - accountAssets[borrower].push(vToken); - - emit MarketEntered(vToken, borrower); - - return Error.NO_ERROR; - } - - /** - * @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 vTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address vTokenAddress) external returns (uint) { - checkActionPauseState(vTokenAddress, Action.EXIT_MARKET); - - VToken vToken = VToken(vTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the vToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = vToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(vToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set vToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete vToken from the account’s list of assets */ - // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 - VToken[] storage userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint i; - for (; i < len; ++i) { - if (userAssetList[i] == vToken) { - userAssetList[i] = userAssetList[len - 1]; - userAssetList.length--; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(i < len); - - emit MarketExited(vToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Grants or revokes the borrowing delegate rights to / from an account. - * If allowed, the delegate will be able to borrow funds on behalf of the sender. - * Upon a delegated borrow, the delegate will receive the funds, and the borrower - * will see the debt on their account. - * @param delegate The address to update the rights for - * @param allowBorrows Whether to grant (true) or revoke (false) the rights - */ - function updateDelegate(address delegate, bool allowBorrows) external { - _updateDelegate(msg.sender, delegate, allowBorrows); - } - - function _updateDelegate(address borrower, address delegate, bool allowBorrows) internal { - approvedDelegates[borrower][delegate] = allowBorrows; - emit DelegateUpdated(borrower, delegate, allowBorrows); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param vToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.MINT); - ensureListed(markets[vToken]); - - uint256 supplyCap = supplyCaps[vToken]; - require(supplyCap != 0, "market supply cap is 0"); - - uint256 vTokenSupply = VToken(vToken).totalSupply(); - Exp memory exchangeRate = Exp({ mantissa: VToken(vToken).exchangeRateStored() }); - uint256 nextTotalSupply = mul_ScalarTruncateAddUInt(exchangeRate, vTokenSupply, mintAmount); - require(nextTotalSupply <= supplyCap, "market supply cap reached"); - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, minter); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates mint and reverts on rejection. May emit logs. - * @param vToken Asset being minted - * @param minter The address minting the tokens - * @param actualMintAmount The amount of the underlying asset being minted - * @param mintTokens The number of tokens being minted - */ - function mintVerify(address vToken, address minter, uint actualMintAmount, uint mintTokens) external {} - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param vToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint) { - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.REDEEM); - - uint allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, redeemer); - - return uint(Error.NO_ERROR); - } - - function redeemAllowedInternal(address vToken, address redeemer, uint redeemTokens) internal view returns (uint) { - ensureListed(markets[vToken]); - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[vToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - redeemer, - VToken(vToken), - redeemTokens, - 0 - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates redeem and reverts on rejection. May emit logs. - * @param vToken Asset being redeemed - * @param redeemer The address redeeming the tokens - * @param redeemAmount The amount of the underlying asset being redeemed - * @param redeemTokens The number of tokens being redeemed - */ - // solhint-disable-next-line no-unused-vars - function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external { - require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param vToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.BORROW); - - ensureListed(markets[vToken]); - - if (!markets[vToken].accountMembership[borrower]) { - // only vTokens may call borrowAllowed if borrower not in market - require(msg.sender == vToken, "sender must be vToken"); - - // attempt to add borrower to the market - Error err = addToMarketInternal(VToken(vToken), borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - } - - if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - uint borrowCap = borrowCaps[vToken]; - // Borrow cap of 0 corresponds to unlimited borrowing - if (borrowCap != 0) { - uint nextTotalBorrows = add_(VToken(vToken).totalBorrows(), borrowAmount); - require(nextTotalBorrows < borrowCap, "market borrow cap reached"); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - borrower, - VToken(vToken), - 0, - borrowAmount - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates borrow and reverts on rejection. May emit logs. - * @param vToken Asset whose underlying is being borrowed - * @param borrower The address borrowing the underlying - * @param borrowAmount The amount of the underlying asset requested to borrow - */ - function borrowVerify(address vToken, address borrower, uint borrowAmount) external {} - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param vToken The market to verify the repay against - * @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 account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address vToken, - // solhint-disable-next-line no-unused-vars - address payer, - address borrower, - // solhint-disable-next-line no-unused-vars - uint repayAmount - ) external returns (uint) { - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.REPAY); - ensureListed(markets[vToken]); - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates repayBorrow and reverts on rejection. May emit logs. - * @param vToken Asset being repaid - * @param payer The address repaying the borrow - * @param borrower The address of the borrower - * @param actualRepayAmount The amount of underlying being repaid - */ - function repayBorrowVerify( - address vToken, - address payer, - address borrower, - uint actualRepayAmount, - uint borrowerIndex - ) external {} - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external returns (uint) { - checkProtocolPauseState(); - - // if we want to pause liquidating to vTokenCollateral, we should pause seizing - checkActionPauseState(vTokenBorrowed, Action.LIQUIDATE); - - if (liquidatorContract != address(0) && liquidator != liquidatorContract) { - return uint(Error.UNAUTHORIZED); - } - - ensureListed(markets[vTokenCollateral]); - - uint borrowBalance; - if (address(vTokenBorrowed) != address(vaiController)) { - ensureListed(markets[vTokenBorrowed]); - borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); - } else { - borrowBalance = vaiController.getVAIRepayAmount(borrower); - } - - if (isForcedLiquidationEnabled[vTokenBorrowed]) { - if (repayAmount > borrowBalance) { - return uint(Error.TOO_MUCH_REPAY); - } - return uint(Error.NO_ERROR); - } - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(0), 0, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - // The liquidator may not repay more than what is allowed by the closeFactor - // maxClose = multipy of closeFactorMantissa and borrowBalance - if (repayAmount > mul_ScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance)) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 actualRepayAmount The amount of underlying being repaid - * @param seizeTokens The amount of collateral token that will be seized - */ - function liquidateBorrowVerify( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint actualRepayAmount, - uint seizeTokens - ) external {} - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens // solhint-disable-line no-unused-vars - ) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vTokenCollateral, Action.SEIZE); - - // We've added VAIController as a borrowed token list check for seize - ensureListed(markets[vTokenCollateral]); - if (address(vTokenBorrowed) != address(vaiController)) { - ensureListed(markets[vTokenBorrowed]); - } - - if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vTokenCollateral); - distributeSupplierVenus(vTokenCollateral, borrower); - distributeSupplierVenus(vTokenCollateral, liquidator); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates seize and reverts on rejection. May emit logs. - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 seizeVerify( - address vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external {} - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param vToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.TRANSFER); - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - uint allowed = redeemAllowedInternal(vToken, src, transferTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, src); - distributeSupplierVenus(vToken, dst); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates transfer and reverts on rejection. May emit logs. - * @param vToken Asset being transferred - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - */ - function transferVerify(address vToken, address src, address dst, uint transferTokens) external {} - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) external view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, VToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @return (possible error code (semi-opaque), - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidity( - address account, - address vTokenModify, - uint redeemTokens, - uint borrowAmount - ) external view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal( - account, - VToken(vTokenModify), - redeemTokens, - borrowAmount - ); - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidityInternal( - address account, - VToken vTokenModify, - uint redeemTokens, - uint borrowAmount - ) internal view returns (Error, uint, uint) { - (uint err, uint liquidity, uint shortfall) = comptrollerLens.getHypotheticalAccountLiquidity( - address(this), - account, - vTokenModify, - redeemTokens, - borrowAmount - ); - return (Error(err), liquidity, shortfall); - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenBorrowed The address of the borrowed vToken - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - (uint err, uint seizeTokens) = comptrollerLens.liquidateCalculateSeizeTokens( - address(this), - vTokenBorrowed, - vTokenCollateral, - actualRepayAmount - ); - return (err, seizeTokens); - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateVAICalculateSeizeTokens( - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - (uint err, uint seizeTokens) = comptrollerLens.liquidateVAICalculateSeizeTokens( - address(this), - vTokenCollateral, - actualRepayAmount - ); - return (err, seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) external returns (uint) { - // Check caller is admin - ensureAdmin(); - ensureNonzeroAddress(address(newOracle)); - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise will revert - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) { - // Check caller is admin - ensureAdmin(); - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the address of the access control of this contract - * @dev Admin function to set the access control address - * @param newAccessControlAddress New address for the access control - * @return uint 0=success, otherwise will revert - */ - function _setAccessControl(address newAccessControlAddress) external returns (uint) { - // Check caller is admin - ensureAdmin(); - ensureNonzeroAddress(newAccessControlAddress); - - address oldAccessControlAddress = accessControl; - accessControl = newAccessControlAddress; - emit NewAccessControl(oldAccessControlAddress, accessControl); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Restricted function to set per-market collateralFactor - * @param vToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(VToken vToken, uint newCollateralFactorMantissa) external returns (uint) { - // Check caller is allowed by access control manager - ensureAllowed("_setCollateralFactor(address,uint256)"); - ensureNonzeroAddress(address(vToken)); - - // Verify market is listed - Market storage market = markets[address(vToken)]; - ensureListed(market); - - Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { - return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - ensureAllowed("_setLiquidationIncentive(uint256)"); - - require(newLiquidationIncentiveMantissa >= 1e18, "incentive < 1e18"); - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - function _setLiquidatorContract(address newLiquidatorContract_) external { - // Check caller is admin - ensureAdmin(); - address oldLiquidatorContract = liquidatorContract; - liquidatorContract = newLiquidatorContract_; - emit NewLiquidatorContract(oldLiquidatorContract, newLiquidatorContract_); - } - - /** - * @notice Enables forced liquidations for a market. If forced liquidation is enabled, - * borrows in the market may be liquidated regardless of the account liquidity - * @param vTokenBorrowed Borrowed vToken - * @param enable Whether to enable forced liquidations - */ - function _setForcedLiquidation(address vTokenBorrowed, bool enable) external { - ensureAllowed("_setForcedLiquidation(address,bool)"); - if (vTokenBorrowed != address(vaiController)) { - ensureListed(markets[vTokenBorrowed]); - } - isForcedLiquidationEnabled[address(vTokenBorrowed)] = enable; - emit IsForcedLiquidationEnabledUpdated(vTokenBorrowed, enable); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param vToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(VToken vToken) external returns (uint) { - ensureAllowed("_supportMarket(address)"); - - if (markets[address(vToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - vToken.isVToken(); // Sanity check to make sure its really a VToken - - // Note that isVenus is not in active use anymore - markets[address(vToken)] = Market({ isListed: true, isVenus: false, collateralFactorMantissa: 0 }); - - _addMarketInternal(vToken); - _initializeMarket(address(vToken)); - - emit MarketListed(vToken); - - return uint(Error.NO_ERROR); - } - - function _addMarketInternal(VToken vToken) internal { - for (uint i; i < allMarkets.length; ++i) { - require(allMarkets[i] != vToken, "already added"); - } - allMarkets.push(vToken); - } - - function _initializeMarket(address vToken) internal { - uint32 blockNumber = getBlockNumberAsUint32(); - - VenusMarketState storage supplyState = venusSupplyState[vToken]; - VenusMarketState storage borrowState = venusBorrowState[vToken]; - - /* - * Update market state indices - */ - if (supplyState.index == 0) { - // Initialize supply state index with default value - supplyState.index = venusInitialIndex; - } - - if (borrowState.index == 0) { - // Initialize borrow state index with default value - borrowState.index = venusInitialIndex; - } - - /* - * Update market state block numbers - */ - supplyState.block = borrowState.block = blockNumber; - } - - /** - * @notice Admin function to change the Pause Guardian - * @param newPauseGuardian The address of the new Pause Guardian - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _setPauseGuardian(address newPauseGuardian) external returns (uint) { - ensureAdmin(); - ensureNonzeroAddress(newPauseGuardian); - - // Save current value for inclusion in log - address oldPauseGuardian = pauseGuardian; - - // Store pauseGuardian with value newPauseGuardian - pauseGuardian = newPauseGuardian; - - // Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian) - emit NewPauseGuardian(oldPauseGuardian, newPauseGuardian); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Set the given borrow caps for the given vToken markets. Borrowing that brings total borrows to or above borrow cap will revert. - * @dev Access is controled by ACM. A borrow cap of 0 corresponds to unlimited borrowing. - * @param vTokens The addresses of the markets (tokens) to change the borrow caps for - * @param newBorrowCaps The new borrow cap values in underlying to be set. A value of 0 corresponds to unlimited borrowing. - */ - function _setMarketBorrowCaps(VToken[] calldata vTokens, uint[] calldata newBorrowCaps) external { - ensureAllowed("_setMarketBorrowCaps(address[],uint256[])"); - - uint numMarkets = vTokens.length; - uint numBorrowCaps = newBorrowCaps.length; - - require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input"); - - for (uint i; i < numMarkets; ++i) { - borrowCaps[address(vTokens[i])] = newBorrowCaps[i]; - emit NewBorrowCap(vTokens[i], newBorrowCaps[i]); - } - } - - /** - * @notice Set the given supply caps for the given vToken markets. Supply that brings total Supply to or above supply cap will revert. - * @dev Admin function to set the supply caps. A supply cap of 0 corresponds to Minting NotAllowed. - * @param vTokens The addresses of the markets (tokens) to change the supply caps for - * @param newSupplyCaps The new supply cap values in underlying to be set. A value of 0 corresponds to Minting NotAllowed. - */ - function _setMarketSupplyCaps(VToken[] calldata vTokens, uint256[] calldata newSupplyCaps) external { - ensureAllowed("_setMarketSupplyCaps(address[],uint256[])"); - - uint numMarkets = vTokens.length; - uint numSupplyCaps = newSupplyCaps.length; - - require(numMarkets != 0 && numMarkets == numSupplyCaps, "invalid input"); - - for (uint i; i < numMarkets; ++i) { - supplyCaps[address(vTokens[i])] = newSupplyCaps[i]; - emit NewSupplyCap(vTokens[i], newSupplyCaps[i]); - } - } - - /** - * @notice Set whole protocol pause/unpause state - */ - function _setProtocolPaused(bool state) external returns (bool) { - ensureAllowed("_setProtocolPaused(bool)"); - - protocolPaused = state; - emit ActionProtocolPaused(state); - return state; - } - - /** - * @notice Pause/unpause certain actions - * @param markets Markets to pause/unpause the actions on - * @param actions List of action ids to pause/unpause - * @param paused The new paused state (true=paused, false=unpaused) - */ - function _setActionsPaused(address[] calldata markets, Action[] calldata actions, bool paused) external { - ensureAllowed("_setActionsPaused(address[],uint256[],bool)"); - - uint256 numMarkets = markets.length; - uint256 numActions = actions.length; - for (uint marketIdx; marketIdx < numMarkets; ++marketIdx) { - for (uint actionIdx; actionIdx < numActions; ++actionIdx) { - setActionPausedInternal(markets[marketIdx], actions[actionIdx], paused); - } - } - } - - /** - * @dev Pause/unpause an action on a market - * @param market Market to pause/unpause the action on - * @param action Action id to pause/unpause - * @param paused The new paused state (true=paused, false=unpaused) - */ - function setActionPausedInternal(address market, Action action, bool paused) internal { - ensureListed(markets[market]); - _actionPaused[market][uint(action)] = paused; - emit ActionPausedMarket(VToken(market), action, paused); - } - - /** - * @notice Sets a new VAI controller - * @dev Admin function to set a new VAI controller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setVAIController(VAIControllerInterface vaiController_) external returns (uint) { - // Check caller is admin - ensureAdmin(); - ensureNonzeroAddress(address(vaiController_)); - - VAIControllerInterface oldVaiController = vaiController; - vaiController = vaiController_; - emit NewVAIController(oldVaiController, vaiController_); - - return uint(Error.NO_ERROR); - } - - function _setVAIMintRate(uint newVAIMintRate) external returns (uint) { - // Check caller is admin - ensureAdmin(); - uint oldVAIMintRate = vaiMintRate; - vaiMintRate = newVAIMintRate; - emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); - - return uint(Error.NO_ERROR); - } - - function _setTreasuryData( - address newTreasuryGuardian, - address newTreasuryAddress, - uint newTreasuryPercent - ) external returns (uint) { - // Check caller is admin - ensureAdminOr(treasuryGuardian); - - require(newTreasuryPercent < 1e18, "percent >= 100%"); - ensureNonzeroAddress(newTreasuryGuardian); - ensureNonzeroAddress(newTreasuryAddress); - - address oldTreasuryGuardian = treasuryGuardian; - address oldTreasuryAddress = treasuryAddress; - uint oldTreasuryPercent = treasuryPercent; - - treasuryGuardian = newTreasuryGuardian; - treasuryAddress = newTreasuryAddress; - treasuryPercent = newTreasuryPercent; - - emit NewTreasuryGuardian(oldTreasuryGuardian, newTreasuryGuardian); - emit NewTreasuryAddress(oldTreasuryAddress, newTreasuryAddress); - emit NewTreasuryPercent(oldTreasuryPercent, newTreasuryPercent); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller) external { - require(msg.sender == unitroller.admin(), "only unitroller admin can"); - require(unitroller._acceptImplementation() == 0, "not authorized"); - } - - /*** Venus Distribution ***/ - - function setVenusSpeedInternal(VToken vToken, uint supplySpeed, uint borrowSpeed) internal { - ensureListed(markets[address(vToken)]); - - if (venusSupplySpeeds[address(vToken)] != supplySpeed) { - // Supply speed updated so let's update supply state to ensure that - // 1. XVS accrued properly for the old speed, and - // 2. XVS accrued at the new speed starts after this block. - - updateVenusSupplyIndex(address(vToken)); - // Update speed and emit event - venusSupplySpeeds[address(vToken)] = supplySpeed; - emit VenusSupplySpeedUpdated(vToken, supplySpeed); - } - - if (venusBorrowSpeeds[address(vToken)] != borrowSpeed) { - // Borrow speed updated so let's update borrow state to ensure that - // 1. XVS accrued properly for the old speed, and - // 2. XVS accrued at the new speed starts after this block. - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - - // Update speed and emit event - venusBorrowSpeeds[address(vToken)] = borrowSpeed; - emit VenusBorrowSpeedUpdated(vToken, borrowSpeed); - } - } - - /** - * @dev Set ComptrollerLens contract address - */ - function _setComptrollerLens(ComptrollerLensInterface comptrollerLens_) external returns (uint) { - ensureAdmin(); - ensureNonzeroAddress(address(comptrollerLens_)); - address oldComptrollerLens = address(comptrollerLens); - comptrollerLens = comptrollerLens_; - emit NewComptrollerLens(oldComptrollerLens, address(comptrollerLens)); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Accrue XVS to the market by updating the supply index - * @param vToken The market whose supply index to update - */ - function updateVenusSupplyIndex(address vToken) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - uint supplySpeed = venusSupplySpeeds[vToken]; - uint32 blockNumber = getBlockNumberAsUint32(); - uint deltaBlocks = sub_(uint(blockNumber), uint(supplyState.block)); - if (deltaBlocks > 0 && supplySpeed > 0) { - uint supplyTokens = VToken(vToken).totalSupply(); - uint venusAccrued = mul_(deltaBlocks, supplySpeed); - Double memory ratio = supplyTokens > 0 ? fraction(venusAccrued, supplyTokens) : Double({ mantissa: 0 }); - supplyState.index = safe224(add_(Double({ mantissa: supplyState.index }), ratio).mantissa, "224"); - supplyState.block = blockNumber; - } else if (deltaBlocks > 0) { - supplyState.block = blockNumber; - } - } - - /** - * @notice Accrue XVS to the market by updating the borrow index - * @param vToken The market whose borrow index to update - */ - function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - uint borrowSpeed = venusBorrowSpeeds[vToken]; - uint32 blockNumber = getBlockNumberAsUint32(); - uint deltaBlocks = sub_(uint(blockNumber), uint(borrowState.block)); - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); - uint venusAccrued = mul_(deltaBlocks, borrowSpeed); - Double memory ratio = borrowAmount > 0 ? fraction(venusAccrued, borrowAmount) : Double({ mantissa: 0 }); - borrowState.index = safe224(add_(Double({ mantissa: borrowState.index }), ratio).mantissa, "224"); - borrowState.block = blockNumber; - } else if (deltaBlocks > 0) { - borrowState.block = blockNumber; - } - } - - /** - * @notice Calculate XVS accrued by a supplier and possibly transfer it to them - * @param vToken The market in which the supplier is interacting - * @param supplier The address of the supplier to distribute XVS to - */ - function distributeSupplierVenus(address vToken, address supplier) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - uint supplyIndex = venusSupplyState[vToken].index; - uint supplierIndex = venusSupplierIndex[vToken][supplier]; - - // Update supplier's index to the current index since we are distributing accrued XVS - venusSupplierIndex[vToken][supplier] = supplyIndex; - - if (supplierIndex == 0 && supplyIndex >= venusInitialIndex) { - // Covers the case where users supplied tokens before the market's supply state index was set. - // Rewards the user with XVS accrued from the start of when supplier rewards were first - // set for the market. - supplierIndex = venusInitialIndex; - } - - // Calculate change in the cumulative sum of the XVS per vToken accrued - Double memory deltaIndex = Double({ mantissa: sub_(supplyIndex, supplierIndex) }); - - // Multiply of supplierTokens and supplierDelta - uint supplierDelta = mul_(VToken(vToken).balanceOf(supplier), deltaIndex); - - // Addition of supplierAccrued and supplierDelta - venusAccrued[supplier] = add_(venusAccrued[supplier], supplierDelta); - - emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex); - } - - /** - * @notice Calculate XVS accrued by a borrower and possibly transfer it to them - * @dev Borrowers will not begin to accrue until after the first interaction with the protocol. - * @param vToken The market in which the borrower is interacting - * @param borrower The address of the borrower to distribute XVS to - */ - function distributeBorrowerVenus(address vToken, address borrower, Exp memory marketBorrowIndex) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - uint borrowIndex = venusBorrowState[vToken].index; - uint borrowerIndex = venusBorrowerIndex[vToken][borrower]; - - // Update borrowers's index to the current index since we are distributing accrued XVS - venusBorrowerIndex[vToken][borrower] = borrowIndex; - - if (borrowerIndex == 0 && borrowIndex >= venusInitialIndex) { - // Covers the case where users borrowed tokens before the market's borrow state index was set. - // Rewards the user with XVS accrued from the start of when borrower rewards were first - // set for the market. - borrowerIndex = venusInitialIndex; - } - - // Calculate change in the cumulative sum of the XVS per borrowed unit accrued - Double memory deltaIndex = Double({ mantissa: sub_(borrowIndex, borrowerIndex) }); - - uint borrowerDelta = mul_(div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex), deltaIndex); - - venusAccrued[borrower] = add_(venusAccrued[borrower], borrowerDelta); - - emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex); - } - - /** - * @notice Claim all the xvs accrued by holder in all markets and VAI - * @param holder The address to claim XVS for - */ - function claimVenus(address holder) public { - return claimVenus(holder, allMarkets); - } - - /** - * @notice Claim all the xvs accrued by holder in the specified markets - * @param holder The address to claim XVS for - * @param vTokens The list of markets to claim XVS in - */ - function claimVenus(address holder, VToken[] memory vTokens) public { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, vTokens, true, true); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - */ - function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { - claimVenus(holders, vTokens, borrowers, suppliers, false); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - * @param collateral Whether or not to use XVS earned as collateral, only takes effect when the holder has a shortfall - */ - function claimVenus( - address[] memory holders, - VToken[] memory vTokens, - bool borrowers, - bool suppliers, - bool collateral - ) public { - uint j; - uint256 holdersLength = holders.length; - for (uint i; i < vTokens.length; ++i) { - VToken vToken = vTokens[i]; - ensureListed(markets[address(vToken)]); - if (borrowers) { - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - for (j = 0; j < holdersLength; ++j) { - distributeBorrowerVenus(address(vToken), holders[j], borrowIndex); - } - } - if (suppliers) { - updateVenusSupplyIndex(address(vToken)); - for (j = 0; j < holdersLength; ++j) { - distributeSupplierVenus(address(vToken), holders[j]); - } - } - } - - for (j = 0; j < holdersLength; ++j) { - address holder = holders[j]; - // If there is a positive shortfall, the XVS reward is accrued, - // but won't be granted to this holder - (, , uint shortfall) = getHypotheticalAccountLiquidityInternal(holder, VToken(0), 0, 0); - venusAccrued[holder] = grantXVSInternal(holder, venusAccrued[holder], shortfall, collateral); - } - } - - /** - * @notice Claim all the xvs accrued by holder in all markets, a shorthand for `claimVenus` with collateral set to `true` - * @param holder The address to claim XVS for - */ - function claimVenusAsCollateral(address holder) external { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, allMarkets, true, true, true); - } - - /** - * @notice Transfer XVS to the user with user's shortfall considered - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param user The address of the user to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - * @param shortfall The shortfall of the user - * @param collateral Whether or not we will use user's venus reward as collateral to pay off the debt - * @return The amount of XVS which was NOT transferred to the user - */ - function grantXVSInternal(address user, uint amount, uint shortfall, bool collateral) internal returns (uint) { - // If the user is blacklisted, they can't get XVS rewards - require( - user != 0xEF044206Db68E40520BfA82D45419d498b4bc7Bf && - user != 0x7589dD3355DAE848FDbF75044A3495351655cB1A && - user != 0x33df7a7F6D44307E1e5F3B15975b47515e5524c0 && - user != 0x24e77E5b74B30b026E9996e4bc3329c881e24968, - "Blacklisted" - ); - - XVS xvs = XVS(getXVSAddress()); - - if (amount == 0 || amount > xvs.balanceOf(address(this))) { - return amount; - } - - if (shortfall == 0) { - xvs.transfer(user, amount); - return 0; - } - // If user's bankrupt and doesn't use pending xvs as collateral, don't grant - // anything, otherwise, we will transfer the pending xvs as collateral to - // vXVS token and mint vXVS for the user. - // - // If mintBehalf failed, don't grant any xvs - require(collateral, "bankrupt"); - - xvs.approve(getXVSVTokenAddress(), amount); - require( - VBep20Interface(getXVSVTokenAddress()).mintBehalf(user, amount) == uint(Error.NO_ERROR), - "mint behalf error" - ); - - // set venusAccrued[user] to 0 - return 0; - } - - /*** Venus Distribution Admin ***/ - - /** - * @notice Transfer XVS to the recipient - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param recipient The address of the recipient to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - */ - function _grantXVS(address recipient, uint amount) external { - ensureAdminOr(comptrollerImplementation); - uint amountLeft = grantXVSInternal(recipient, amount, 0, false); - require(amountLeft == 0, "no xvs"); - emit VenusGranted(recipient, amount); - } - - /** - * @notice Set the amount of XVS distributed per block to VAI Vault - * @param venusVAIVaultRate_ The amount of XVS wei per block to distribute to VAI Vault - */ - function _setVenusVAIVaultRate(uint venusVAIVaultRate_) external { - ensureAdmin(); - - uint oldVenusVAIVaultRate = venusVAIVaultRate; - venusVAIVaultRate = venusVAIVaultRate_; - emit NewVenusVAIVaultRate(oldVenusVAIVaultRate, venusVAIVaultRate_); - } - - /** - * @notice Set the VAI Vault infos - * @param vault_ The address of the VAI Vault - * @param releaseStartBlock_ The start block of release to VAI Vault - * @param minReleaseAmount_ The minimum release amount to VAI Vault - */ - function _setVAIVaultInfo(address vault_, uint256 releaseStartBlock_, uint256 minReleaseAmount_) external { - ensureAdmin(); - ensureNonzeroAddress(vault_); - - vaiVaultAddress = vault_; - releaseStartBlock = releaseStartBlock_; - minReleaseAmount = minReleaseAmount_; - emit NewVAIVaultInfo(vault_, releaseStartBlock_, minReleaseAmount_); - } - - /** - * @notice Set XVS speed for a single market - * @param vTokens The market whose XVS speed to update - * @param supplySpeeds New XVS speed for supply - * @param borrowSpeeds New XVS speed for borrow - */ - function _setVenusSpeeds( - VToken[] calldata vTokens, - uint[] calldata supplySpeeds, - uint[] calldata borrowSpeeds - ) external { - ensureAdminOr(comptrollerImplementation); - - uint numTokens = vTokens.length; - require(numTokens == supplySpeeds.length && numTokens == borrowSpeeds.length, "invalid input"); - - for (uint i; i < numTokens; ++i) { - ensureNonzeroAddress(address(vTokens[i])); - setVenusSpeedInternal(vTokens[i], supplySpeeds[i], borrowSpeeds[i]); - } - } - - /** - * @notice Return all of the markets - * @dev The automatic getter may be used to access an individual market. - * @return The list of market addresses - */ - function getAllMarkets() external view returns (VToken[] memory) { - return allMarkets; - } - - function getBlockNumber() internal view returns (uint) { - return block.number; - } - - /** - * @notice Return the address of the XVS token - * @return The address of XVS - */ - function getXVSAddress() public view returns (address) { - return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; - } - - /** - * @notice Return the address of the XVS vToken - * @return The address of XVS vToken - */ - function getXVSVTokenAddress() public view returns (address) { - return 0x151B1e2635A717bcDc836ECd6FbB62B674FE3E1D; - } - - /** - * @notice Checks if a certain action is paused on a market - * @param action Action id - * @param market vToken address - */ - function actionPaused(address market, Action action) public view returns (bool) { - return _actionPaused[market][uint(action)]; - } - - /*** VAI functions ***/ - - /** - * @notice Set the minted VAI amount of the `owner` - * @param owner The address of the account to set - * @param amount The amount of VAI to set to the account - * @return The number of minted VAI by `owner` - */ - function setMintedVAIOf(address owner, uint amount) external returns (uint) { - checkProtocolPauseState(); - - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); - // Check caller is vaiController - if (msg.sender != address(vaiController)) { - return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); - } - mintedVAIs[owner] = amount; - - return uint(Error.NO_ERROR); - } - - /** - * @notice Transfer XVS to VAI Vault - */ - function releaseToVault() internal { - if (releaseStartBlock == 0 || getBlockNumber() < releaseStartBlock) { - return; - } - - XVS xvs = XVS(getXVSAddress()); - - uint256 xvsBalance = xvs.balanceOf(address(this)); - if (xvsBalance == 0) { - return; - } - - uint256 actualAmount; - uint256 deltaBlocks = sub_(getBlockNumber(), releaseStartBlock); - // releaseAmount = venusVAIVaultRate * deltaBlocks - uint256 _releaseAmount = mul_(venusVAIVaultRate, deltaBlocks); - - if (xvsBalance >= _releaseAmount) { - actualAmount = _releaseAmount; - } else { - actualAmount = xvsBalance; - } - - if (actualAmount < minReleaseAmount) { - return; - } - - releaseStartBlock = getBlockNumber(); - - xvs.transfer(vaiVaultAddress, actualAmount); - emit DistributedVAIVaultVenus(actualAmount); - - IVAIVault(vaiVaultAddress).updatePendingRewards(); - } - - function getBlockNumberAsUint32() internal view returns (uint32) { - return safe32(getBlockNumber(), "block # > 32 bits"); - } -} diff --git a/contracts/Comptroller/ComptrollerG1.sol b/contracts/Comptroller/ComptrollerG1.sol deleted file mode 100644 index 69cbee4a1..000000000 --- a/contracts/Comptroller/ComptrollerG1.sol +++ /dev/null @@ -1,1605 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VTokens/VToken.sol"; -import "../Utils/ErrorReporter.sol"; -import "../Utils/Exponential.sol"; -import "../Tokens/XVS/XVS.sol"; -import "../Tokens/VAI/VAI.sol"; -import "./ComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Venus's Comptroller Contract - * @author Venus - */ -contract ComptrollerG1 is ComptrollerV1Storage, ComptrollerInterfaceG1, ComptrollerErrorReporter, Exponential { - /// @notice Emitted when an admin supports a market - event MarketListed(VToken vToken); - - /// @notice Emitted when an account enters a market - event MarketEntered(VToken vToken, address account); - - /// @notice Emitted when an account exits a market - event MarketExited(VToken vToken, address account); - - /// @notice Emitted when close factor is changed by admin - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /// @notice Emitted when a collateral factor is changed by admin - event NewCollateralFactor(VToken vToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /// @notice Emitted when liquidation incentive is changed by admin - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /// @notice Emitted when maxAssets is changed by admin - event NewMaxAssets(uint oldMaxAssets, uint newMaxAssets); - - /// @notice Emitted when price oracle is changed - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /// @notice Emitted when pause guardian is changed - event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); - - /// @notice Emitted when an action is paused globally - event ActionPaused(string action, bool pauseState); - - /// @notice Emitted when an action is paused on a market - event ActionPaused(VToken vToken, string action, bool pauseState); - - /// @notice Emitted when market venus status is changed - event MarketVenus(VToken vToken, bool isVenus); - - /// @notice Emitted when Venus rate is changed - event NewVenusRate(uint oldVenusRate, uint newVenusRate); - - /// @notice Emitted when a new Venus speed is calculated for a market - event VenusSpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when XVS is distributed to a supplier - event DistributedSupplierVenus( - VToken indexed vToken, - address indexed supplier, - uint venusDelta, - uint venusSupplyIndex - ); - - /// @notice Emitted when XVS is distributed to a borrower - event DistributedBorrowerVenus( - VToken indexed vToken, - address indexed borrower, - uint venusDelta, - uint venusBorrowIndex - ); - - /// @notice Emitted when XVS is distributed to a VAI minter - event DistributedVAIMinterVenus(address indexed vaiMinter, uint venusDelta, uint venusVAIMintIndex); - - /// @notice Emitted when VAIController is changed - event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); - - /// @notice Emitted when VAI mint rate is changed by admin - event NewVAIMintRate(uint oldVAIMintRate, uint newVAIMintRate); - - /// @notice Emitted when protocol state is changed by admin - event ActionProtocolPaused(bool state); - - /// @notice The threshold above which the flywheel transfers XVS, in wei - uint public constant venusClaimThreshold = 0.001e18; - - /// @notice The initial Venus index for a market - uint224 public constant venusInitialIndex = 1e36; - - // closeFactorMantissa must be strictly greater than this value - uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05 - - // closeFactorMantissa must not exceed this value - uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 - - // liquidationIncentiveMantissa must be no less than this value - uint internal constant liquidationIncentiveMinMantissa = 1.0e18; // 1.0 - - // liquidationIncentiveMantissa must be no greater than this value - uint internal constant liquidationIncentiveMaxMantissa = 1.5e18; // 1.5 - - constructor() public { - admin = msg.sender; - } - - modifier onlyProtocolAllowed() { - require(!protocolPaused, "protocol is paused"); - _; - } - - modifier onlyAdmin() { - require(msg.sender == admin, "only admin can"); - _; - } - - modifier onlyListedMarket(VToken vToken) { - require(markets[address(vToken)].isListed, "venus market is not listed"); - _; - } - - modifier validPauseState(bool state) { - require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can"); - require(msg.sender == admin || state == true, "only admin can unpause"); - _; - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (VToken[] memory) { - return accountAssets[account]; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param vToken The vToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, VToken vToken) external view returns (bool) { - return markets[address(vToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param vTokens The list of addresses of the vToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory) { - uint len = vTokens.length; - - uint[] memory results = new uint[](len); - for (uint i = 0; i < len; i++) { - results[i] = uint(addToMarketInternal(VToken(vTokens[i]), msg.sender)); - } - - return results; - } - - /** - * @notice Add the market to the borrower's "assets in" for liquidity calculations - * @param vToken The market to enter - * @param borrower The address of the account to modify - * @return Success indicator for whether the market was entered - */ - function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { - Market storage marketToJoin = markets[address(vToken)]; - - if (!marketToJoin.isListed) { - // market is not listed, cannot join - return Error.MARKET_NOT_LISTED; - } - - if (marketToJoin.accountMembership[borrower]) { - // already joined - return Error.NO_ERROR; - } - - if (accountAssets[borrower].length >= maxAssets) { - // no space, cannot join - return Error.TOO_MANY_ASSETS; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[borrower] = true; - accountAssets[borrower].push(vToken); - - emit MarketEntered(vToken, borrower); - - return Error.NO_ERROR; - } - - /** - * @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 vTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address vTokenAddress) external returns (uint) { - VToken vToken = VToken(vTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the vToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = vToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(vToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set vToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete vToken from the account’s list of assets */ - // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 - VToken[] storage userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint i; - for (; i < len; i++) { - if (userAssetList[i] == vToken) { - userAssetList[i] = userAssetList[len - 1]; - userAssetList.length--; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(i < len); - - emit MarketExited(vToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param vToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address vToken, address minter, uint mintAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintGuardianPaused[vToken], "mint is paused"); - - // Shh - currently unused - mintAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, minter, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates mint and reverts on rejection. May emit logs. - * @param vToken Asset being minted - * @param minter The address minting the tokens - * @param actualMintAmount The amount of the underlying asset being minted - * @param mintTokens The number of tokens being minted - */ - function mintVerify(address vToken, address minter, uint actualMintAmount, uint mintTokens) external { - // Shh - currently unused - vToken; - minter; - actualMintAmount; - mintTokens; - } - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param vToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed( - address vToken, - address redeemer, - uint redeemTokens - ) external onlyProtocolAllowed returns (uint) { - uint allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, redeemer, false); - - return uint(Error.NO_ERROR); - } - - function redeemAllowedInternal(address vToken, address redeemer, uint redeemTokens) internal view returns (uint) { - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[vToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - redeemer, - VToken(vToken), - redeemTokens, - 0 - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates redeem and reverts on rejection. May emit logs. - * @param vToken Asset being redeemed - * @param redeemer The address redeeming the tokens - * @param redeemAmount The amount of the underlying asset being redeemed - * @param redeemTokens The number of tokens being redeemed - */ - function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external { - // Shh - currently unused - vToken; - redeemer; - - // Require tokens is zero or amount is also zero - require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param vToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed( - address vToken, - address borrower, - uint borrowAmount - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!borrowGuardianPaused[vToken], "borrow is paused"); - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (!markets[vToken].accountMembership[borrower]) { - // only vTokens may call borrowAllowed if borrower not in market - require(msg.sender == vToken, "sender must be vToken"); - - // attempt to add borrower to the market - Error err = addToMarketInternal(VToken(vToken), borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - } - - if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - borrower, - VToken(vToken), - 0, - borrowAmount - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates borrow and reverts on rejection. May emit logs. - * @param vToken Asset whose underlying is being borrowed - * @param borrower The address borrowing the underlying - * @param borrowAmount The amount of the underlying asset requested to borrow - */ - function borrowVerify(address vToken, address borrower, uint borrowAmount) external { - // Shh - currently unused - vToken; - borrower; - borrowAmount; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param vToken The market to verify the repay against - * @param payer The account which would repay the asset - * @param borrower The account which would repay the asset - * @param repayAmount The amount of the underlying asset the account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address vToken, - address payer, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - payer; - borrower; - repayAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates repayBorrow and reverts on rejection. May emit logs. - * @param vToken Asset being repaid - * @param payer The address repaying the borrow - * @param borrower The address of the borrower - * @param actualRepayAmount The amount of underlying being repaid - */ - function repayBorrowVerify( - address vToken, - address payer, - address borrower, - uint actualRepayAmount, - uint borrowerIndex - ) external { - // Shh - currently unused - vToken; - payer; - borrower; - actualRepayAmount; - borrowerIndex; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - liquidator; - - if (!markets[vTokenBorrowed].isListed || !markets[vTokenCollateral].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(0), 0, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - /* The liquidator may not repay more than what is allowed by the closeFactor */ - uint borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); - (MathError mathErr, uint maxClose) = mulScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance); - if (mathErr != MathError.NO_ERROR) { - return uint(Error.MATH_ERROR); - } - if (repayAmount > maxClose) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 actualRepayAmount The amount of underlying being repaid - */ - function liquidateBorrowVerify( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint actualRepayAmount, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenBorrowed; - vTokenCollateral; - liquidator; - borrower; - actualRepayAmount; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!seizeGuardianPaused, "seize is paused"); - - // Shh - currently unused - seizeTokens; - - if (!markets[vTokenCollateral].isListed || !markets[vTokenBorrowed].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vTokenCollateral); - distributeSupplierVenus(vTokenCollateral, borrower, false); - distributeSupplierVenus(vTokenCollateral, liquidator, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates seize and reverts on rejection. May emit logs. - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 seizeVerify( - address vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenCollateral; - vTokenBorrowed; - liquidator; - borrower; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param vToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed( - address vToken, - address src, - address dst, - uint transferTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!transferGuardianPaused, "transfer is paused"); - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - uint allowed = redeemAllowedInternal(vToken, src, transferTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, src, false); - distributeSupplierVenus(vToken, dst, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates transfer and reverts on rejection. May emit logs. - * @param vToken Asset being transferred - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - */ - function transferVerify(address vToken, address src, address dst, uint transferTokens) external { - // Shh - currently unused - vToken; - src; - dst; - transferTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /*** Liquidity/Liquidation Calculations ***/ - - /** - * @dev Local vars for avoiding stack-depth limits in calculating account liquidity. - * Note that `vTokenBalance` is the number of vTokens the account owns in the market, - * whereas `borrowBalance` is the amount of underlying that the account has borrowed. - */ - struct AccountLiquidityLocalVars { - uint sumCollateral; - uint sumBorrowPlusEffects; - uint vTokenBalance; - uint borrowBalance; - uint exchangeRateMantissa; - uint oraclePriceMantissa; - Exp collateralFactor; - Exp exchangeRate; - Exp oraclePrice; - Exp tokensToDenom; - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, VToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @return (possible error code (semi-opaque), - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidity( - address account, - address vTokenModify, - uint redeemTokens, - uint borrowAmount - ) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal( - account, - VToken(vTokenModify), - redeemTokens, - borrowAmount - ); - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - // solhint-disable-next-line code-complexity - function getHypotheticalAccountLiquidityInternal( - address account, - VToken vTokenModify, - uint redeemTokens, - uint borrowAmount - ) internal view returns (Error, uint, uint) { - AccountLiquidityLocalVars memory vars; // Holds all our calculation results - uint oErr; - MathError mErr; - - // For each asset the account is in - VToken[] memory assets = accountAssets[account]; - for (uint i = 0; i < assets.length; i++) { - VToken asset = assets[i]; - - // Read the balances and exchange rate from the vToken - (oErr, vars.vTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot( - account - ); - if (oErr != 0) { - // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0); - } - vars.collateralFactor = Exp({ mantissa: markets[address(asset)].collateralFactorMantissa }); - vars.exchangeRate = Exp({ mantissa: vars.exchangeRateMantissa }); - - // Get the normalized price of the asset - vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset); - if (vars.oraclePriceMantissa == 0) { - return (Error.PRICE_ERROR, 0, 0); - } - vars.oraclePrice = Exp({ mantissa: vars.oraclePriceMantissa }); - - // Pre-compute a conversion factor from tokens -> bnb (normalized price value) - (mErr, vars.tokensToDenom) = mulExp3(vars.collateralFactor, vars.exchangeRate, vars.oraclePrice); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumCollateral += tokensToDenom * vTokenBalance - (mErr, vars.sumCollateral) = mulScalarTruncateAddUInt( - vars.tokensToDenom, - vars.vTokenBalance, - vars.sumCollateral - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumBorrowPlusEffects += oraclePrice * borrowBalance - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.oraclePrice, - vars.borrowBalance, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // Calculate effects of interacting with vTokenModify - if (asset == vTokenModify) { - // redeem effect - // sumBorrowPlusEffects += tokensToDenom * redeemTokens - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.tokensToDenom, - redeemTokens, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // borrow effect - // sumBorrowPlusEffects += oraclePrice * borrowAmount - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.oraclePrice, - borrowAmount, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - } - } - - /// @dev VAI Integration^ - (mErr, vars.sumBorrowPlusEffects) = addUInt(vars.sumBorrowPlusEffects, mintedVAIs[account]); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - /// @dev VAI Integration$ - - // These are safe, as the underflow condition is checked first - if (vars.sumCollateral > vars.sumBorrowPlusEffects) { - return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0); - } else { - return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral); - } - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenBorrowed The address of the borrowed vToken - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = oracle.getUnderlyingPrice(VToken(vTokenBorrowed)); - uint priceCollateralMantissa = oracle.getUnderlyingPrice(VToken(vTokenCollateral)); - if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - MathError mathErr; - - (mathErr, numerator) = mulExp(liquidationIncentiveMantissa, priceBorrowedMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, denominator) = mulExp(priceCollateralMantissa, exchangeRateMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, ratio) = divExp(numerator, denominator); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, seizeTokens) = mulScalarTruncate(ratio, actualRepayAmount); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) public returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK); - } - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_CLOSE_FACTOR_OWNER_CHECK); - } - - Exp memory newCloseFactorExp = Exp({ mantissa: newCloseFactorMantissa }); - Exp memory lowLimit = Exp({ mantissa: closeFactorMinMantissa }); - if (lessThanOrEqualExp(newCloseFactorExp, lowLimit)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - Exp memory highLimit = Exp({ mantissa: closeFactorMaxMantissa }); - if (lessThanExp(highLimit, newCloseFactorExp)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Admin function to set per-market collateralFactor - * @param vToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(VToken vToken, uint newCollateralFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK); - } - - // Verify market is listed - Market storage market = markets[address(vToken)]; - if (!market.isListed) { - return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS); - } - - Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { - return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets maxAssets which controls how many markets can be entered - * @dev Admin function to set maxAssets - * @param newMaxAssets New max assets - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setMaxAssets(uint newMaxAssets) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_MAX_ASSETS_OWNER_CHECK); - } - - uint oldMaxAssets = maxAssets; - maxAssets = newMaxAssets; - emit NewMaxAssets(oldMaxAssets, newMaxAssets); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK); - } - - // Check de-scaled min <= newLiquidationIncentive <= max - Exp memory newLiquidationIncentive = Exp({ mantissa: newLiquidationIncentiveMantissa }); - Exp memory minLiquidationIncentive = Exp({ mantissa: liquidationIncentiveMinMantissa }); - if (lessThanExp(newLiquidationIncentive, minLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - Exp memory maxLiquidationIncentive = Exp({ mantissa: liquidationIncentiveMaxMantissa }); - if (lessThanExp(maxLiquidationIncentive, newLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param vToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(VToken vToken) external returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SUPPORT_MARKET_OWNER_CHECK); - } - - if (markets[address(vToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - vToken.isVToken(); // Sanity check to make sure its really a VToken - - markets[address(vToken)] = Market({ isListed: true, isVenus: false, collateralFactorMantissa: 0 }); - - _addMarketInternal(vToken); - - emit MarketListed(vToken); - - return uint(Error.NO_ERROR); - } - - function _addMarketInternal(VToken vToken) internal { - for (uint i = 0; i < allMarkets.length; i++) { - require(allMarkets[i] != vToken, "market already added"); - } - allMarkets.push(vToken); - } - - /** - * @notice Admin function to change the Pause Guardian - * @param newPauseGuardian The address of the new Pause Guardian - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _setPauseGuardian(address newPauseGuardian) public returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PAUSE_GUARDIAN_OWNER_CHECK); - } - - // Save current value for inclusion in log - address oldPauseGuardian = pauseGuardian; - - // Store pauseGuardian with value newPauseGuardian - pauseGuardian = newPauseGuardian; - - // Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian) - emit NewPauseGuardian(oldPauseGuardian, newPauseGuardian); - - return uint(Error.NO_ERROR); - } - - function _setMintPaused( - VToken vToken, - bool state - ) public onlyListedMarket(vToken) validPauseState(state) returns (bool) { - mintGuardianPaused[address(vToken)] = state; - emit ActionPaused(vToken, "Mint", state); - return state; - } - - function _setBorrowPaused( - VToken vToken, - bool state - ) public onlyListedMarket(vToken) validPauseState(state) returns (bool) { - borrowGuardianPaused[address(vToken)] = state; - emit ActionPaused(vToken, "Borrow", state); - return state; - } - - function _setTransferPaused(bool state) public validPauseState(state) returns (bool) { - transferGuardianPaused = state; - emit ActionPaused("Transfer", state); - return state; - } - - function _setSeizePaused(bool state) public validPauseState(state) returns (bool) { - seizeGuardianPaused = state; - emit ActionPaused("Seize", state); - return state; - } - - function _setMintVAIPaused(bool state) public validPauseState(state) returns (bool) { - mintVAIGuardianPaused = state; - emit ActionPaused("MintVAI", state); - return state; - } - - function _setRepayVAIPaused(bool state) public validPauseState(state) returns (bool) { - repayVAIGuardianPaused = state; - emit ActionPaused("RepayVAI", state); - return state; - } - - /** - * @notice Set whole protocol pause/unpause state - */ - function _setProtocolPaused(bool state) public onlyAdmin returns (bool) { - protocolPaused = state; - emit ActionProtocolPaused(state); - return state; - } - - /** - * @notice Sets a new VAI controller - * @dev Admin function to set a new VAI controller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setVAIController(VAIControllerInterface vaiController_) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAICONTROLLER_OWNER_CHECK); - } - - VAIControllerInterface oldRate = vaiController; - vaiController = vaiController_; - emit NewVAIController(oldRate, vaiController_); - } - - function _setVAIMintRate(uint newVAIMintRate) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAI_MINT_RATE_CHECK); - } - - uint oldVAIMintRate = vaiMintRate; - vaiMintRate = newVAIMintRate; - emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller) public { - require(msg.sender == unitroller.admin(), "only unitroller admin can"); - require(unitroller._acceptImplementation() == 0, "not authorized"); - } - - /*** Venus Distribution ***/ - - /** - * @notice Recalculate and update Venus speeds for all Venus markets - */ - function refreshVenusSpeeds() public { - require(msg.sender == tx.origin, "only externally owned accounts can"); - refreshVenusSpeedsInternal(); - } - - function refreshVenusSpeedsInternal() internal { - uint i; - VToken vToken; - - for (i = 0; i < allMarkets.length; i++) { - vToken = allMarkets[i]; - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusSupplyIndex(address(vToken)); - updateVenusBorrowIndex(address(vToken), borrowIndex); - } - - Exp memory totalUtility = Exp({ mantissa: 0 }); - Exp[] memory utilities = new Exp[](allMarkets.length); - for (i = 0; i < allMarkets.length; i++) { - vToken = allMarkets[i]; - if (markets[address(vToken)].isVenus) { - Exp memory assetPrice = Exp({ mantissa: oracle.getUnderlyingPrice(vToken) }); - Exp memory utility = mul_(assetPrice, vToken.totalBorrows()); - utilities[i] = utility; - totalUtility = add_(totalUtility, utility); - } - } - - for (i = 0; i < allMarkets.length; i++) { - vToken = allMarkets[i]; - uint newSpeed = totalUtility.mantissa > 0 ? mul_(venusRate, div_(utilities[i], totalUtility)) : 0; - venusSpeeds[address(vToken)] = newSpeed; - emit VenusSpeedUpdated(vToken, newSpeed); - } - } - - /** - * @notice Accrue XVS to the market by updating the supply index - * @param vToken The market whose supply index to update - */ - function updateVenusSupplyIndex(address vToken) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - uint supplySpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(supplyState.block)); - if (deltaBlocks > 0 && supplySpeed > 0) { - uint supplyTokens = VToken(vToken).totalSupply(); - uint venusAccrued = mul_(deltaBlocks, supplySpeed); - Double memory ratio = supplyTokens > 0 ? fraction(venusAccrued, supplyTokens) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: supplyState.index }), ratio); - venusSupplyState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - supplyState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Accrue XVS to the market by updating the borrow index - * @param vToken The market whose borrow index to update - */ - function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - uint borrowSpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(borrowState.block)); - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); - uint venusAccrued = mul_(deltaBlocks, borrowSpeed); - Double memory ratio = borrowAmount > 0 ? fraction(venusAccrued, borrowAmount) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: borrowState.index }), ratio); - venusBorrowState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - borrowState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Accrue XVS to by updating the VAI minter index - */ - function updateVenusVAIMintIndex() internal { - if (address(vaiController) != address(0)) { - vaiController.updateVenusVAIMintIndex(); - } - } - - /** - * @notice Calculate XVS accrued by a supplier and possibly transfer it to them - * @param vToken The market in which the supplier is interacting - * @param supplier The address of the supplier to distribute XVS to - */ - function distributeSupplierVenus(address vToken, address supplier, bool distributeAll) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - Double memory supplyIndex = Double({ mantissa: supplyState.index }); - Double memory supplierIndex = Double({ mantissa: venusSupplierIndex[vToken][supplier] }); - venusSupplierIndex[vToken][supplier] = supplyIndex.mantissa; - - if (supplierIndex.mantissa == 0 && supplyIndex.mantissa > 0) { - supplierIndex.mantissa = venusInitialIndex; - } - - Double memory deltaIndex = sub_(supplyIndex, supplierIndex); - uint supplierTokens = VToken(vToken).balanceOf(supplier); - uint supplierDelta = mul_(supplierTokens, deltaIndex); - uint supplierAccrued = add_(venusAccrued[supplier], supplierDelta); - venusAccrued[supplier] = transferXVS(supplier, supplierAccrued, distributeAll ? 0 : venusClaimThreshold); - emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex.mantissa); - } - - /** - * @notice Calculate XVS accrued by a borrower and possibly transfer it to them - * @dev Borrowers will not begin to accrue until after the first interaction with the protocol. - * @param vToken The market in which the borrower is interacting - * @param borrower The address of the borrower to distribute XVS to - */ - function distributeBorrowerVenus( - address vToken, - address borrower, - Exp memory marketBorrowIndex, - bool distributeAll - ) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - Double memory borrowIndex = Double({ mantissa: borrowState.index }); - Double memory borrowerIndex = Double({ mantissa: venusBorrowerIndex[vToken][borrower] }); - venusBorrowerIndex[vToken][borrower] = borrowIndex.mantissa; - - if (borrowerIndex.mantissa > 0) { - Double memory deltaIndex = sub_(borrowIndex, borrowerIndex); - uint borrowerAmount = div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex); - uint borrowerDelta = mul_(borrowerAmount, deltaIndex); - uint borrowerAccrued = add_(venusAccrued[borrower], borrowerDelta); - venusAccrued[borrower] = transferXVS(borrower, borrowerAccrued, distributeAll ? 0 : venusClaimThreshold); - emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex.mantissa); - } - } - - /** - * @notice Calculate XVS accrued by a VAI minter and possibly transfer it to them - * @dev VAI minters will not begin to accrue until after the first interaction with the protocol. - * @param vaiMinter The address of the VAI minter to distribute XVS to - */ - function distributeVAIMinterVenus(address vaiMinter, bool distributeAll) internal { - if (address(vaiController) != address(0)) { - uint vaiMinterAccrued; - uint vaiMinterDelta; - uint vaiMintIndexMantissa; - uint err; - (err, vaiMinterAccrued, vaiMinterDelta, vaiMintIndexMantissa) = vaiController.calcDistributeVAIMinterVenus( - vaiMinter - ); - if (err == uint(Error.NO_ERROR)) { - venusAccrued[vaiMinter] = transferXVS( - vaiMinter, - vaiMinterAccrued, - distributeAll ? 0 : venusClaimThreshold - ); - emit DistributedVAIMinterVenus(vaiMinter, vaiMinterDelta, vaiMintIndexMantissa); - } - } - } - - /** - * @notice Transfer XVS to the user, if they are above the threshold - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param user The address of the user to transfer XVS to - * @param userAccrued The amount of XVS to (possibly) transfer - * @return The amount of XVS which was NOT transferred to the user - */ - function transferXVS(address user, uint userAccrued, uint threshold) internal returns (uint) { - if (userAccrued >= threshold && userAccrued > 0) { - XVS xvs = XVS(getXVSAddress()); - uint xvsRemaining = xvs.balanceOf(address(this)); - if (userAccrued <= xvsRemaining) { - xvs.transfer(user, userAccrued); - return 0; - } - } - return userAccrued; - } - - /** - * @notice Claim all the xvs accrued by holder in all markets and VAI - * @param holder The address to claim XVS for - */ - function claimVenus(address holder) public { - return claimVenus(holder, allMarkets); - } - - /** - * @notice Claim all the xvs accrued by holder in the specified markets - * @param holder The address to claim XVS for - * @param vTokens The list of markets to claim XVS in - */ - function claimVenus(address holder, VToken[] memory vTokens) public { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, vTokens, true, true); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - */ - function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { - uint j; - updateVenusVAIMintIndex(); - for (j = 0; j < holders.length; j++) { - distributeVAIMinterVenus(holders[j], true); - } - for (uint i = 0; i < vTokens.length; i++) { - VToken vToken = vTokens[i]; - require(markets[address(vToken)].isListed, "not listed market"); - if (borrowers) { - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - for (j = 0; j < holders.length; j++) { - distributeBorrowerVenus(address(vToken), holders[j], borrowIndex, true); - } - } - if (suppliers) { - updateVenusSupplyIndex(address(vToken)); - for (j = 0; j < holders.length; j++) { - distributeSupplierVenus(address(vToken), holders[j], true); - } - } - } - } - - /*** Venus Distribution Admin ***/ - - /** - * @notice Set the amount of XVS distributed per block - * @param venusRate_ The amount of XVS wei per block to distribute - */ - function _setVenusRate(uint venusRate_) public onlyAdmin { - uint oldRate = venusRate; - venusRate = venusRate_; - emit NewVenusRate(oldRate, venusRate_); - - refreshVenusSpeedsInternal(); - } - - /** - * @notice Add markets to venusMarkets, allowing them to earn XVS in the flywheel - * @param vTokens The addresses of the markets to add - */ - function _addVenusMarkets(address[] calldata vTokens) external onlyAdmin { - for (uint i = 0; i < vTokens.length; i++) { - _addVenusMarketInternal(vTokens[i]); - } - - refreshVenusSpeedsInternal(); - } - - function _addVenusMarketInternal(address vToken) internal { - Market storage market = markets[vToken]; - require(market.isListed, "venus market is not listed"); - require(!market.isVenus, "venus market already added"); - - market.isVenus = true; - emit MarketVenus(VToken(vToken), true); - - if (venusSupplyState[vToken].index == 0 && venusSupplyState[vToken].block == 0) { - venusSupplyState[vToken] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number overflows") - }); - } - - if (venusBorrowState[vToken].index == 0 && venusBorrowState[vToken].block == 0) { - venusBorrowState[vToken] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number overflows") - }); - } - } - - function _initializeVenusVAIState(uint blockNumber) public { - require(msg.sender == admin, "only admin can"); - if (address(vaiController) != address(0)) { - vaiController._initializeVenusVAIState(blockNumber); - } - } - - /** - * @notice Remove a market from venusMarkets, preventing it from earning XVS in the flywheel - * @param vToken The address of the market to drop - */ - function _dropVenusMarket(address vToken) public onlyAdmin { - Market storage market = markets[vToken]; - require(market.isVenus == true, "not venus market"); - - market.isVenus = false; - emit MarketVenus(VToken(vToken), false); - - refreshVenusSpeedsInternal(); - } - - /** - * @notice Return all of the markets - * @dev The automatic getter may be used to access an individual market. - * @return The list of market addresses - */ - function getAllMarkets() public view returns (VToken[] memory) { - return allMarkets; - } - - function getBlockNumber() public view returns (uint) { - return block.number; - } - - /** - * @notice Return the address of the XVS token - * @return The address of XVS - */ - function getXVSAddress() public view returns (address) { - return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; - } - - /*** VAI functions ***/ - - /** - * @notice Set the minted VAI amount of the `owner` - * @param owner The address of the account to set - * @param amount The amount of VAI to set to the account - * @return The number of minted VAI by `owner` - */ - function setMintedVAIOf(address owner, uint amount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); - // Check caller is vaiController - if (msg.sender != address(vaiController)) { - return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); - } - mintedVAIs[owner] = amount; - - return uint(Error.NO_ERROR); - } - - /** - * @notice Mint VAI - */ - function mintVAI(uint mintVAIAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused, "mintVAI is paused"); - - // Keep the flywheel moving - updateVenusVAIMintIndex(); - distributeVAIMinterVenus(msg.sender, false); - return vaiController.mintVAI(msg.sender, mintVAIAmount); - } - - /** - * @notice Repay VAI - */ - function repayVAI(uint repayVAIAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!repayVAIGuardianPaused, "repayVAI is paused"); - - // Keep the flywheel moving - updateVenusVAIMintIndex(); - distributeVAIMinterVenus(msg.sender, false); - return vaiController.repayVAI(msg.sender, repayVAIAmount); - } - - /** - * @notice Get the minted VAI amount of the `owner` - * @param owner The address of the account to query - * @return The number of minted VAI by `owner` - */ - function mintedVAIOf(address owner) external view returns (uint) { - return mintedVAIs[owner]; - } - - /** - * @notice Get Mintable VAI amount - */ - function getMintableVAI(address minter) external view returns (uint, uint) { - return vaiController.getMintableVAI(minter); - } -} diff --git a/contracts/Comptroller/ComptrollerG2.sol b/contracts/Comptroller/ComptrollerG2.sol deleted file mode 100644 index c2ad5be63..000000000 --- a/contracts/Comptroller/ComptrollerG2.sol +++ /dev/null @@ -1,1605 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VTokens/VToken.sol"; -import "../Utils/ErrorReporter.sol"; -import "../Utils/Exponential.sol"; -import "../Tokens/XVS/XVS.sol"; -import "../Tokens/VAI/VAI.sol"; -import "./ComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Venus's Comptroller Contract - * @author Venus - */ -contract ComptrollerG2 is ComptrollerV1Storage, ComptrollerInterfaceG1, ComptrollerErrorReporter, Exponential { - /// @notice Emitted when an admin supports a market - event MarketListed(VToken vToken); - - /// @notice Emitted when an account enters a market - event MarketEntered(VToken vToken, address account); - - /// @notice Emitted when an account exits a market - event MarketExited(VToken vToken, address account); - - /// @notice Emitted when close factor is changed by admin - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /// @notice Emitted when a collateral factor is changed by admin - event NewCollateralFactor(VToken vToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /// @notice Emitted when liquidation incentive is changed by admin - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /// @notice Emitted when maxAssets is changed by admin - event NewMaxAssets(uint oldMaxAssets, uint newMaxAssets); - - /// @notice Emitted when price oracle is changed - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /// @notice Emitted when pause guardian is changed - event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); - - /// @notice Emitted when an action is paused globally - event ActionPaused(string action, bool pauseState); - - /// @notice Emitted when an action is paused on a market - event ActionPaused(VToken vToken, string action, bool pauseState); - - /// @notice Emitted when market venus status is changed - event MarketVenus(VToken vToken, bool isVenus); - - /// @notice Emitted when Venus rate is changed - event NewVenusRate(uint oldVenusRate, uint newVenusRate); - - /// @notice Emitted when a new Venus speed is calculated for a market - event VenusSpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when XVS is distributed to a supplier - event DistributedSupplierVenus( - VToken indexed vToken, - address indexed supplier, - uint venusDelta, - uint venusSupplyIndex - ); - - /// @notice Emitted when XVS is distributed to a borrower - event DistributedBorrowerVenus( - VToken indexed vToken, - address indexed borrower, - uint venusDelta, - uint venusBorrowIndex - ); - - /// @notice Emitted when XVS is distributed to a VAI minter - event DistributedVAIMinterVenus(address indexed vaiMinter, uint venusDelta, uint venusVAIMintIndex); - - /// @notice Emitted when VAIController is changed - event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); - - /// @notice Emitted when VAI mint rate is changed by admin - event NewVAIMintRate(uint oldVAIMintRate, uint newVAIMintRate); - - /// @notice Emitted when protocol state is changed by admin - event ActionProtocolPaused(bool state); - - /// @notice The threshold above which the flywheel transfers XVS, in wei - uint public constant venusClaimThreshold = 0.001e18; - - /// @notice The initial Venus index for a market - uint224 public constant venusInitialIndex = 1e36; - - // closeFactorMantissa must be strictly greater than this value - uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05 - - // closeFactorMantissa must not exceed this value - uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 - - // liquidationIncentiveMantissa must be no less than this value - uint internal constant liquidationIncentiveMinMantissa = 1.0e18; // 1.0 - - // liquidationIncentiveMantissa must be no greater than this value - uint internal constant liquidationIncentiveMaxMantissa = 1.5e18; // 1.5 - - constructor() public { - admin = msg.sender; - } - - modifier onlyProtocolAllowed() { - require(!protocolPaused, "protocol is paused"); - _; - } - - modifier onlyAdmin() { - require(msg.sender == admin, "only admin can"); - _; - } - - modifier onlyListedMarket(VToken vToken) { - require(markets[address(vToken)].isListed, "venus market is not listed"); - _; - } - - modifier validPauseState(bool state) { - require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can"); - require(msg.sender == admin || state == true, "only admin can unpause"); - _; - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (VToken[] memory) { - return accountAssets[account]; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param vToken The vToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, VToken vToken) external view returns (bool) { - return markets[address(vToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param vTokens The list of addresses of the vToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory) { - uint len = vTokens.length; - - uint[] memory results = new uint[](len); - for (uint i = 0; i < len; i++) { - results[i] = uint(addToMarketInternal(VToken(vTokens[i]), msg.sender)); - } - - return results; - } - - /** - * @notice Add the market to the borrower's "assets in" for liquidity calculations - * @param vToken The market to enter - * @param borrower The address of the account to modify - * @return Success indicator for whether the market was entered - */ - function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { - Market storage marketToJoin = markets[address(vToken)]; - - if (!marketToJoin.isListed) { - // market is not listed, cannot join - return Error.MARKET_NOT_LISTED; - } - - if (marketToJoin.accountMembership[borrower]) { - // already joined - return Error.NO_ERROR; - } - - if (accountAssets[borrower].length >= maxAssets) { - // no space, cannot join - return Error.TOO_MANY_ASSETS; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[borrower] = true; - accountAssets[borrower].push(vToken); - - emit MarketEntered(vToken, borrower); - - return Error.NO_ERROR; - } - - /** - * @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 vTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address vTokenAddress) external returns (uint) { - VToken vToken = VToken(vTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the vToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = vToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(vToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set vToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete vToken from the account’s list of assets */ - // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 - VToken[] storage userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint i; - for (; i < len; i++) { - if (userAssetList[i] == vToken) { - userAssetList[i] = userAssetList[len - 1]; - userAssetList.length--; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(i < len); - - emit MarketExited(vToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param vToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address vToken, address minter, uint mintAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintGuardianPaused[vToken], "mint is paused"); - - // Shh - currently unused - mintAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, minter, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates mint and reverts on rejection. May emit logs. - * @param vToken Asset being minted - * @param minter The address minting the tokens - * @param actualMintAmount The amount of the underlying asset being minted - * @param mintTokens The number of tokens being minted - */ - function mintVerify(address vToken, address minter, uint actualMintAmount, uint mintTokens) external { - // Shh - currently unused - vToken; - minter; - actualMintAmount; - mintTokens; - } - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param vToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed( - address vToken, - address redeemer, - uint redeemTokens - ) external onlyProtocolAllowed returns (uint) { - uint allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, redeemer, false); - - return uint(Error.NO_ERROR); - } - - function redeemAllowedInternal(address vToken, address redeemer, uint redeemTokens) internal view returns (uint) { - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[vToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - redeemer, - VToken(vToken), - redeemTokens, - 0 - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates redeem and reverts on rejection. May emit logs. - * @param vToken Asset being redeemed - * @param redeemer The address redeeming the tokens - * @param redeemAmount The amount of the underlying asset being redeemed - * @param redeemTokens The number of tokens being redeemed - */ - function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external { - // Shh - currently unused - vToken; - redeemer; - - // Require tokens is zero or amount is also zero - require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param vToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed( - address vToken, - address borrower, - uint borrowAmount - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!borrowGuardianPaused[vToken], "borrow is paused"); - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (!markets[vToken].accountMembership[borrower]) { - // only vTokens may call borrowAllowed if borrower not in market - require(msg.sender == vToken, "sender must be vToken"); - - // attempt to add borrower to the market - Error err = addToMarketInternal(VToken(vToken), borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - } - - if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - borrower, - VToken(vToken), - 0, - borrowAmount - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates borrow and reverts on rejection. May emit logs. - * @param vToken Asset whose underlying is being borrowed - * @param borrower The address borrowing the underlying - * @param borrowAmount The amount of the underlying asset requested to borrow - */ - function borrowVerify(address vToken, address borrower, uint borrowAmount) external { - // Shh - currently unused - vToken; - borrower; - borrowAmount; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param vToken The market to verify the repay against - * @param payer The account which would repay the asset - * @param borrower The account which would repay the asset - * @param repayAmount The amount of the underlying asset the account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address vToken, - address payer, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - payer; - borrower; - repayAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates repayBorrow and reverts on rejection. May emit logs. - * @param vToken Asset being repaid - * @param payer The address repaying the borrow - * @param borrower The address of the borrower - * @param actualRepayAmount The amount of underlying being repaid - */ - function repayBorrowVerify( - address vToken, - address payer, - address borrower, - uint actualRepayAmount, - uint borrowerIndex - ) external { - // Shh - currently unused - vToken; - payer; - borrower; - actualRepayAmount; - borrowerIndex; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - liquidator; - - if (!markets[vTokenBorrowed].isListed || !markets[vTokenCollateral].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(0), 0, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - /* The liquidator may not repay more than what is allowed by the closeFactor */ - uint borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); - (MathError mathErr, uint maxClose) = mulScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance); - if (mathErr != MathError.NO_ERROR) { - return uint(Error.MATH_ERROR); - } - if (repayAmount > maxClose) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 actualRepayAmount The amount of underlying being repaid - */ - function liquidateBorrowVerify( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint actualRepayAmount, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenBorrowed; - vTokenCollateral; - liquidator; - borrower; - actualRepayAmount; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!seizeGuardianPaused, "seize is paused"); - - // Shh - currently unused - seizeTokens; - - if (!markets[vTokenCollateral].isListed || !markets[vTokenBorrowed].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vTokenCollateral); - distributeSupplierVenus(vTokenCollateral, borrower, false); - distributeSupplierVenus(vTokenCollateral, liquidator, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates seize and reverts on rejection. May emit logs. - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 seizeVerify( - address vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenCollateral; - vTokenBorrowed; - liquidator; - borrower; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param vToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed( - address vToken, - address src, - address dst, - uint transferTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!transferGuardianPaused, "transfer is paused"); - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - uint allowed = redeemAllowedInternal(vToken, src, transferTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, src, false); - distributeSupplierVenus(vToken, dst, false); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates transfer and reverts on rejection. May emit logs. - * @param vToken Asset being transferred - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - */ - function transferVerify(address vToken, address src, address dst, uint transferTokens) external { - // Shh - currently unused - vToken; - src; - dst; - transferTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /*** Liquidity/Liquidation Calculations ***/ - - /** - * @dev Local vars for avoiding stack-depth limits in calculating account liquidity. - * Note that `vTokenBalance` is the number of vTokens the account owns in the market, - * whereas `borrowBalance` is the amount of underlying that the account has borrowed. - */ - struct AccountLiquidityLocalVars { - uint sumCollateral; - uint sumBorrowPlusEffects; - uint vTokenBalance; - uint borrowBalance; - uint exchangeRateMantissa; - uint oraclePriceMantissa; - Exp collateralFactor; - Exp exchangeRate; - Exp oraclePrice; - Exp tokensToDenom; - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, VToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @return (possible error code (semi-opaque), - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidity( - address account, - address vTokenModify, - uint redeemTokens, - uint borrowAmount - ) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal( - account, - VToken(vTokenModify), - redeemTokens, - borrowAmount - ); - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - // solhint-disable-next-line code-complexity - function getHypotheticalAccountLiquidityInternal( - address account, - VToken vTokenModify, - uint redeemTokens, - uint borrowAmount - ) internal view returns (Error, uint, uint) { - AccountLiquidityLocalVars memory vars; // Holds all our calculation results - uint oErr; - MathError mErr; - - // For each asset the account is in - VToken[] memory assets = accountAssets[account]; - for (uint i = 0; i < assets.length; i++) { - VToken asset = assets[i]; - - // Read the balances and exchange rate from the vToken - (oErr, vars.vTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot( - account - ); - if (oErr != 0) { - // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0); - } - vars.collateralFactor = Exp({ mantissa: markets[address(asset)].collateralFactorMantissa }); - vars.exchangeRate = Exp({ mantissa: vars.exchangeRateMantissa }); - - // Get the normalized price of the asset - vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset); - if (vars.oraclePriceMantissa == 0) { - return (Error.PRICE_ERROR, 0, 0); - } - vars.oraclePrice = Exp({ mantissa: vars.oraclePriceMantissa }); - - // Pre-compute a conversion factor from tokens -> bnb (normalized price value) - (mErr, vars.tokensToDenom) = mulExp3(vars.collateralFactor, vars.exchangeRate, vars.oraclePrice); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumCollateral += tokensToDenom * vTokenBalance - (mErr, vars.sumCollateral) = mulScalarTruncateAddUInt( - vars.tokensToDenom, - vars.vTokenBalance, - vars.sumCollateral - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumBorrowPlusEffects += oraclePrice * borrowBalance - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.oraclePrice, - vars.borrowBalance, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // Calculate effects of interacting with vTokenModify - if (asset == vTokenModify) { - // redeem effect - // sumBorrowPlusEffects += tokensToDenom * redeemTokens - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.tokensToDenom, - redeemTokens, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // borrow effect - // sumBorrowPlusEffects += oraclePrice * borrowAmount - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.oraclePrice, - borrowAmount, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - } - } - - /// @dev VAI Integration^ - (mErr, vars.sumBorrowPlusEffects) = addUInt(vars.sumBorrowPlusEffects, mintedVAIs[account]); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - /// @dev VAI Integration$ - - // These are safe, as the underflow condition is checked first - if (vars.sumCollateral > vars.sumBorrowPlusEffects) { - return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0); - } else { - return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral); - } - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenBorrowed The address of the borrowed vToken - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = oracle.getUnderlyingPrice(VToken(vTokenBorrowed)); - uint priceCollateralMantissa = oracle.getUnderlyingPrice(VToken(vTokenCollateral)); - if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - MathError mathErr; - - (mathErr, numerator) = mulExp(liquidationIncentiveMantissa, priceBorrowedMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, denominator) = mulExp(priceCollateralMantissa, exchangeRateMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, ratio) = divExp(numerator, denominator); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, seizeTokens) = mulScalarTruncate(ratio, actualRepayAmount); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) public returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK); - } - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_CLOSE_FACTOR_OWNER_CHECK); - } - - Exp memory newCloseFactorExp = Exp({ mantissa: newCloseFactorMantissa }); - Exp memory lowLimit = Exp({ mantissa: closeFactorMinMantissa }); - if (lessThanOrEqualExp(newCloseFactorExp, lowLimit)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - Exp memory highLimit = Exp({ mantissa: closeFactorMaxMantissa }); - if (lessThanExp(highLimit, newCloseFactorExp)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Admin function to set per-market collateralFactor - * @param vToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(VToken vToken, uint newCollateralFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK); - } - - // Verify market is listed - Market storage market = markets[address(vToken)]; - if (!market.isListed) { - return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS); - } - - Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { - return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets maxAssets which controls how many markets can be entered - * @dev Admin function to set maxAssets - * @param newMaxAssets New max assets - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setMaxAssets(uint newMaxAssets) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_MAX_ASSETS_OWNER_CHECK); - } - - uint oldMaxAssets = maxAssets; - maxAssets = newMaxAssets; - emit NewMaxAssets(oldMaxAssets, newMaxAssets); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK); - } - - // Check de-scaled min <= newLiquidationIncentive <= max - Exp memory newLiquidationIncentive = Exp({ mantissa: newLiquidationIncentiveMantissa }); - Exp memory minLiquidationIncentive = Exp({ mantissa: liquidationIncentiveMinMantissa }); - if (lessThanExp(newLiquidationIncentive, minLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - Exp memory maxLiquidationIncentive = Exp({ mantissa: liquidationIncentiveMaxMantissa }); - if (lessThanExp(maxLiquidationIncentive, newLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param vToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(VToken vToken) external returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SUPPORT_MARKET_OWNER_CHECK); - } - - if (markets[address(vToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - vToken.isVToken(); // Sanity check to make sure its really a VToken - - markets[address(vToken)] = Market({ isListed: true, isVenus: false, collateralFactorMantissa: 0 }); - - _addMarketInternal(vToken); - - emit MarketListed(vToken); - - return uint(Error.NO_ERROR); - } - - function _addMarketInternal(VToken vToken) internal { - for (uint i = 0; i < allMarkets.length; i++) { - require(allMarkets[i] != vToken, "market already added"); - } - allMarkets.push(vToken); - } - - /** - * @notice Admin function to change the Pause Guardian - * @param newPauseGuardian The address of the new Pause Guardian - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _setPauseGuardian(address newPauseGuardian) public returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PAUSE_GUARDIAN_OWNER_CHECK); - } - - // Save current value for inclusion in log - address oldPauseGuardian = pauseGuardian; - - // Store pauseGuardian with value newPauseGuardian - pauseGuardian = newPauseGuardian; - - // Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian) - emit NewPauseGuardian(oldPauseGuardian, newPauseGuardian); - - return uint(Error.NO_ERROR); - } - - function _setMintPaused( - VToken vToken, - bool state - ) public onlyListedMarket(vToken) validPauseState(state) returns (bool) { - mintGuardianPaused[address(vToken)] = state; - emit ActionPaused(vToken, "Mint", state); - return state; - } - - function _setBorrowPaused( - VToken vToken, - bool state - ) public onlyListedMarket(vToken) validPauseState(state) returns (bool) { - borrowGuardianPaused[address(vToken)] = state; - emit ActionPaused(vToken, "Borrow", state); - return state; - } - - function _setTransferPaused(bool state) public validPauseState(state) returns (bool) { - transferGuardianPaused = state; - emit ActionPaused("Transfer", state); - return state; - } - - function _setSeizePaused(bool state) public validPauseState(state) returns (bool) { - seizeGuardianPaused = state; - emit ActionPaused("Seize", state); - return state; - } - - function _setMintVAIPaused(bool state) public validPauseState(state) returns (bool) { - mintVAIGuardianPaused = state; - emit ActionPaused("MintVAI", state); - return state; - } - - function _setRepayVAIPaused(bool state) public validPauseState(state) returns (bool) { - repayVAIGuardianPaused = state; - emit ActionPaused("RepayVAI", state); - return state; - } - - /** - * @notice Set whole protocol pause/unpause state - */ - function _setProtocolPaused(bool state) public onlyAdmin returns (bool) { - protocolPaused = state; - emit ActionProtocolPaused(state); - return state; - } - - /** - * @notice Sets a new VAI controller - * @dev Admin function to set a new VAI controller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setVAIController(VAIControllerInterface vaiController_) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAICONTROLLER_OWNER_CHECK); - } - - VAIControllerInterface oldRate = vaiController; - vaiController = vaiController_; - emit NewVAIController(oldRate, vaiController_); - } - - function _setVAIMintRate(uint newVAIMintRate) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAI_MINT_RATE_CHECK); - } - - uint oldVAIMintRate = vaiMintRate; - vaiMintRate = newVAIMintRate; - emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller) public { - require(msg.sender == unitroller.admin(), "only unitroller admin can"); - require(unitroller._acceptImplementation() == 0, "not authorized"); - } - - /*** Venus Distribution ***/ - - /** - * @notice Recalculate and update Venus speeds for all Venus markets - */ - function refreshVenusSpeeds() public { - require(msg.sender == tx.origin, "only externally owned accounts can"); - refreshVenusSpeedsInternal(); - } - - function refreshVenusSpeedsInternal() internal { - uint i; - VToken vToken; - - for (i = 0; i < allMarkets.length; i++) { - vToken = allMarkets[i]; - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusSupplyIndex(address(vToken)); - updateVenusBorrowIndex(address(vToken), borrowIndex); - } - - Exp memory totalUtility = Exp({ mantissa: 0 }); - Exp[] memory utilities = new Exp[](allMarkets.length); - for (i = 0; i < allMarkets.length; i++) { - vToken = allMarkets[i]; - if (markets[address(vToken)].isVenus) { - Exp memory assetPrice = Exp({ mantissa: oracle.getUnderlyingPrice(vToken) }); - Exp memory utility = mul_(assetPrice, vToken.totalBorrows()); - utilities[i] = utility; - totalUtility = add_(totalUtility, utility); - } - } - - for (i = 0; i < allMarkets.length; i++) { - vToken = allMarkets[i]; - uint newSpeed = totalUtility.mantissa > 0 ? mul_(venusRate, div_(utilities[i], totalUtility)) : 0; - venusSpeeds[address(vToken)] = newSpeed; - emit VenusSpeedUpdated(vToken, newSpeed); - } - } - - /** - * @notice Accrue XVS to the market by updating the supply index - * @param vToken The market whose supply index to update - */ - function updateVenusSupplyIndex(address vToken) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - uint supplySpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(supplyState.block)); - if (deltaBlocks > 0 && supplySpeed > 0) { - uint supplyTokens = VToken(vToken).totalSupply(); - uint venusAccrued = mul_(deltaBlocks, supplySpeed); - Double memory ratio = supplyTokens > 0 ? fraction(venusAccrued, supplyTokens) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: supplyState.index }), ratio); - venusSupplyState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - supplyState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Accrue XVS to the market by updating the borrow index - * @param vToken The market whose borrow index to update - */ - function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - uint borrowSpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(borrowState.block)); - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); - uint venusAccrued = mul_(deltaBlocks, borrowSpeed); - Double memory ratio = borrowAmount > 0 ? fraction(venusAccrued, borrowAmount) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: borrowState.index }), ratio); - venusBorrowState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - borrowState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Accrue XVS to by updating the VAI minter index - */ - function updateVenusVAIMintIndex() internal { - if (address(vaiController) != address(0)) { - vaiController.updateVenusVAIMintIndex(); - } - } - - /** - * @notice Calculate XVS accrued by a supplier and possibly transfer it to them - * @param vToken The market in which the supplier is interacting - * @param supplier The address of the supplier to distribute XVS to - */ - function distributeSupplierVenus(address vToken, address supplier, bool distributeAll) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - Double memory supplyIndex = Double({ mantissa: supplyState.index }); - Double memory supplierIndex = Double({ mantissa: venusSupplierIndex[vToken][supplier] }); - venusSupplierIndex[vToken][supplier] = supplyIndex.mantissa; - - if (supplierIndex.mantissa == 0 && supplyIndex.mantissa > 0) { - supplierIndex.mantissa = venusInitialIndex; - } - - Double memory deltaIndex = sub_(supplyIndex, supplierIndex); - uint supplierTokens = VToken(vToken).balanceOf(supplier); - uint supplierDelta = mul_(supplierTokens, deltaIndex); - uint supplierAccrued = add_(venusAccrued[supplier], supplierDelta); - venusAccrued[supplier] = transferXVS(supplier, supplierAccrued, distributeAll ? 0 : venusClaimThreshold); - emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex.mantissa); - } - - /** - * @notice Calculate XVS accrued by a borrower and possibly transfer it to them - * @dev Borrowers will not begin to accrue until after the first interaction with the protocol. - * @param vToken The market in which the borrower is interacting - * @param borrower The address of the borrower to distribute XVS to - */ - function distributeBorrowerVenus( - address vToken, - address borrower, - Exp memory marketBorrowIndex, - bool distributeAll - ) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - Double memory borrowIndex = Double({ mantissa: borrowState.index }); - Double memory borrowerIndex = Double({ mantissa: venusBorrowerIndex[vToken][borrower] }); - venusBorrowerIndex[vToken][borrower] = borrowIndex.mantissa; - - if (borrowerIndex.mantissa > 0) { - Double memory deltaIndex = sub_(borrowIndex, borrowerIndex); - uint borrowerAmount = div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex); - uint borrowerDelta = mul_(borrowerAmount, deltaIndex); - uint borrowerAccrued = add_(venusAccrued[borrower], borrowerDelta); - venusAccrued[borrower] = transferXVS(borrower, borrowerAccrued, distributeAll ? 0 : venusClaimThreshold); - emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex.mantissa); - } - } - - /** - * @notice Calculate XVS accrued by a VAI minter and possibly transfer it to them - * @dev VAI minters will not begin to accrue until after the first interaction with the protocol. - * @param vaiMinter The address of the VAI minter to distribute XVS to - */ - function distributeVAIMinterVenus(address vaiMinter, bool distributeAll) internal { - if (address(vaiController) != address(0)) { - uint vaiMinterAccrued; - uint vaiMinterDelta; - uint vaiMintIndexMantissa; - uint err; - (err, vaiMinterAccrued, vaiMinterDelta, vaiMintIndexMantissa) = vaiController.calcDistributeVAIMinterVenus( - vaiMinter - ); - if (err == uint(Error.NO_ERROR)) { - venusAccrued[vaiMinter] = transferXVS( - vaiMinter, - vaiMinterAccrued, - distributeAll ? 0 : venusClaimThreshold - ); - emit DistributedVAIMinterVenus(vaiMinter, vaiMinterDelta, vaiMintIndexMantissa); - } - } - } - - /** - * @notice Transfer XVS to the user, if they are above the threshold - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param user The address of the user to transfer XVS to - * @param userAccrued The amount of XVS to (possibly) transfer - * @return The amount of XVS which was NOT transferred to the user - */ - function transferXVS(address user, uint userAccrued, uint threshold) internal returns (uint) { - if (userAccrued >= threshold && userAccrued > 0) { - XVS xvs = XVS(getXVSAddress()); - uint xvsRemaining = xvs.balanceOf(address(this)); - if (userAccrued <= xvsRemaining) { - xvs.transfer(user, userAccrued); - return 0; - } - } - return userAccrued; - } - - /** - * @notice Claim all the xvs accrued by holder in all markets and VAI - * @param holder The address to claim XVS for - */ - function claimVenus(address holder) public { - return claimVenus(holder, allMarkets); - } - - /** - * @notice Claim all the xvs accrued by holder in the specified markets - * @param holder The address to claim XVS for - * @param vTokens The list of markets to claim XVS in - */ - function claimVenus(address holder, VToken[] memory vTokens) public { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, vTokens, true, true); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - */ - function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { - uint j; - updateVenusVAIMintIndex(); - for (j = 0; j < holders.length; j++) { - distributeVAIMinterVenus(holders[j], true); - } - for (uint i = 0; i < vTokens.length; i++) { - VToken vToken = vTokens[i]; - require(markets[address(vToken)].isListed, "not listed market"); - if (borrowers) { - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - for (j = 0; j < holders.length; j++) { - distributeBorrowerVenus(address(vToken), holders[j], borrowIndex, true); - } - } - if (suppliers) { - updateVenusSupplyIndex(address(vToken)); - for (j = 0; j < holders.length; j++) { - distributeSupplierVenus(address(vToken), holders[j], true); - } - } - } - } - - /*** Venus Distribution Admin ***/ - - /** - * @notice Set the amount of XVS distributed per block - * @param venusRate_ The amount of XVS wei per block to distribute - */ - function _setVenusRate(uint venusRate_) public onlyAdmin { - uint oldRate = venusRate; - venusRate = venusRate_; - emit NewVenusRate(oldRate, venusRate_); - - refreshVenusSpeedsInternal(); - } - - /** - * @notice Add markets to venusMarkets, allowing them to earn XVS in the flywheel - * @param vTokens The addresses of the markets to add - */ - function _addVenusMarkets(address[] calldata vTokens) external onlyAdmin { - for (uint i = 0; i < vTokens.length; i++) { - _addVenusMarketInternal(vTokens[i]); - } - - refreshVenusSpeedsInternal(); - } - - function _addVenusMarketInternal(address vToken) internal { - Market storage market = markets[vToken]; - require(market.isListed, "venus market is not listed"); - require(!market.isVenus, "venus market already added"); - - market.isVenus = true; - emit MarketVenus(VToken(vToken), true); - - if (venusSupplyState[vToken].index == 0 && venusSupplyState[vToken].block == 0) { - venusSupplyState[vToken] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number overflows") - }); - } - - if (venusBorrowState[vToken].index == 0 && venusBorrowState[vToken].block == 0) { - venusBorrowState[vToken] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number overflows") - }); - } - } - - function _initializeVenusVAIState(uint blockNumber) public { - require(msg.sender == admin, "only admin can"); - if (address(vaiController) != address(0)) { - vaiController._initializeVenusVAIState(blockNumber); - } - } - - /** - * @notice Remove a market from venusMarkets, preventing it from earning XVS in the flywheel - * @param vToken The address of the market to drop - */ - function _dropVenusMarket(address vToken) public onlyAdmin { - Market storage market = markets[vToken]; - require(market.isVenus == true, "not venus market"); - - market.isVenus = false; - emit MarketVenus(VToken(vToken), false); - - refreshVenusSpeedsInternal(); - } - - /** - * @notice Return all of the markets - * @dev The automatic getter may be used to access an individual market. - * @return The list of market addresses - */ - function getAllMarkets() public view returns (VToken[] memory) { - return allMarkets; - } - - function getBlockNumber() public view returns (uint) { - return block.number; - } - - /** - * @notice Return the address of the XVS token - * @return The address of XVS - */ - function getXVSAddress() public view returns (address) { - return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; - } - - /*** VAI functions ***/ - - /** - * @notice Set the minted VAI amount of the `owner` - * @param owner The address of the account to set - * @param amount The amount of VAI to set to the account - * @return The number of minted VAI by `owner` - */ - function setMintedVAIOf(address owner, uint amount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); - // Check caller is vaiController - if (msg.sender != address(vaiController)) { - return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); - } - mintedVAIs[owner] = amount; - - return uint(Error.NO_ERROR); - } - - /** - * @notice Mint VAI - */ - function mintVAI(uint mintVAIAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused, "mintVAI is paused"); - - // Keep the flywheel moving - updateVenusVAIMintIndex(); - distributeVAIMinterVenus(msg.sender, false); - return vaiController.mintVAI(msg.sender, mintVAIAmount); - } - - /** - * @notice Repay VAI - */ - function repayVAI(uint repayVAIAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!repayVAIGuardianPaused, "repayVAI is paused"); - - // Keep the flywheel moving - updateVenusVAIMintIndex(); - distributeVAIMinterVenus(msg.sender, false); - return vaiController.repayVAI(msg.sender, repayVAIAmount); - } - - /** - * @notice Get the minted VAI amount of the `owner` - * @param owner The address of the account to query - * @return The number of minted VAI by `owner` - */ - function mintedVAIOf(address owner) external view returns (uint) { - return mintedVAIs[owner]; - } - - /** - * @notice Get Mintable VAI amount - */ - function getMintableVAI(address minter) external view returns (uint, uint) { - return vaiController.getMintableVAI(minter); - } -} diff --git a/contracts/Comptroller/ComptrollerG3.sol b/contracts/Comptroller/ComptrollerG3.sol deleted file mode 100644 index bb00a9d11..000000000 --- a/contracts/Comptroller/ComptrollerG3.sol +++ /dev/null @@ -1,1561 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VTokens/VToken.sol"; -import "../Utils/ErrorReporter.sol"; -import "../Utils/Exponential.sol"; -import "../Tokens/XVS/XVS.sol"; -import "../Tokens/VAI/VAI.sol"; -import "./ComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Venus's Comptroller Contract - * @author Venus - */ -contract ComptrollerG3 is ComptrollerV3Storage, ComptrollerInterfaceG1, ComptrollerErrorReporter, Exponential { - /// @notice Emitted when an admin supports a market - event MarketListed(VToken vToken); - - /// @notice Emitted when an account enters a market - event MarketEntered(VToken vToken, address account); - - /// @notice Emitted when an account exits a market - event MarketExited(VToken vToken, address account); - - /// @notice Emitted when close factor is changed by admin - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /// @notice Emitted when a collateral factor is changed by admin - event NewCollateralFactor(VToken vToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /// @notice Emitted when liquidation incentive is changed by admin - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /// @notice Emitted when maxAssets is changed by admin - event NewMaxAssets(uint oldMaxAssets, uint newMaxAssets); - - /// @notice Emitted when price oracle is changed - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /// @notice Emitted when VAI Vault info is changed - event NewVAIVaultInfo(address vault_, uint releaseStartBlock_, uint releaseInterval_); - - /// @notice Emitted when pause guardian is changed - event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); - - /// @notice Emitted when an action is paused globally - event ActionPaused(string action, bool pauseState); - - /// @notice Emitted when an action is paused on a market - event ActionPaused(VToken vToken, string action, bool pauseState); - - /// @notice Emitted when Venus VAI Vault rate is changed - event NewVenusVAIVaultRate(uint oldVenusVAIVaultRate, uint newVenusVAIVaultRate); - - /// @notice Emitted when a new Venus speed is calculated for a market - event VenusSpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when XVS is distributed to a supplier - event DistributedSupplierVenus( - VToken indexed vToken, - address indexed supplier, - uint venusDelta, - uint venusSupplyIndex - ); - - /// @notice Emitted when XVS is distributed to a borrower - event DistributedBorrowerVenus( - VToken indexed vToken, - address indexed borrower, - uint venusDelta, - uint venusBorrowIndex - ); - - /// @notice Emitted when XVS is distributed to a VAI minter - event DistributedVAIMinterVenus(address indexed vaiMinter, uint venusDelta, uint venusVAIMintIndex); - - /// @notice Emitted when XVS is distributed to VAI Vault - event DistributedVAIVaultVenus(uint amount); - - /// @notice Emitted when VAIController is changed - event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); - - /// @notice Emitted when VAI mint rate is changed by admin - event NewVAIMintRate(uint oldVAIMintRate, uint newVAIMintRate); - - /// @notice Emitted when protocol state is changed by admin - event ActionProtocolPaused(bool state); - - /// @notice Emitted when borrow cap for a vToken is changed - event NewBorrowCap(VToken indexed vToken, uint newBorrowCap); - - /// @notice Emitted when borrow cap guardian is changed - event NewBorrowCapGuardian(address oldBorrowCapGuardian, address newBorrowCapGuardian); - - /// @notice The initial Venus index for a market - uint224 public constant venusInitialIndex = 1e36; - - // closeFactorMantissa must be strictly greater than this value - uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05 - - // closeFactorMantissa must not exceed this value - uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 - - // liquidationIncentiveMantissa must be no less than this value - uint internal constant liquidationIncentiveMinMantissa = 1.0e18; // 1.0 - - // liquidationIncentiveMantissa must be no greater than this value - uint internal constant liquidationIncentiveMaxMantissa = 1.5e18; // 1.5 - - constructor() public { - admin = msg.sender; - } - - modifier onlyProtocolAllowed() { - require(!protocolPaused, "protocol is paused"); - _; - } - - modifier onlyAdmin() { - require(msg.sender == admin, "only admin can"); - _; - } - - modifier onlyListedMarket(VToken vToken) { - require(markets[address(vToken)].isListed, "venus market is not listed"); - _; - } - - modifier validPauseState(bool state) { - require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can"); - require(msg.sender == admin || state == true, "only admin can unpause"); - _; - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (VToken[] memory) { - return accountAssets[account]; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param vToken The vToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, VToken vToken) external view returns (bool) { - return markets[address(vToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param vTokens The list of addresses of the vToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory) { - uint len = vTokens.length; - - uint[] memory results = new uint[](len); - for (uint i = 0; i < len; i++) { - results[i] = uint(addToMarketInternal(VToken(vTokens[i]), msg.sender)); - } - - return results; - } - - /** - * @notice Add the market to the borrower's "assets in" for liquidity calculations - * @param vToken The market to enter - * @param borrower The address of the account to modify - * @return Success indicator for whether the market was entered - */ - function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { - Market storage marketToJoin = markets[address(vToken)]; - - if (!marketToJoin.isListed) { - // market is not listed, cannot join - return Error.MARKET_NOT_LISTED; - } - - if (marketToJoin.accountMembership[borrower]) { - // already joined - return Error.NO_ERROR; - } - - if (accountAssets[borrower].length >= maxAssets) { - // no space, cannot join - return Error.TOO_MANY_ASSETS; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[borrower] = true; - accountAssets[borrower].push(vToken); - - emit MarketEntered(vToken, borrower); - - return Error.NO_ERROR; - } - - /** - * @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 vTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address vTokenAddress) external returns (uint) { - VToken vToken = VToken(vTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the vToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = vToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(vToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set vToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete vToken from the account’s list of assets */ - // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 - VToken[] storage userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint i; - for (; i < len; i++) { - if (userAssetList[i] == vToken) { - userAssetList[i] = userAssetList[len - 1]; - userAssetList.length--; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(i < len); - - emit MarketExited(vToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param vToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address vToken, address minter, uint mintAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintGuardianPaused[vToken], "mint is paused"); - - // Shh - currently unused - mintAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, minter); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates mint and reverts on rejection. May emit logs. - * @param vToken Asset being minted - * @param minter The address minting the tokens - * @param actualMintAmount The amount of the underlying asset being minted - * @param mintTokens The number of tokens being minted - */ - function mintVerify(address vToken, address minter, uint actualMintAmount, uint mintTokens) external { - // Shh - currently unused - vToken; - minter; - actualMintAmount; - mintTokens; - } - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param vToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed( - address vToken, - address redeemer, - uint redeemTokens - ) external onlyProtocolAllowed returns (uint) { - uint allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, redeemer); - - return uint(Error.NO_ERROR); - } - - function redeemAllowedInternal(address vToken, address redeemer, uint redeemTokens) internal view returns (uint) { - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[vToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - redeemer, - VToken(vToken), - redeemTokens, - 0 - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates redeem and reverts on rejection. May emit logs. - * @param vToken Asset being redeemed - * @param redeemer The address redeeming the tokens - * @param redeemAmount The amount of the underlying asset being redeemed - * @param redeemTokens The number of tokens being redeemed - */ - function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external { - // Shh - currently unused - vToken; - redeemer; - - // Require tokens is zero or amount is also zero - require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param vToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed( - address vToken, - address borrower, - uint borrowAmount - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!borrowGuardianPaused[vToken], "borrow is paused"); - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (!markets[vToken].accountMembership[borrower]) { - // only vTokens may call borrowAllowed if borrower not in market - require(msg.sender == vToken, "sender must be vToken"); - - // attempt to add borrower to the market - Error err = addToMarketInternal(VToken(vToken), borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - } - - if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - uint borrowCap = borrowCaps[vToken]; - // Borrow cap of 0 corresponds to unlimited borrowing - if (borrowCap != 0) { - uint totalBorrows = VToken(vToken).totalBorrows(); - (MathError mathErr, uint nextTotalBorrows) = addUInt(totalBorrows, borrowAmount); - require(mathErr == MathError.NO_ERROR, "total borrows overflow"); - require(nextTotalBorrows < borrowCap, "market borrow cap reached"); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - borrower, - VToken(vToken), - 0, - borrowAmount - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates borrow and reverts on rejection. May emit logs. - * @param vToken Asset whose underlying is being borrowed - * @param borrower The address borrowing the underlying - * @param borrowAmount The amount of the underlying asset requested to borrow - */ - function borrowVerify(address vToken, address borrower, uint borrowAmount) external { - // Shh - currently unused - vToken; - borrower; - borrowAmount; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param vToken The market to verify the repay against - * @param payer The account which would repay the asset - * @param borrower The account which would repay the asset - * @param repayAmount The amount of the underlying asset the account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address vToken, - address payer, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - payer; - borrower; - repayAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates repayBorrow and reverts on rejection. May emit logs. - * @param vToken Asset being repaid - * @param payer The address repaying the borrow - * @param borrower The address of the borrower - * @param actualRepayAmount The amount of underlying being repaid - */ - function repayBorrowVerify( - address vToken, - address payer, - address borrower, - uint actualRepayAmount, - uint borrowerIndex - ) external { - // Shh - currently unused - vToken; - payer; - borrower; - actualRepayAmount; - borrowerIndex; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - liquidator; - - if (!markets[vTokenBorrowed].isListed || !markets[vTokenCollateral].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(0), 0, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - /* The liquidator may not repay more than what is allowed by the closeFactor */ - uint borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); - (MathError mathErr, uint maxClose) = mulScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance); - if (mathErr != MathError.NO_ERROR) { - return uint(Error.MATH_ERROR); - } - if (repayAmount > maxClose) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 actualRepayAmount The amount of underlying being repaid - */ - function liquidateBorrowVerify( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint actualRepayAmount, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenBorrowed; - vTokenCollateral; - liquidator; - borrower; - actualRepayAmount; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!seizeGuardianPaused, "seize is paused"); - - // Shh - currently unused - seizeTokens; - - if (!markets[vTokenCollateral].isListed || !markets[vTokenBorrowed].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vTokenCollateral); - distributeSupplierVenus(vTokenCollateral, borrower); - distributeSupplierVenus(vTokenCollateral, liquidator); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates seize and reverts on rejection. May emit logs. - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 seizeVerify( - address vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenCollateral; - vTokenBorrowed; - liquidator; - borrower; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param vToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed( - address vToken, - address src, - address dst, - uint transferTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!transferGuardianPaused, "transfer is paused"); - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - uint allowed = redeemAllowedInternal(vToken, src, transferTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, src); - distributeSupplierVenus(vToken, dst); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates transfer and reverts on rejection. May emit logs. - * @param vToken Asset being transferred - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - */ - function transferVerify(address vToken, address src, address dst, uint transferTokens) external { - // Shh - currently unused - vToken; - src; - dst; - transferTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /*** Liquidity/Liquidation Calculations ***/ - - /** - * @dev Local vars for avoiding stack-depth limits in calculating account liquidity. - * Note that `vTokenBalance` is the number of vTokens the account owns in the market, - * whereas `borrowBalance` is the amount of underlying that the account has borrowed. - */ - struct AccountLiquidityLocalVars { - uint sumCollateral; - uint sumBorrowPlusEffects; - uint vTokenBalance; - uint borrowBalance; - uint exchangeRateMantissa; - uint oraclePriceMantissa; - Exp collateralFactor; - Exp exchangeRate; - Exp oraclePrice; - Exp tokensToDenom; - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, VToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @return (possible error code (semi-opaque), - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidity( - address account, - address vTokenModify, - uint redeemTokens, - uint borrowAmount - ) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal( - account, - VToken(vTokenModify), - redeemTokens, - borrowAmount - ); - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - // solhint-disable-next-line code-complexity - function getHypotheticalAccountLiquidityInternal( - address account, - VToken vTokenModify, - uint redeemTokens, - uint borrowAmount - ) internal view returns (Error, uint, uint) { - AccountLiquidityLocalVars memory vars; // Holds all our calculation results - uint oErr; - MathError mErr; - - // For each asset the account is in - VToken[] memory assets = accountAssets[account]; - for (uint i = 0; i < assets.length; i++) { - VToken asset = assets[i]; - - // Read the balances and exchange rate from the vToken - (oErr, vars.vTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot( - account - ); - if (oErr != 0) { - // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0); - } - vars.collateralFactor = Exp({ mantissa: markets[address(asset)].collateralFactorMantissa }); - vars.exchangeRate = Exp({ mantissa: vars.exchangeRateMantissa }); - - // Get the normalized price of the asset - vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset); - if (vars.oraclePriceMantissa == 0) { - return (Error.PRICE_ERROR, 0, 0); - } - vars.oraclePrice = Exp({ mantissa: vars.oraclePriceMantissa }); - - // Pre-compute a conversion factor from tokens -> bnb (normalized price value) - (mErr, vars.tokensToDenom) = mulExp3(vars.collateralFactor, vars.exchangeRate, vars.oraclePrice); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumCollateral += tokensToDenom * vTokenBalance - (mErr, vars.sumCollateral) = mulScalarTruncateAddUInt( - vars.tokensToDenom, - vars.vTokenBalance, - vars.sumCollateral - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumBorrowPlusEffects += oraclePrice * borrowBalance - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.oraclePrice, - vars.borrowBalance, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // Calculate effects of interacting with vTokenModify - if (asset == vTokenModify) { - // redeem effect - // sumBorrowPlusEffects += tokensToDenom * redeemTokens - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.tokensToDenom, - redeemTokens, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // borrow effect - // sumBorrowPlusEffects += oraclePrice * borrowAmount - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt( - vars.oraclePrice, - borrowAmount, - vars.sumBorrowPlusEffects - ); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - } - } - - /// @dev VAI Integration^ - (mErr, vars.sumBorrowPlusEffects) = addUInt(vars.sumBorrowPlusEffects, mintedVAIs[account]); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - /// @dev VAI Integration$ - - // These are safe, as the underflow condition is checked first - if (vars.sumCollateral > vars.sumBorrowPlusEffects) { - return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0); - } else { - return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral); - } - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenBorrowed The address of the borrowed vToken - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = oracle.getUnderlyingPrice(VToken(vTokenBorrowed)); - uint priceCollateralMantissa = oracle.getUnderlyingPrice(VToken(vTokenCollateral)); - if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - MathError mathErr; - - (mathErr, numerator) = mulExp(liquidationIncentiveMantissa, priceBorrowedMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, denominator) = mulExp(priceCollateralMantissa, exchangeRateMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, ratio) = divExp(numerator, denominator); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, seizeTokens) = mulScalarTruncate(ratio, actualRepayAmount); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) public returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK); - } - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_CLOSE_FACTOR_OWNER_CHECK); - } - - Exp memory newCloseFactorExp = Exp({ mantissa: newCloseFactorMantissa }); - Exp memory lowLimit = Exp({ mantissa: closeFactorMinMantissa }); - if (lessThanOrEqualExp(newCloseFactorExp, lowLimit)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - Exp memory highLimit = Exp({ mantissa: closeFactorMaxMantissa }); - if (lessThanExp(highLimit, newCloseFactorExp)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Admin function to set per-market collateralFactor - * @param vToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(VToken vToken, uint newCollateralFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK); - } - - // Verify market is listed - Market storage market = markets[address(vToken)]; - if (!market.isListed) { - return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS); - } - - Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { - return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets maxAssets which controls how many markets can be entered - * @dev Admin function to set maxAssets - * @param newMaxAssets New max assets - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setMaxAssets(uint newMaxAssets) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_MAX_ASSETS_OWNER_CHECK); - } - - uint oldMaxAssets = maxAssets; - maxAssets = newMaxAssets; - emit NewMaxAssets(oldMaxAssets, newMaxAssets); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK); - } - - // Check de-scaled min <= newLiquidationIncentive <= max - Exp memory newLiquidationIncentive = Exp({ mantissa: newLiquidationIncentiveMantissa }); - Exp memory minLiquidationIncentive = Exp({ mantissa: liquidationIncentiveMinMantissa }); - if (lessThanExp(newLiquidationIncentive, minLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - Exp memory maxLiquidationIncentive = Exp({ mantissa: liquidationIncentiveMaxMantissa }); - if (lessThanExp(maxLiquidationIncentive, newLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param vToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(VToken vToken) external returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SUPPORT_MARKET_OWNER_CHECK); - } - - if (markets[address(vToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - vToken.isVToken(); // Sanity check to make sure its really a VToken - - // Note that isVenus is not in active use anymore - markets[address(vToken)] = Market({ isListed: true, isVenus: false, collateralFactorMantissa: 0 }); - - _addMarketInternal(vToken); - - emit MarketListed(vToken); - - return uint(Error.NO_ERROR); - } - - function _addMarketInternal(VToken vToken) internal { - for (uint i = 0; i < allMarkets.length; i++) { - require(allMarkets[i] != vToken, "market already added"); - } - allMarkets.push(vToken); - } - - /** - * @notice Set the given borrow caps for the given vToken markets. Borrowing that brings total borrows to or above borrow cap will revert. - * @dev Admin or borrowCapGuardian function to set the borrow caps. A borrow cap of 0 corresponds to unlimited borrowing. - * @param vTokens The addresses of the markets (tokens) to change the borrow caps for - * @param newBorrowCaps The new borrow cap values in underlying to be set. A value of 0 corresponds to unlimited borrowing. - */ - function _setMarketBorrowCaps(VToken[] calldata vTokens, uint[] calldata newBorrowCaps) external { - require( - msg.sender == admin || msg.sender == borrowCapGuardian, - "only admin or borrow cap guardian can set borrow caps" - ); - - uint numMarkets = vTokens.length; - uint numBorrowCaps = newBorrowCaps.length; - - require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input"); - - for (uint i = 0; i < numMarkets; i++) { - borrowCaps[address(vTokens[i])] = newBorrowCaps[i]; - emit NewBorrowCap(vTokens[i], newBorrowCaps[i]); - } - } - - /** - * @notice Admin function to change the Borrow Cap Guardian - * @param newBorrowCapGuardian The address of the new Borrow Cap Guardian - */ - function _setBorrowCapGuardian(address newBorrowCapGuardian) external { - require(msg.sender == admin, "only admin can set borrow cap guardian"); - - // Save current value for inclusion in log - address oldBorrowCapGuardian = borrowCapGuardian; - - // Store borrowCapGuardian with value newBorrowCapGuardian - borrowCapGuardian = newBorrowCapGuardian; - - // Emit NewBorrowCapGuardian(OldBorrowCapGuardian, NewBorrowCapGuardian) - emit NewBorrowCapGuardian(oldBorrowCapGuardian, newBorrowCapGuardian); - } - - /** - * @notice Set whole protocol pause/unpause state - */ - function _setProtocolPaused(bool state) public onlyAdmin returns (bool) { - protocolPaused = state; - emit ActionProtocolPaused(state); - return state; - } - - /** - * @notice Sets a new VAI controller - * @dev Admin function to set a new VAI controller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setVAIController(VAIControllerInterface vaiController_) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAICONTROLLER_OWNER_CHECK); - } - - VAIControllerInterface oldRate = vaiController; - vaiController = vaiController_; - emit NewVAIController(oldRate, vaiController_); - } - - function _setVAIMintRate(uint newVAIMintRate) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAI_MINT_RATE_CHECK); - } - - uint oldVAIMintRate = vaiMintRate; - vaiMintRate = newVAIMintRate; - emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller) public { - require(msg.sender == unitroller.admin(), "only unitroller admin can"); - require(unitroller._acceptImplementation() == 0, "not authorized"); - } - - /** - * @notice Checks caller is admin, or this contract is becoming the new implementation - */ - function adminOrInitializing() internal view returns (bool) { - return msg.sender == admin || msg.sender == comptrollerImplementation; - } - - /*** Venus Distribution ***/ - - function setVenusSpeedInternal(VToken vToken, uint venusSpeed) internal { - uint currentVenusSpeed = venusSpeeds[address(vToken)]; - if (currentVenusSpeed != 0) { - // note that XVS speed could be set to 0 to halt liquidity rewards for a market - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusSupplyIndex(address(vToken)); - updateVenusBorrowIndex(address(vToken), borrowIndex); - } else if (venusSpeed != 0) { - // Add the XVS market - Market storage market = markets[address(vToken)]; - require(market.isListed == true, "venus market is not listed"); - - if (venusSupplyState[address(vToken)].index == 0 && venusSupplyState[address(vToken)].block == 0) { - venusSupplyState[address(vToken)] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number exceeds 32 bits") - }); - } - - if (venusBorrowState[address(vToken)].index == 0 && venusBorrowState[address(vToken)].block == 0) { - venusBorrowState[address(vToken)] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number exceeds 32 bits") - }); - } - } - - if (currentVenusSpeed != venusSpeed) { - venusSpeeds[address(vToken)] = venusSpeed; - emit VenusSpeedUpdated(vToken, venusSpeed); - } - } - - /** - * @notice Accrue XVS to the market by updating the supply index - * @param vToken The market whose supply index to update - */ - function updateVenusSupplyIndex(address vToken) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - uint supplySpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(supplyState.block)); - if (deltaBlocks > 0 && supplySpeed > 0) { - uint supplyTokens = VToken(vToken).totalSupply(); - uint venusAccrued = mul_(deltaBlocks, supplySpeed); - Double memory ratio = supplyTokens > 0 ? fraction(venusAccrued, supplyTokens) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: supplyState.index }), ratio); - venusSupplyState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - supplyState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Accrue XVS to the market by updating the borrow index - * @param vToken The market whose borrow index to update - */ - function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - uint borrowSpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(borrowState.block)); - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); - uint venusAccrued = mul_(deltaBlocks, borrowSpeed); - Double memory ratio = borrowAmount > 0 ? fraction(venusAccrued, borrowAmount) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: borrowState.index }), ratio); - venusBorrowState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - borrowState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Calculate XVS accrued by a supplier and possibly transfer it to them - * @param vToken The market in which the supplier is interacting - * @param supplier The address of the supplier to distribute XVS to - */ - function distributeSupplierVenus(address vToken, address supplier) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - VenusMarketState storage supplyState = venusSupplyState[vToken]; - Double memory supplyIndex = Double({ mantissa: supplyState.index }); - Double memory supplierIndex = Double({ mantissa: venusSupplierIndex[vToken][supplier] }); - venusSupplierIndex[vToken][supplier] = supplyIndex.mantissa; - - if (supplierIndex.mantissa == 0 && supplyIndex.mantissa > 0) { - supplierIndex.mantissa = venusInitialIndex; - } - - Double memory deltaIndex = sub_(supplyIndex, supplierIndex); - uint supplierTokens = VToken(vToken).balanceOf(supplier); - uint supplierDelta = mul_(supplierTokens, deltaIndex); - uint supplierAccrued = add_(venusAccrued[supplier], supplierDelta); - venusAccrued[supplier] = supplierAccrued; - emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex.mantissa); - } - - /** - * @notice Calculate XVS accrued by a borrower and possibly transfer it to them - * @dev Borrowers will not begin to accrue until after the first interaction with the protocol. - * @param vToken The market in which the borrower is interacting - * @param borrower The address of the borrower to distribute XVS to - */ - function distributeBorrowerVenus(address vToken, address borrower, Exp memory marketBorrowIndex) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - VenusMarketState storage borrowState = venusBorrowState[vToken]; - Double memory borrowIndex = Double({ mantissa: borrowState.index }); - Double memory borrowerIndex = Double({ mantissa: venusBorrowerIndex[vToken][borrower] }); - venusBorrowerIndex[vToken][borrower] = borrowIndex.mantissa; - - if (borrowerIndex.mantissa > 0) { - Double memory deltaIndex = sub_(borrowIndex, borrowerIndex); - uint borrowerAmount = div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex); - uint borrowerDelta = mul_(borrowerAmount, deltaIndex); - uint borrowerAccrued = add_(venusAccrued[borrower], borrowerDelta); - venusAccrued[borrower] = borrowerAccrued; - emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex.mantissa); - } - } - - /** - * @notice Calculate XVS accrued by a VAI minter and possibly transfer it to them - * @dev VAI minters will not begin to accrue until after the first interaction with the protocol. - * @param vaiMinter The address of the VAI minter to distribute XVS to - */ - // solhint-disable-next-line no-unused-vars - function distributeVAIMinterVenus(address vaiMinter, bool distributeAll) public { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - if (address(vaiController) != address(0)) { - uint vaiMinterAccrued; - uint vaiMinterDelta; - uint vaiMintIndexMantissa; - uint err; - (err, vaiMinterAccrued, vaiMinterDelta, vaiMintIndexMantissa) = vaiController.calcDistributeVAIMinterVenus( - vaiMinter - ); - if (err == uint(Error.NO_ERROR)) { - venusAccrued[vaiMinter] = vaiMinterAccrued; - emit DistributedVAIMinterVenus(vaiMinter, vaiMinterDelta, vaiMintIndexMantissa); - } - } - } - - /** - * @notice Claim all the xvs accrued by holder in all markets and VAI - * @param holder The address to claim XVS for - */ - function claimVenus(address holder) public { - return claimVenus(holder, allMarkets); - } - - /** - * @notice Claim all the xvs accrued by holder in the specified markets - * @param holder The address to claim XVS for - * @param vTokens The list of markets to claim XVS in - */ - function claimVenus(address holder, VToken[] memory vTokens) public { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, vTokens, true, true); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - */ - function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { - uint j; - if (address(vaiController) != address(0)) { - vaiController.updateVenusVAIMintIndex(); - } - for (j = 0; j < holders.length; j++) { - distributeVAIMinterVenus(holders[j], true); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - for (uint i = 0; i < vTokens.length; i++) { - VToken vToken = vTokens[i]; - require(markets[address(vToken)].isListed, "not listed market"); - if (borrowers) { - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - for (j = 0; j < holders.length; j++) { - distributeBorrowerVenus(address(vToken), holders[j], borrowIndex); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - } - if (suppliers) { - updateVenusSupplyIndex(address(vToken)); - for (j = 0; j < holders.length; j++) { - distributeSupplierVenus(address(vToken), holders[j]); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - } - } - } - - /** - * @notice Transfer XVS to the user - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param user The address of the user to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - * @return The amount of XVS which was NOT transferred to the user - */ - function grantXVSInternal(address user, uint amount) internal returns (uint) { - XVS xvs = XVS(getXVSAddress()); - uint venusRemaining = xvs.balanceOf(address(this)); - if (amount > 0 && amount <= venusRemaining) { - xvs.transfer(user, amount); - return 0; - } - return amount; - } - - /*** Venus Distribution Admin ***/ - - /** - * @notice Set the amount of XVS distributed per block to VAI Vault - * @param venusVAIVaultRate_ The amount of XVS wei per block to distribute to VAI Vault - */ - function _setVenusVAIVaultRate(uint venusVAIVaultRate_) public { - require(msg.sender == admin, "only admin can"); - - uint oldVenusVAIVaultRate = venusVAIVaultRate; - venusVAIVaultRate = venusVAIVaultRate_; - emit NewVenusVAIVaultRate(oldVenusVAIVaultRate, venusVAIVaultRate_); - } - - /** - * @notice Set the VAI Vault infos - * @param vault_ The address of the VAI Vault - * @param releaseStartBlock_ The start block of release to VAI Vault - * @param minReleaseAmount_ The minimum release amount to VAI Vault - */ - function _setVAIVaultInfo(address vault_, uint256 releaseStartBlock_, uint256 minReleaseAmount_) public { - require(msg.sender == admin, "only admin can"); - - vaiVaultAddress = vault_; - releaseStartBlock = releaseStartBlock_; - minReleaseAmount = minReleaseAmount_; - emit NewVAIVaultInfo(vault_, releaseStartBlock_, minReleaseAmount_); - } - - /** - * @notice Set XVS speed for a single market - * @param vToken The market whose XVS speed to update - * @param venusSpeed New XVS speed for market - */ - function _setVenusSpeed(VToken vToken, uint venusSpeed) public { - require(adminOrInitializing(), "only admin can set venus speed"); - setVenusSpeedInternal(vToken, venusSpeed); - } - - /** - * @notice Return all of the markets - * @dev The automatic getter may be used to access an individual market. - * @return The list of market addresses - */ - function getAllMarkets() public view returns (VToken[] memory) { - return allMarkets; - } - - function getBlockNumber() public view returns (uint) { - return block.number; - } - - /** - * @notice Return the address of the XVS token - * @return The address of XVS - */ - function getXVSAddress() public view returns (address) { - return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; - } - - /*** VAI functions ***/ - - /** - * @notice Set the minted VAI amount of the `owner` - * @param owner The address of the account to set - * @param amount The amount of VAI to set to the account - * @return The number of minted VAI by `owner` - */ - function setMintedVAIOf(address owner, uint amount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); - // Check caller is vaiController - if (msg.sender != address(vaiController)) { - return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); - } - mintedVAIs[owner] = amount; - - return uint(Error.NO_ERROR); - } - - /** - * @notice Transfer XVS to VAI Vault - */ - function releaseToVault() public { - if (releaseStartBlock == 0 || getBlockNumber() < releaseStartBlock) { - return; - } - - XVS xvs = XVS(getXVSAddress()); - - uint256 xvsBalance = xvs.balanceOf(address(this)); - if (xvsBalance == 0) { - return; - } - - uint256 actualAmount; - uint256 deltaBlocks = sub_(getBlockNumber(), releaseStartBlock); - // releaseAmount = venusVAIVaultRate * deltaBlocks - uint256 _releaseAmount = mul_(venusVAIVaultRate, deltaBlocks); - - if (_releaseAmount < minReleaseAmount) { - return; - } - - if (xvsBalance >= _releaseAmount) { - actualAmount = _releaseAmount; - } else { - actualAmount = xvsBalance; - } - - releaseStartBlock = getBlockNumber(); - - xvs.transfer(vaiVaultAddress, actualAmount); - emit DistributedVAIVaultVenus(actualAmount); - - IVAIVault(vaiVaultAddress).updatePendingRewards(); - } -} diff --git a/contracts/Comptroller/ComptrollerG4.sol b/contracts/Comptroller/ComptrollerG4.sol deleted file mode 100644 index aa478da38..000000000 --- a/contracts/Comptroller/ComptrollerG4.sol +++ /dev/null @@ -1,1560 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VTokens/VToken.sol"; -import "../Utils/ErrorReporter.sol"; -import "../Tokens/XVS/XVS.sol"; -import "../Tokens/VAI/VAI.sol"; -import "./ComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Venus's Comptroller Contract - * @author Venus - */ -contract ComptrollerG4 is ComptrollerV4Storage, ComptrollerInterfaceG2, ComptrollerErrorReporter, ExponentialNoError { - /// @notice Emitted when an admin supports a market - event MarketListed(VToken vToken); - - /// @notice Emitted when an account enters a market - event MarketEntered(VToken vToken, address account); - - /// @notice Emitted when an account exits a market - event MarketExited(VToken vToken, address account); - - /// @notice Emitted when close factor is changed by admin - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /// @notice Emitted when a collateral factor is changed by admin - event NewCollateralFactor(VToken vToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /// @notice Emitted when liquidation incentive is changed by admin - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /// @notice Emitted when price oracle is changed - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /// @notice Emitted when VAI Vault info is changed - event NewVAIVaultInfo(address vault_, uint releaseStartBlock_, uint releaseInterval_); - - /// @notice Emitted when pause guardian is changed - event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); - - /// @notice Emitted when an action is paused globally - event ActionPaused(string action, bool pauseState); - - /// @notice Emitted when an action is paused on a market - event ActionPaused(VToken vToken, string action, bool pauseState); - - /// @notice Emitted when Venus VAI Vault rate is changed - event NewVenusVAIVaultRate(uint oldVenusVAIVaultRate, uint newVenusVAIVaultRate); - - /// @notice Emitted when a new Venus speed is calculated for a market - event VenusSpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when XVS is distributed to a supplier - event DistributedSupplierVenus( - VToken indexed vToken, - address indexed supplier, - uint venusDelta, - uint venusSupplyIndex - ); - - /// @notice Emitted when XVS is distributed to a borrower - event DistributedBorrowerVenus( - VToken indexed vToken, - address indexed borrower, - uint venusDelta, - uint venusBorrowIndex - ); - - /// @notice Emitted when XVS is distributed to a VAI minter - event DistributedVAIMinterVenus(address indexed vaiMinter, uint venusDelta, uint venusVAIMintIndex); - - /// @notice Emitted when XVS is distributed to VAI Vault - event DistributedVAIVaultVenus(uint amount); - - /// @notice Emitted when VAIController is changed - event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); - - /// @notice Emitted when VAI mint rate is changed by admin - event NewVAIMintRate(uint oldVAIMintRate, uint newVAIMintRate); - - /// @notice Emitted when protocol state is changed by admin - event ActionProtocolPaused(bool state); - - /// @notice Emitted when borrow cap for a vToken is changed - event NewBorrowCap(VToken indexed vToken, uint newBorrowCap); - - /// @notice Emitted when borrow cap guardian is changed - event NewBorrowCapGuardian(address oldBorrowCapGuardian, address newBorrowCapGuardian); - - /// @notice Emitted when treasury guardian is changed - event NewTreasuryGuardian(address oldTreasuryGuardian, address newTreasuryGuardian); - - /// @notice Emitted when treasury address is changed - event NewTreasuryAddress(address oldTreasuryAddress, address newTreasuryAddress); - - /// @notice Emitted when treasury percent is changed - event NewTreasuryPercent(uint oldTreasuryPercent, uint newTreasuryPercent); - - /// @notice The initial Venus index for a market - uint224 public constant venusInitialIndex = 1e36; - - // closeFactorMantissa must be strictly greater than this value - uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05 - - // closeFactorMantissa must not exceed this value - uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 - - constructor() public { - admin = msg.sender; - } - - modifier onlyProtocolAllowed() { - require(!protocolPaused, "protocol is paused"); - _; - } - - modifier onlyAdmin() { - require(msg.sender == admin, "only admin can"); - _; - } - - modifier onlyListedMarket(VToken vToken) { - require(markets[address(vToken)].isListed, "venus market is not listed"); - _; - } - - modifier validPauseState(bool state) { - require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can"); - require(msg.sender == admin || state == true, "only admin can unpause"); - _; - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (VToken[] memory) { - return accountAssets[account]; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param vToken The vToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, VToken vToken) external view returns (bool) { - return markets[address(vToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param vTokens The list of addresses of the vToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory) { - uint len = vTokens.length; - - uint[] memory results = new uint[](len); - for (uint i = 0; i < len; i++) { - results[i] = uint(addToMarketInternal(VToken(vTokens[i]), msg.sender)); - } - - return results; - } - - /** - * @notice Add the market to the borrower's "assets in" for liquidity calculations - * @param vToken The market to enter - * @param borrower The address of the account to modify - * @return Success indicator for whether the market was entered - */ - function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { - Market storage marketToJoin = markets[address(vToken)]; - - if (!marketToJoin.isListed) { - // market is not listed, cannot join - return Error.MARKET_NOT_LISTED; - } - - if (marketToJoin.accountMembership[borrower]) { - // already joined - return Error.NO_ERROR; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[borrower] = true; - accountAssets[borrower].push(vToken); - - emit MarketEntered(vToken, borrower); - - return Error.NO_ERROR; - } - - /** - * @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 vTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address vTokenAddress) external returns (uint) { - VToken vToken = VToken(vTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the vToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = vToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(vToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set vToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete vToken from the account’s list of assets */ - // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 - VToken[] storage userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint i; - for (; i < len; i++) { - if (userAssetList[i] == vToken) { - userAssetList[i] = userAssetList[len - 1]; - userAssetList.length--; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(i < len); - - emit MarketExited(vToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param vToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address vToken, address minter, uint mintAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintGuardianPaused[vToken], "mint is paused"); - - // Shh - currently unused - mintAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, minter); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates mint and reverts on rejection. May emit logs. - * @param vToken Asset being minted - * @param minter The address minting the tokens - * @param actualMintAmount The amount of the underlying asset being minted - * @param mintTokens The number of tokens being minted - */ - function mintVerify(address vToken, address minter, uint actualMintAmount, uint mintTokens) external { - // Shh - currently unused - vToken; - minter; - actualMintAmount; - mintTokens; - } - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param vToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed( - address vToken, - address redeemer, - uint redeemTokens - ) external onlyProtocolAllowed returns (uint) { - uint allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, redeemer); - - return uint(Error.NO_ERROR); - } - - function redeemAllowedInternal(address vToken, address redeemer, uint redeemTokens) internal view returns (uint) { - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[vToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - redeemer, - VToken(vToken), - redeemTokens, - 0 - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates redeem and reverts on rejection. May emit logs. - * @param vToken Asset being redeemed - * @param redeemer The address redeeming the tokens - * @param redeemAmount The amount of the underlying asset being redeemed - * @param redeemTokens The number of tokens being redeemed - */ - function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external { - // Shh - currently unused - vToken; - redeemer; - - // Require tokens is zero or amount is also zero - require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param vToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed( - address vToken, - address borrower, - uint borrowAmount - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!borrowGuardianPaused[vToken], "borrow is paused"); - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (!markets[vToken].accountMembership[borrower]) { - // only vTokens may call borrowAllowed if borrower not in market - require(msg.sender == vToken, "sender must be vToken"); - - // attempt to add borrower to the market - Error err = addToMarketInternal(VToken(vToken), borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - } - - if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - uint borrowCap = borrowCaps[vToken]; - // Borrow cap of 0 corresponds to unlimited borrowing - if (borrowCap != 0) { - uint totalBorrows = VToken(vToken).totalBorrows(); - uint nextTotalBorrows = add_(totalBorrows, borrowAmount); - require(nextTotalBorrows < borrowCap, "market borrow cap reached"); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - borrower, - VToken(vToken), - 0, - borrowAmount - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates borrow and reverts on rejection. May emit logs. - * @param vToken Asset whose underlying is being borrowed - * @param borrower The address borrowing the underlying - * @param borrowAmount The amount of the underlying asset requested to borrow - */ - function borrowVerify(address vToken, address borrower, uint borrowAmount) external { - // Shh - currently unused - vToken; - borrower; - borrowAmount; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param vToken The market to verify the repay against - * @param payer The account which would repay the asset - * @param borrower The account which would repay the asset - * @param repayAmount The amount of the underlying asset the account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address vToken, - address payer, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - payer; - borrower; - repayAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates repayBorrow and reverts on rejection. May emit logs. - * @param vToken Asset being repaid - * @param payer The address repaying the borrow - * @param borrower The address of the borrower - * @param actualRepayAmount The amount of underlying being repaid - */ - function repayBorrowVerify( - address vToken, - address payer, - address borrower, - uint actualRepayAmount, - uint borrowerIndex - ) external { - // Shh - currently unused - vToken; - payer; - borrower; - actualRepayAmount; - borrowerIndex; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - liquidator; - - if ( - !(markets[vTokenBorrowed].isListed || address(vTokenBorrowed) == address(vaiController)) || - !markets[vTokenCollateral].isListed - ) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(0), 0, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - /* The liquidator may not repay more than what is allowed by the closeFactor */ - uint borrowBalance; - if (address(vTokenBorrowed) != address(vaiController)) { - borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); - } else { - borrowBalance = mintedVAIs[borrower]; - } - - uint maxClose = mul_ScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance); - if (repayAmount > maxClose) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 actualRepayAmount The amount of underlying being repaid - */ - function liquidateBorrowVerify( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint actualRepayAmount, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenBorrowed; - vTokenCollateral; - liquidator; - borrower; - actualRepayAmount; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!seizeGuardianPaused, "seize is paused"); - - // Shh - currently unused - seizeTokens; - - // We've added VAIController as a borrowed token list check for seize - if ( - !markets[vTokenCollateral].isListed || - !(markets[vTokenBorrowed].isListed || address(vTokenBorrowed) == address(vaiController)) - ) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vTokenCollateral); - distributeSupplierVenus(vTokenCollateral, borrower); - distributeSupplierVenus(vTokenCollateral, liquidator); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates seize and reverts on rejection. May emit logs. - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 seizeVerify( - address vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenCollateral; - vTokenBorrowed; - liquidator; - borrower; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param vToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed( - address vToken, - address src, - address dst, - uint transferTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!transferGuardianPaused, "transfer is paused"); - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - uint allowed = redeemAllowedInternal(vToken, src, transferTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, src); - distributeSupplierVenus(vToken, dst); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates transfer and reverts on rejection. May emit logs. - * @param vToken Asset being transferred - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - */ - function transferVerify(address vToken, address src, address dst, uint transferTokens) external { - // Shh - currently unused - vToken; - src; - dst; - transferTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /*** Liquidity/Liquidation Calculations ***/ - - /** - * @dev Local vars for avoiding stack-depth limits in calculating account liquidity. - * Note that `vTokenBalance` is the number of vTokens the account owns in the market, - * whereas `borrowBalance` is the amount of underlying that the account has borrowed. - */ - struct AccountLiquidityLocalVars { - uint sumCollateral; - uint sumBorrowPlusEffects; - uint vTokenBalance; - uint borrowBalance; - uint exchangeRateMantissa; - uint oraclePriceMantissa; - Exp collateralFactor; - Exp exchangeRate; - Exp oraclePrice; - Exp tokensToDenom; - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, VToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @return (possible error code (semi-opaque), - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidity( - address account, - address vTokenModify, - uint redeemTokens, - uint borrowAmount - ) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal( - account, - VToken(vTokenModify), - redeemTokens, - borrowAmount - ); - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidityInternal( - address account, - VToken vTokenModify, - uint redeemTokens, - uint borrowAmount - ) internal view returns (Error, uint, uint) { - AccountLiquidityLocalVars memory vars; // Holds all our calculation results - uint oErr; - - // For each asset the account is in - VToken[] memory assets = accountAssets[account]; - for (uint i = 0; i < assets.length; i++) { - VToken asset = assets[i]; - - // Read the balances and exchange rate from the vToken - (oErr, vars.vTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot( - account - ); - if (oErr != 0) { - // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0); - } - vars.collateralFactor = Exp({ mantissa: markets[address(asset)].collateralFactorMantissa }); - vars.exchangeRate = Exp({ mantissa: vars.exchangeRateMantissa }); - - // Get the normalized price of the asset - vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset); - if (vars.oraclePriceMantissa == 0) { - return (Error.PRICE_ERROR, 0, 0); - } - vars.oraclePrice = Exp({ mantissa: vars.oraclePriceMantissa }); - - // Pre-compute a conversion factor from tokens -> bnb (normalized price value) - vars.tokensToDenom = mul_(mul_(vars.collateralFactor, vars.exchangeRate), vars.oraclePrice); - - // sumCollateral += tokensToDenom * vTokenBalance - vars.sumCollateral = mul_ScalarTruncateAddUInt(vars.tokensToDenom, vars.vTokenBalance, vars.sumCollateral); - - // sumBorrowPlusEffects += oraclePrice * borrowBalance - vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( - vars.oraclePrice, - vars.borrowBalance, - vars.sumBorrowPlusEffects - ); - - // Calculate effects of interacting with vTokenModify - if (asset == vTokenModify) { - // redeem effect - // sumBorrowPlusEffects += tokensToDenom * redeemTokens - vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( - vars.tokensToDenom, - redeemTokens, - vars.sumBorrowPlusEffects - ); - - // borrow effect - // sumBorrowPlusEffects += oraclePrice * borrowAmount - vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( - vars.oraclePrice, - borrowAmount, - vars.sumBorrowPlusEffects - ); - } - } - - vars.sumBorrowPlusEffects = add_(vars.sumBorrowPlusEffects, mintedVAIs[account]); - - // These are safe, as the underflow condition is checked first - if (vars.sumCollateral > vars.sumBorrowPlusEffects) { - return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0); - } else { - return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral); - } - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenBorrowed The address of the borrowed vToken - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = oracle.getUnderlyingPrice(VToken(vTokenBorrowed)); - uint priceCollateralMantissa = oracle.getUnderlyingPrice(VToken(vTokenCollateral)); - if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - - numerator = mul_(Exp({ mantissa: liquidationIncentiveMantissa }), Exp({ mantissa: priceBorrowedMantissa })); - denominator = mul_(Exp({ mantissa: priceCollateralMantissa }), Exp({ mantissa: exchangeRateMantissa })); - ratio = div_(numerator, denominator); - - seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount); - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateVAICalculateSeizeTokens( - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = 1e18; // Note: this is VAI - uint priceCollateralMantissa = oracle.getUnderlyingPrice(VToken(vTokenCollateral)); - if (priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - - numerator = mul_(Exp({ mantissa: liquidationIncentiveMantissa }), Exp({ mantissa: priceBorrowedMantissa })); - denominator = mul_(Exp({ mantissa: priceCollateralMantissa }), Exp({ mantissa: exchangeRateMantissa })); - ratio = div_(numerator, denominator); - - seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount); - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) public returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK); - } - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) { - // Check caller is admin - require(msg.sender == admin, "only admin can set close factor"); - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Admin function to set per-market collateralFactor - * @param vToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(VToken vToken, uint newCollateralFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK); - } - - // Verify market is listed - Market storage market = markets[address(vToken)]; - if (!market.isListed) { - return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS); - } - - Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { - return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK); - } - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param vToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(VToken vToken) external returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SUPPORT_MARKET_OWNER_CHECK); - } - - if (markets[address(vToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - vToken.isVToken(); // Sanity check to make sure its really a VToken - - // Note that isVenus is not in active use anymore - markets[address(vToken)] = Market({ isListed: true, isVenus: false, collateralFactorMantissa: 0 }); - - _addMarketInternal(vToken); - - emit MarketListed(vToken); - - return uint(Error.NO_ERROR); - } - - function _addMarketInternal(VToken vToken) internal { - for (uint i = 0; i < allMarkets.length; i++) { - require(allMarkets[i] != vToken, "market already added"); - } - allMarkets.push(vToken); - } - - /** - * @notice Admin function to change the Pause Guardian - * @param newPauseGuardian The address of the new Pause Guardian - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _setPauseGuardian(address newPauseGuardian) public returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PAUSE_GUARDIAN_OWNER_CHECK); - } - - // Save current value for inclusion in log - address oldPauseGuardian = pauseGuardian; - - // Store pauseGuardian with value newPauseGuardian - pauseGuardian = newPauseGuardian; - - // Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian) - emit NewPauseGuardian(oldPauseGuardian, newPauseGuardian); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Set the given borrow caps for the given vToken markets. Borrowing that brings total borrows to or above borrow cap will revert. - * @dev Admin or borrowCapGuardian function to set the borrow caps. A borrow cap of 0 corresponds to unlimited borrowing. - * @param vTokens The addresses of the markets (tokens) to change the borrow caps for - * @param newBorrowCaps The new borrow cap values in underlying to be set. A value of 0 corresponds to unlimited borrowing. - */ - function _setMarketBorrowCaps(VToken[] calldata vTokens, uint[] calldata newBorrowCaps) external { - require( - msg.sender == admin || msg.sender == borrowCapGuardian, - "only admin or borrow cap guardian can set borrow caps" - ); - - uint numMarkets = vTokens.length; - uint numBorrowCaps = newBorrowCaps.length; - - require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input"); - - for (uint i = 0; i < numMarkets; i++) { - borrowCaps[address(vTokens[i])] = newBorrowCaps[i]; - emit NewBorrowCap(vTokens[i], newBorrowCaps[i]); - } - } - - /** - * @notice Admin function to change the Borrow Cap Guardian - * @param newBorrowCapGuardian The address of the new Borrow Cap Guardian - */ - function _setBorrowCapGuardian(address newBorrowCapGuardian) external onlyAdmin { - // Save current value for inclusion in log - address oldBorrowCapGuardian = borrowCapGuardian; - - // Store borrowCapGuardian with value newBorrowCapGuardian - borrowCapGuardian = newBorrowCapGuardian; - - // Emit NewBorrowCapGuardian(OldBorrowCapGuardian, NewBorrowCapGuardian) - emit NewBorrowCapGuardian(oldBorrowCapGuardian, newBorrowCapGuardian); - } - - /** - * @notice Set whole protocol pause/unpause state - */ - function _setProtocolPaused(bool state) public validPauseState(state) returns (bool) { - protocolPaused = state; - emit ActionProtocolPaused(state); - return state; - } - - /** - * @notice Sets a new VAI controller - * @dev Admin function to set a new VAI controller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setVAIController(VAIControllerInterface vaiController_) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAICONTROLLER_OWNER_CHECK); - } - - VAIControllerInterface oldRate = vaiController; - vaiController = vaiController_; - emit NewVAIController(oldRate, vaiController_); - } - - function _setVAIMintRate(uint newVAIMintRate) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAI_MINT_RATE_CHECK); - } - - uint oldVAIMintRate = vaiMintRate; - vaiMintRate = newVAIMintRate; - emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); - - return uint(Error.NO_ERROR); - } - - function _setTreasuryData( - address newTreasuryGuardian, - address newTreasuryAddress, - uint newTreasuryPercent - ) external returns (uint) { - // Check caller is admin - if (!(msg.sender == admin || msg.sender == treasuryGuardian)) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_TREASURY_OWNER_CHECK); - } - - require(newTreasuryPercent < 1e18, "treasury percent cap overflow"); - - address oldTreasuryGuardian = treasuryGuardian; - address oldTreasuryAddress = treasuryAddress; - uint oldTreasuryPercent = treasuryPercent; - - treasuryGuardian = newTreasuryGuardian; - treasuryAddress = newTreasuryAddress; - treasuryPercent = newTreasuryPercent; - - emit NewTreasuryGuardian(oldTreasuryGuardian, newTreasuryGuardian); - emit NewTreasuryAddress(oldTreasuryAddress, newTreasuryAddress); - emit NewTreasuryPercent(oldTreasuryPercent, newTreasuryPercent); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller) public { - require(msg.sender == unitroller.admin(), "only unitroller admin can"); - require(unitroller._acceptImplementation() == 0, "not authorized"); - } - - /** - * @notice Checks caller is admin, or this contract is becoming the new implementation - */ - function adminOrInitializing() internal view returns (bool) { - return msg.sender == admin || msg.sender == comptrollerImplementation; - } - - /*** Venus Distribution ***/ - - function setVenusSpeedInternal(VToken vToken, uint venusSpeed) internal { - uint currentVenusSpeed = venusSpeeds[address(vToken)]; - if (currentVenusSpeed != 0) { - // note that XVS speed could be set to 0 to halt liquidity rewards for a market - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusSupplyIndex(address(vToken)); - updateVenusBorrowIndex(address(vToken), borrowIndex); - } else if (venusSpeed != 0) { - // Add the XVS market - Market storage market = markets[address(vToken)]; - require(market.isListed == true, "venus market is not listed"); - - if (venusSupplyState[address(vToken)].index == 0 && venusSupplyState[address(vToken)].block == 0) { - venusSupplyState[address(vToken)] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number exceeds 32 bits") - }); - } - - if (venusBorrowState[address(vToken)].index == 0 && venusBorrowState[address(vToken)].block == 0) { - venusBorrowState[address(vToken)] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number exceeds 32 bits") - }); - } - } - - if (currentVenusSpeed != venusSpeed) { - venusSpeeds[address(vToken)] = venusSpeed; - emit VenusSpeedUpdated(vToken, venusSpeed); - } - } - - /** - * @notice Accrue XVS to the market by updating the supply index - * @param vToken The market whose supply index to update - */ - function updateVenusSupplyIndex(address vToken) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - uint supplySpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(supplyState.block)); - if (deltaBlocks > 0 && supplySpeed > 0) { - uint supplyTokens = VToken(vToken).totalSupply(); - uint venusAccrued = mul_(deltaBlocks, supplySpeed); - Double memory ratio = supplyTokens > 0 ? fraction(venusAccrued, supplyTokens) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: supplyState.index }), ratio); - venusSupplyState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - supplyState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Accrue XVS to the market by updating the borrow index - * @param vToken The market whose borrow index to update - */ - function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - uint borrowSpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(borrowState.block)); - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); - uint venusAccrued = mul_(deltaBlocks, borrowSpeed); - Double memory ratio = borrowAmount > 0 ? fraction(venusAccrued, borrowAmount) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: borrowState.index }), ratio); - venusBorrowState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - borrowState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Calculate XVS accrued by a supplier and possibly transfer it to them - * @param vToken The market in which the supplier is interacting - * @param supplier The address of the supplier to distribute XVS to - */ - function distributeSupplierVenus(address vToken, address supplier) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - VenusMarketState storage supplyState = venusSupplyState[vToken]; - Double memory supplyIndex = Double({ mantissa: supplyState.index }); - Double memory supplierIndex = Double({ mantissa: venusSupplierIndex[vToken][supplier] }); - venusSupplierIndex[vToken][supplier] = supplyIndex.mantissa; - - if (supplierIndex.mantissa == 0 && supplyIndex.mantissa > 0) { - supplierIndex.mantissa = venusInitialIndex; - } - - Double memory deltaIndex = sub_(supplyIndex, supplierIndex); - uint supplierTokens = VToken(vToken).balanceOf(supplier); - uint supplierDelta = mul_(supplierTokens, deltaIndex); - uint supplierAccrued = add_(venusAccrued[supplier], supplierDelta); - venusAccrued[supplier] = supplierAccrued; - emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex.mantissa); - } - - /** - * @notice Calculate XVS accrued by a borrower and possibly transfer it to them - * @dev Borrowers will not begin to accrue until after the first interaction with the protocol. - * @param vToken The market in which the borrower is interacting - * @param borrower The address of the borrower to distribute XVS to - */ - function distributeBorrowerVenus(address vToken, address borrower, Exp memory marketBorrowIndex) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - VenusMarketState storage borrowState = venusBorrowState[vToken]; - Double memory borrowIndex = Double({ mantissa: borrowState.index }); - Double memory borrowerIndex = Double({ mantissa: venusBorrowerIndex[vToken][borrower] }); - venusBorrowerIndex[vToken][borrower] = borrowIndex.mantissa; - - if (borrowerIndex.mantissa > 0) { - Double memory deltaIndex = sub_(borrowIndex, borrowerIndex); - uint borrowerAmount = div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex); - uint borrowerDelta = mul_(borrowerAmount, deltaIndex); - uint borrowerAccrued = add_(venusAccrued[borrower], borrowerDelta); - venusAccrued[borrower] = borrowerAccrued; - emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex.mantissa); - } - } - - /** - * @notice Calculate XVS accrued by a VAI minter and possibly transfer it to them - * @dev VAI minters will not begin to accrue until after the first interaction with the protocol. - * @param vaiMinter The address of the VAI minter to distribute XVS to - */ - function distributeVAIMinterVenus(address vaiMinter) public { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - if (address(vaiController) != address(0)) { - uint vaiMinterAccrued; - uint vaiMinterDelta; - uint vaiMintIndexMantissa; - uint err; - (err, vaiMinterAccrued, vaiMinterDelta, vaiMintIndexMantissa) = vaiController.calcDistributeVAIMinterVenus( - vaiMinter - ); - if (err == uint(Error.NO_ERROR)) { - venusAccrued[vaiMinter] = vaiMinterAccrued; - emit DistributedVAIMinterVenus(vaiMinter, vaiMinterDelta, vaiMintIndexMantissa); - } - } - } - - /** - * @notice Claim all the xvs accrued by holder in all markets and VAI - * @param holder The address to claim XVS for - */ - function claimVenus(address holder) public { - return claimVenus(holder, allMarkets); - } - - /** - * @notice Claim all the xvs accrued by holder in the specified markets - * @param holder The address to claim XVS for - * @param vTokens The list of markets to claim XVS in - */ - function claimVenus(address holder, VToken[] memory vTokens) public { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, vTokens, true, true); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - */ - function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { - uint j; - if (address(vaiController) != address(0)) { - vaiController.updateVenusVAIMintIndex(); - } - for (j = 0; j < holders.length; j++) { - distributeVAIMinterVenus(holders[j]); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - for (uint i = 0; i < vTokens.length; i++) { - VToken vToken = vTokens[i]; - require(markets[address(vToken)].isListed, "not listed market"); - if (borrowers) { - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - for (j = 0; j < holders.length; j++) { - distributeBorrowerVenus(address(vToken), holders[j], borrowIndex); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - } - if (suppliers) { - updateVenusSupplyIndex(address(vToken)); - for (j = 0; j < holders.length; j++) { - distributeSupplierVenus(address(vToken), holders[j]); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - } - } - } - - /** - * @notice Transfer XVS to the user - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param user The address of the user to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - * @return The amount of XVS which was NOT transferred to the user - */ - function grantXVSInternal(address user, uint amount) internal returns (uint) { - XVS xvs = XVS(getXVSAddress()); - uint venusRemaining = xvs.balanceOf(address(this)); - if (amount > 0 && amount <= venusRemaining) { - xvs.transfer(user, amount); - return 0; - } - return amount; - } - - /*** Venus Distribution Admin ***/ - - /** - * @notice Set the amount of XVS distributed per block to VAI Vault - * @param venusVAIVaultRate_ The amount of XVS wei per block to distribute to VAI Vault - */ - function _setVenusVAIVaultRate(uint venusVAIVaultRate_) public onlyAdmin { - uint oldVenusVAIVaultRate = venusVAIVaultRate; - venusVAIVaultRate = venusVAIVaultRate_; - emit NewVenusVAIVaultRate(oldVenusVAIVaultRate, venusVAIVaultRate_); - } - - /** - * @notice Set the VAI Vault infos - * @param vault_ The address of the VAI Vault - * @param releaseStartBlock_ The start block of release to VAI Vault - * @param minReleaseAmount_ The minimum release amount to VAI Vault - */ - function _setVAIVaultInfo(address vault_, uint256 releaseStartBlock_, uint256 minReleaseAmount_) public onlyAdmin { - vaiVaultAddress = vault_; - releaseStartBlock = releaseStartBlock_; - minReleaseAmount = minReleaseAmount_; - emit NewVAIVaultInfo(vault_, releaseStartBlock_, minReleaseAmount_); - } - - /** - * @notice Set XVS speed for a single market - * @param vToken The market whose XVS speed to update - * @param venusSpeed New XVS speed for market - */ - function _setVenusSpeed(VToken vToken, uint venusSpeed) public { - require(adminOrInitializing(), "only admin can set venus speed"); - setVenusSpeedInternal(vToken, venusSpeed); - } - - /** - * @notice Return all of the markets - * @dev The automatic getter may be used to access an individual market. - * @return The list of market addresses - */ - function getAllMarkets() public view returns (VToken[] memory) { - return allMarkets; - } - - function getBlockNumber() public view returns (uint) { - return block.number; - } - - /** - * @notice Return the address of the XVS token - * @return The address of XVS - */ - function getXVSAddress() public view returns (address) { - return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; - } - - /*** VAI functions ***/ - - /** - * @notice Set the minted VAI amount of the `owner` - * @param owner The address of the account to set - * @param amount The amount of VAI to set to the account - * @return The number of minted VAI by `owner` - */ - function setMintedVAIOf(address owner, uint amount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); - // Check caller is vaiController - if (msg.sender != address(vaiController)) { - return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); - } - mintedVAIs[owner] = amount; - - return uint(Error.NO_ERROR); - } - - /** - * @notice Transfer XVS to VAI Vault - */ - function releaseToVault() public { - if (releaseStartBlock == 0 || getBlockNumber() < releaseStartBlock) { - return; - } - - XVS xvs = XVS(getXVSAddress()); - - uint256 xvsBalance = xvs.balanceOf(address(this)); - if (xvsBalance == 0) { - return; - } - - uint256 actualAmount; - uint256 deltaBlocks = sub_(getBlockNumber(), releaseStartBlock); - // releaseAmount = venusVAIVaultRate * deltaBlocks - uint256 _releaseAmount = mul_(venusVAIVaultRate, deltaBlocks); - - if (_releaseAmount < minReleaseAmount) { - return; - } - - if (xvsBalance >= _releaseAmount) { - actualAmount = _releaseAmount; - } else { - actualAmount = xvsBalance; - } - - releaseStartBlock = getBlockNumber(); - - xvs.transfer(vaiVaultAddress, actualAmount); - emit DistributedVAIVaultVenus(actualAmount); - - IVAIVault(vaiVaultAddress).updatePendingRewards(); - } -} diff --git a/contracts/Comptroller/ComptrollerG5.sol b/contracts/Comptroller/ComptrollerG5.sol deleted file mode 100644 index e1c447f27..000000000 --- a/contracts/Comptroller/ComptrollerG5.sol +++ /dev/null @@ -1,1576 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VTokens/VToken.sol"; -import "../Utils/ErrorReporter.sol"; -import "../Tokens/XVS/XVS.sol"; -import "../Tokens/VAI/VAI.sol"; -import "./ComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Venus's Comptroller Contract - * @author Venus - */ -contract ComptrollerG5 is ComptrollerV5Storage, ComptrollerInterfaceG2, ComptrollerErrorReporter, ExponentialNoError { - /// @notice Emitted when an admin supports a market - event MarketListed(VToken vToken); - - /// @notice Emitted when an account enters a market - event MarketEntered(VToken vToken, address account); - - /// @notice Emitted when an account exits a market - event MarketExited(VToken vToken, address account); - - /// @notice Emitted when close factor is changed by admin - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /// @notice Emitted when a collateral factor is changed by admin - event NewCollateralFactor(VToken vToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /// @notice Emitted when liquidation incentive is changed by admin - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /// @notice Emitted when price oracle is changed - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /// @notice Emitted when VAI Vault info is changed - event NewVAIVaultInfo(address vault_, uint releaseStartBlock_, uint releaseInterval_); - - /// @notice Emitted when pause guardian is changed - event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); - - /// @notice Emitted when an action is paused globally - event ActionPaused(string action, bool pauseState); - - /// @notice Emitted when an action is paused on a market - event ActionPaused(VToken vToken, string action, bool pauseState); - - /// @notice Emitted when Venus VAI Vault rate is changed - event NewVenusVAIVaultRate(uint oldVenusVAIVaultRate, uint newVenusVAIVaultRate); - - /// @notice Emitted when a new Venus speed is calculated for a market - event VenusSpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when XVS is distributed to a supplier - event DistributedSupplierVenus( - VToken indexed vToken, - address indexed supplier, - uint venusDelta, - uint venusSupplyIndex - ); - - /// @notice Emitted when XVS is distributed to a borrower - event DistributedBorrowerVenus( - VToken indexed vToken, - address indexed borrower, - uint venusDelta, - uint venusBorrowIndex - ); - - /// @notice Emitted when XVS is distributed to a VAI minter - event DistributedVAIMinterVenus(address indexed vaiMinter, uint venusDelta, uint venusVAIMintIndex); - - /// @notice Emitted when XVS is distributed to VAI Vault - event DistributedVAIVaultVenus(uint amount); - - /// @notice Emitted when VAIController is changed - event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); - - /// @notice Emitted when VAI mint rate is changed by admin - event NewVAIMintRate(uint oldVAIMintRate, uint newVAIMintRate); - - /// @notice Emitted when protocol state is changed by admin - event ActionProtocolPaused(bool state); - - /// @notice Emitted when borrow cap for a vToken is changed - event NewBorrowCap(VToken indexed vToken, uint newBorrowCap); - - /// @notice Emitted when borrow cap guardian is changed - event NewBorrowCapGuardian(address oldBorrowCapGuardian, address newBorrowCapGuardian); - - /// @notice Emitted when treasury guardian is changed - event NewTreasuryGuardian(address oldTreasuryGuardian, address newTreasuryGuardian); - - /// @notice Emitted when treasury address is changed - event NewTreasuryAddress(address oldTreasuryAddress, address newTreasuryAddress); - - /// @notice Emitted when treasury percent is changed - event NewTreasuryPercent(uint oldTreasuryPercent, uint newTreasuryPercent); - - /// @notice Emitted when Venus is granted by admin - event VenusGranted(address recipient, uint amount); - - /// @notice The initial Venus index for a market - uint224 public constant venusInitialIndex = 1e36; - - // closeFactorMantissa must be strictly greater than this value - uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05 - - // closeFactorMantissa must not exceed this value - uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 - - constructor() public { - admin = msg.sender; - } - - modifier onlyProtocolAllowed() { - require(!protocolPaused, "protocol is paused"); - _; - } - - modifier onlyAdmin() { - require(msg.sender == admin, "only admin can"); - _; - } - - modifier onlyListedMarket(VToken vToken) { - require(markets[address(vToken)].isListed, "venus market is not listed"); - _; - } - - modifier validPauseState(bool state) { - require(msg.sender == pauseGuardian || msg.sender == admin, "only pause guardian and admin can"); - require(msg.sender == admin || state == true, "only admin can unpause"); - _; - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (VToken[] memory) { - return accountAssets[account]; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param vToken The vToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, VToken vToken) external view returns (bool) { - return markets[address(vToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param vTokens The list of addresses of the vToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory) { - uint len = vTokens.length; - - uint[] memory results = new uint[](len); - for (uint i = 0; i < len; i++) { - results[i] = uint(addToMarketInternal(VToken(vTokens[i]), msg.sender)); - } - - return results; - } - - /** - * @notice Add the market to the borrower's "assets in" for liquidity calculations - * @param vToken The market to enter - * @param borrower The address of the account to modify - * @return Success indicator for whether the market was entered - */ - function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { - Market storage marketToJoin = markets[address(vToken)]; - - if (!marketToJoin.isListed) { - // market is not listed, cannot join - return Error.MARKET_NOT_LISTED; - } - - if (marketToJoin.accountMembership[borrower]) { - // already joined - return Error.NO_ERROR; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[borrower] = true; - accountAssets[borrower].push(vToken); - - emit MarketEntered(vToken, borrower); - - return Error.NO_ERROR; - } - - /** - * @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 vTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address vTokenAddress) external returns (uint) { - VToken vToken = VToken(vTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the vToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = vToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(vToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set vToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete vToken from the account’s list of assets */ - // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 - VToken[] storage userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint i; - for (; i < len; i++) { - if (userAssetList[i] == vToken) { - userAssetList[i] = userAssetList[len - 1]; - userAssetList.length--; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(i < len); - - emit MarketExited(vToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param vToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address vToken, address minter, uint mintAmount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintGuardianPaused[vToken], "mint is paused"); - - // Shh - currently unused - mintAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, minter); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates mint and reverts on rejection. May emit logs. - * @param vToken Asset being minted - * @param minter The address minting the tokens - * @param actualMintAmount The amount of the underlying asset being minted - * @param mintTokens The number of tokens being minted - */ - function mintVerify(address vToken, address minter, uint actualMintAmount, uint mintTokens) external { - // Shh - currently unused - vToken; - minter; - actualMintAmount; - mintTokens; - } - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param vToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed( - address vToken, - address redeemer, - uint redeemTokens - ) external onlyProtocolAllowed returns (uint) { - uint allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, redeemer); - - return uint(Error.NO_ERROR); - } - - function redeemAllowedInternal(address vToken, address redeemer, uint redeemTokens) internal view returns (uint) { - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[vToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - redeemer, - VToken(vToken), - redeemTokens, - 0 - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates redeem and reverts on rejection. May emit logs. - * @param vToken Asset being redeemed - * @param redeemer The address redeeming the tokens - * @param redeemAmount The amount of the underlying asset being redeemed - * @param redeemTokens The number of tokens being redeemed - */ - function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external { - // Shh - currently unused - vToken; - redeemer; - - // Require tokens is zero or amount is also zero - require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param vToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed( - address vToken, - address borrower, - uint borrowAmount - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!borrowGuardianPaused[vToken], "borrow is paused"); - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (!markets[vToken].accountMembership[borrower]) { - // only vTokens may call borrowAllowed if borrower not in market - require(msg.sender == vToken, "sender must be vToken"); - - // attempt to add borrower to the market - Error err = addToMarketInternal(VToken(vToken), borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - } - - if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - uint borrowCap = borrowCaps[vToken]; - // Borrow cap of 0 corresponds to unlimited borrowing - if (borrowCap != 0) { - uint totalBorrows = VToken(vToken).totalBorrows(); - uint nextTotalBorrows = add_(totalBorrows, borrowAmount); - require(nextTotalBorrows < borrowCap, "market borrow cap reached"); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - borrower, - VToken(vToken), - 0, - borrowAmount - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates borrow and reverts on rejection. May emit logs. - * @param vToken Asset whose underlying is being borrowed - * @param borrower The address borrowing the underlying - * @param borrowAmount The amount of the underlying asset requested to borrow - */ - function borrowVerify(address vToken, address borrower, uint borrowAmount) external { - // Shh - currently unused - vToken; - borrower; - borrowAmount; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param vToken The market to verify the repay against - * @param payer The account which would repay the asset - * @param borrower The account which would repay the asset - * @param repayAmount The amount of the underlying asset the account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address vToken, - address payer, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - payer; - borrower; - repayAmount; - - if (!markets[vToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates repayBorrow and reverts on rejection. May emit logs. - * @param vToken Asset being repaid - * @param payer The address repaying the borrow - * @param borrower The address of the borrower - * @param actualRepayAmount The amount of underlying being repaid - */ - function repayBorrowVerify( - address vToken, - address payer, - address borrower, - uint actualRepayAmount, - uint borrowerIndex - ) external { - // Shh - currently unused - vToken; - payer; - borrower; - actualRepayAmount; - borrowerIndex; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external onlyProtocolAllowed returns (uint) { - // Shh - currently unused - liquidator; - - if ( - !(markets[vTokenBorrowed].isListed || address(vTokenBorrowed) == address(vaiController)) || - !markets[vTokenCollateral].isListed - ) { - return uint(Error.MARKET_NOT_LISTED); - } - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(0), 0, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - /* The liquidator may not repay more than what is allowed by the closeFactor */ - uint borrowBalance; - if (address(vTokenBorrowed) != address(vaiController)) { - borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); - } else { - borrowBalance = mintedVAIs[borrower]; - } - - uint maxClose = mul_ScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance); - if (repayAmount > maxClose) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 actualRepayAmount The amount of underlying being repaid - */ - function liquidateBorrowVerify( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint actualRepayAmount, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenBorrowed; - vTokenCollateral; - liquidator; - borrower; - actualRepayAmount; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!seizeGuardianPaused, "seize is paused"); - - // Shh - currently unused - seizeTokens; - - // We've added VAIController as a borrowed token list check for seize - if ( - !markets[vTokenCollateral].isListed || - !(markets[vTokenBorrowed].isListed || address(vTokenBorrowed) == address(vaiController)) - ) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vTokenCollateral); - distributeSupplierVenus(vTokenCollateral, borrower); - distributeSupplierVenus(vTokenCollateral, liquidator); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates seize and reverts on rejection. May emit logs. - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 seizeVerify( - address vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external { - // Shh - currently unused - vTokenCollateral; - vTokenBorrowed; - liquidator; - borrower; - seizeTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param vToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed( - address vToken, - address src, - address dst, - uint transferTokens - ) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!transferGuardianPaused, "transfer is paused"); - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - uint allowed = redeemAllowedInternal(vToken, src, transferTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, src); - distributeSupplierVenus(vToken, dst); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates transfer and reverts on rejection. May emit logs. - * @param vToken Asset being transferred - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - */ - function transferVerify(address vToken, address src, address dst, uint transferTokens) external { - // Shh - currently unused - vToken; - src; - dst; - transferTokens; - - // Shh - we don't ever want this hook to be marked pure - if (false) { - maxAssets = maxAssets; - } - } - - /*** Liquidity/Liquidation Calculations ***/ - - /** - * @dev Local vars for avoiding stack-depth limits in calculating account liquidity. - * Note that `vTokenBalance` is the number of vTokens the account owns in the market, - * whereas `borrowBalance` is the amount of underlying that the account has borrowed. - */ - struct AccountLiquidityLocalVars { - uint sumCollateral; - uint sumBorrowPlusEffects; - uint vTokenBalance; - uint borrowBalance; - uint exchangeRateMantissa; - uint oraclePriceMantissa; - Exp collateralFactor; - Exp exchangeRate; - Exp oraclePrice; - Exp tokensToDenom; - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, VToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @return (possible error code (semi-opaque), - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidity( - address account, - address vTokenModify, - uint redeemTokens, - uint borrowAmount - ) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal( - account, - VToken(vTokenModify), - redeemTokens, - borrowAmount - ); - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidityInternal( - address account, - VToken vTokenModify, - uint redeemTokens, - uint borrowAmount - ) internal view returns (Error, uint, uint) { - AccountLiquidityLocalVars memory vars; // Holds all our calculation results - uint oErr; - - // For each asset the account is in - VToken[] memory assets = accountAssets[account]; - for (uint i = 0; i < assets.length; i++) { - VToken asset = assets[i]; - - // Read the balances and exchange rate from the vToken - (oErr, vars.vTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot( - account - ); - if (oErr != 0) { - // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0); - } - vars.collateralFactor = Exp({ mantissa: markets[address(asset)].collateralFactorMantissa }); - vars.exchangeRate = Exp({ mantissa: vars.exchangeRateMantissa }); - - // Get the normalized price of the asset - vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset); - if (vars.oraclePriceMantissa == 0) { - return (Error.PRICE_ERROR, 0, 0); - } - vars.oraclePrice = Exp({ mantissa: vars.oraclePriceMantissa }); - - // Pre-compute a conversion factor from tokens -> bnb (normalized price value) - vars.tokensToDenom = mul_(mul_(vars.collateralFactor, vars.exchangeRate), vars.oraclePrice); - - // sumCollateral += tokensToDenom * vTokenBalance - vars.sumCollateral = mul_ScalarTruncateAddUInt(vars.tokensToDenom, vars.vTokenBalance, vars.sumCollateral); - - // sumBorrowPlusEffects += oraclePrice * borrowBalance - vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( - vars.oraclePrice, - vars.borrowBalance, - vars.sumBorrowPlusEffects - ); - - // Calculate effects of interacting with vTokenModify - if (asset == vTokenModify) { - // redeem effect - // sumBorrowPlusEffects += tokensToDenom * redeemTokens - vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( - vars.tokensToDenom, - redeemTokens, - vars.sumBorrowPlusEffects - ); - - // borrow effect - // sumBorrowPlusEffects += oraclePrice * borrowAmount - vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( - vars.oraclePrice, - borrowAmount, - vars.sumBorrowPlusEffects - ); - } - } - - vars.sumBorrowPlusEffects = add_(vars.sumBorrowPlusEffects, mintedVAIs[account]); - - // These are safe, as the underflow condition is checked first - if (vars.sumCollateral > vars.sumBorrowPlusEffects) { - return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0); - } else { - return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral); - } - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenBorrowed The address of the borrowed vToken - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = oracle.getUnderlyingPrice(VToken(vTokenBorrowed)); - uint priceCollateralMantissa = oracle.getUnderlyingPrice(VToken(vTokenCollateral)); - if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - - numerator = mul_(Exp({ mantissa: liquidationIncentiveMantissa }), Exp({ mantissa: priceBorrowedMantissa })); - denominator = mul_(Exp({ mantissa: priceCollateralMantissa }), Exp({ mantissa: exchangeRateMantissa })); - ratio = div_(numerator, denominator); - - seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount); - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateVAICalculateSeizeTokens( - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = 1e18; // Note: this is VAI - uint priceCollateralMantissa = oracle.getUnderlyingPrice(VToken(vTokenCollateral)); - if (priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - - numerator = mul_(Exp({ mantissa: liquidationIncentiveMantissa }), Exp({ mantissa: priceBorrowedMantissa })); - denominator = mul_(Exp({ mantissa: priceCollateralMantissa }), Exp({ mantissa: exchangeRateMantissa })); - ratio = div_(numerator, denominator); - - seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount); - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) public returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK); - } - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) { - // Check caller is admin - require(msg.sender == admin, "only admin can set close factor"); - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Admin function to set per-market collateralFactor - * @param vToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(VToken vToken, uint newCollateralFactorMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK); - } - - // Verify market is listed - Market storage market = markets[address(vToken)]; - if (!market.isListed) { - return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS); - } - - Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { - return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK); - } - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param vToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(VToken vToken) external returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SUPPORT_MARKET_OWNER_CHECK); - } - - if (markets[address(vToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - vToken.isVToken(); // Sanity check to make sure its really a VToken - - // Note that isVenus is not in active use anymore - markets[address(vToken)] = Market({ isListed: true, isVenus: false, collateralFactorMantissa: 0 }); - - _addMarketInternal(vToken); - - emit MarketListed(vToken); - - return uint(Error.NO_ERROR); - } - - function _addMarketInternal(VToken vToken) internal { - for (uint i = 0; i < allMarkets.length; i++) { - require(allMarkets[i] != vToken, "market already added"); - } - allMarkets.push(vToken); - } - - /** - * @notice Admin function to change the Pause Guardian - * @param newPauseGuardian The address of the new Pause Guardian - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _setPauseGuardian(address newPauseGuardian) public returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PAUSE_GUARDIAN_OWNER_CHECK); - } - - // Save current value for inclusion in log - address oldPauseGuardian = pauseGuardian; - - // Store pauseGuardian with value newPauseGuardian - pauseGuardian = newPauseGuardian; - - // Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian) - emit NewPauseGuardian(oldPauseGuardian, newPauseGuardian); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Set the given borrow caps for the given vToken markets. Borrowing that brings total borrows to or above borrow cap will revert. - * @dev Admin or borrowCapGuardian function to set the borrow caps. A borrow cap of 0 corresponds to unlimited borrowing. - * @param vTokens The addresses of the markets (tokens) to change the borrow caps for - * @param newBorrowCaps The new borrow cap values in underlying to be set. A value of 0 corresponds to unlimited borrowing. - */ - function _setMarketBorrowCaps(VToken[] calldata vTokens, uint[] calldata newBorrowCaps) external { - require( - msg.sender == admin || msg.sender == borrowCapGuardian, - "only admin or borrow cap guardian can set borrow caps" - ); - - uint numMarkets = vTokens.length; - uint numBorrowCaps = newBorrowCaps.length; - - require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input"); - - for (uint i = 0; i < numMarkets; i++) { - borrowCaps[address(vTokens[i])] = newBorrowCaps[i]; - emit NewBorrowCap(vTokens[i], newBorrowCaps[i]); - } - } - - /** - * @notice Admin function to change the Borrow Cap Guardian - * @param newBorrowCapGuardian The address of the new Borrow Cap Guardian - */ - function _setBorrowCapGuardian(address newBorrowCapGuardian) external onlyAdmin { - // Save current value for inclusion in log - address oldBorrowCapGuardian = borrowCapGuardian; - - // Store borrowCapGuardian with value newBorrowCapGuardian - borrowCapGuardian = newBorrowCapGuardian; - - // Emit NewBorrowCapGuardian(OldBorrowCapGuardian, NewBorrowCapGuardian) - emit NewBorrowCapGuardian(oldBorrowCapGuardian, newBorrowCapGuardian); - } - - /** - * @notice Set whole protocol pause/unpause state - */ - function _setProtocolPaused(bool state) public validPauseState(state) returns (bool) { - protocolPaused = state; - emit ActionProtocolPaused(state); - return state; - } - - /** - * @notice Sets a new VAI controller - * @dev Admin function to set a new VAI controller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setVAIController(VAIControllerInterface vaiController_) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAICONTROLLER_OWNER_CHECK); - } - - VAIControllerInterface oldRate = vaiController; - vaiController = vaiController_; - emit NewVAIController(oldRate, vaiController_); - } - - function _setVAIMintRate(uint newVAIMintRate) external returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_VAI_MINT_RATE_CHECK); - } - - uint oldVAIMintRate = vaiMintRate; - vaiMintRate = newVAIMintRate; - emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); - - return uint(Error.NO_ERROR); - } - - function _setTreasuryData( - address newTreasuryGuardian, - address newTreasuryAddress, - uint newTreasuryPercent - ) external returns (uint) { - // Check caller is admin - if (!(msg.sender == admin || msg.sender == treasuryGuardian)) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_TREASURY_OWNER_CHECK); - } - - require(newTreasuryPercent < 1e18, "treasury percent cap overflow"); - - address oldTreasuryGuardian = treasuryGuardian; - address oldTreasuryAddress = treasuryAddress; - uint oldTreasuryPercent = treasuryPercent; - - treasuryGuardian = newTreasuryGuardian; - treasuryAddress = newTreasuryAddress; - treasuryPercent = newTreasuryPercent; - - emit NewTreasuryGuardian(oldTreasuryGuardian, newTreasuryGuardian); - emit NewTreasuryAddress(oldTreasuryAddress, newTreasuryAddress); - emit NewTreasuryPercent(oldTreasuryPercent, newTreasuryPercent); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller) public { - require(msg.sender == unitroller.admin(), "only unitroller admin can"); - require(unitroller._acceptImplementation() == 0, "not authorized"); - } - - /** - * @notice Checks caller is admin, or this contract is becoming the new implementation - */ - function adminOrInitializing() internal view returns (bool) { - return msg.sender == admin || msg.sender == comptrollerImplementation; - } - - /*** Venus Distribution ***/ - - function setVenusSpeedInternal(VToken vToken, uint venusSpeed) internal { - uint currentVenusSpeed = venusSpeeds[address(vToken)]; - if (currentVenusSpeed != 0) { - // note that XVS speed could be set to 0 to halt liquidity rewards for a market - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusSupplyIndex(address(vToken)); - updateVenusBorrowIndex(address(vToken), borrowIndex); - } else if (venusSpeed != 0) { - // Add the XVS market - Market storage market = markets[address(vToken)]; - require(market.isListed == true, "venus market is not listed"); - - if (venusSupplyState[address(vToken)].index == 0 && venusSupplyState[address(vToken)].block == 0) { - venusSupplyState[address(vToken)] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number exceeds 32 bits") - }); - } - - if (venusBorrowState[address(vToken)].index == 0 && venusBorrowState[address(vToken)].block == 0) { - venusBorrowState[address(vToken)] = VenusMarketState({ - index: venusInitialIndex, - block: safe32(getBlockNumber(), "block number exceeds 32 bits") - }); - } - } - - if (currentVenusSpeed != venusSpeed) { - venusSpeeds[address(vToken)] = venusSpeed; - emit VenusSpeedUpdated(vToken, venusSpeed); - } - } - - /** - * @notice Accrue XVS to the market by updating the supply index - * @param vToken The market whose supply index to update - */ - function updateVenusSupplyIndex(address vToken) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - uint supplySpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(supplyState.block)); - if (deltaBlocks > 0 && supplySpeed > 0) { - uint supplyTokens = VToken(vToken).totalSupply(); - uint venusAccrued = mul_(deltaBlocks, supplySpeed); - Double memory ratio = supplyTokens > 0 ? fraction(venusAccrued, supplyTokens) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: supplyState.index }), ratio); - venusSupplyState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - supplyState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Accrue XVS to the market by updating the borrow index - * @param vToken The market whose borrow index to update - */ - function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - uint borrowSpeed = venusSpeeds[vToken]; - uint blockNumber = getBlockNumber(); - uint deltaBlocks = sub_(blockNumber, uint(borrowState.block)); - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); - uint venusAccrued = mul_(deltaBlocks, borrowSpeed); - Double memory ratio = borrowAmount > 0 ? fraction(venusAccrued, borrowAmount) : Double({ mantissa: 0 }); - Double memory index = add_(Double({ mantissa: borrowState.index }), ratio); - venusBorrowState[vToken] = VenusMarketState({ - index: safe224(index.mantissa, "new index overflows"), - block: safe32(blockNumber, "block number overflows") - }); - } else if (deltaBlocks > 0) { - borrowState.block = safe32(blockNumber, "block number overflows"); - } - } - - /** - * @notice Calculate XVS accrued by a supplier and possibly transfer it to them - * @param vToken The market in which the supplier is interacting - * @param supplier The address of the supplier to distribute XVS to - */ - function distributeSupplierVenus(address vToken, address supplier) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - VenusMarketState storage supplyState = venusSupplyState[vToken]; - Double memory supplyIndex = Double({ mantissa: supplyState.index }); - Double memory supplierIndex = Double({ mantissa: venusSupplierIndex[vToken][supplier] }); - venusSupplierIndex[vToken][supplier] = supplyIndex.mantissa; - - if (supplierIndex.mantissa == 0 && supplyIndex.mantissa > 0) { - supplierIndex.mantissa = venusInitialIndex; - } - - Double memory deltaIndex = sub_(supplyIndex, supplierIndex); - uint supplierTokens = VToken(vToken).balanceOf(supplier); - uint supplierDelta = mul_(supplierTokens, deltaIndex); - uint supplierAccrued = add_(venusAccrued[supplier], supplierDelta); - venusAccrued[supplier] = supplierAccrued; - emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex.mantissa); - } - - /** - * @notice Calculate XVS accrued by a borrower and possibly transfer it to them - * @dev Borrowers will not begin to accrue until after the first interaction with the protocol. - * @param vToken The market in which the borrower is interacting - * @param borrower The address of the borrower to distribute XVS to - */ - function distributeBorrowerVenus(address vToken, address borrower, Exp memory marketBorrowIndex) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - VenusMarketState storage borrowState = venusBorrowState[vToken]; - Double memory borrowIndex = Double({ mantissa: borrowState.index }); - Double memory borrowerIndex = Double({ mantissa: venusBorrowerIndex[vToken][borrower] }); - venusBorrowerIndex[vToken][borrower] = borrowIndex.mantissa; - - if (borrowerIndex.mantissa > 0) { - Double memory deltaIndex = sub_(borrowIndex, borrowerIndex); - uint borrowerAmount = div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex); - uint borrowerDelta = mul_(borrowerAmount, deltaIndex); - uint borrowerAccrued = add_(venusAccrued[borrower], borrowerDelta); - venusAccrued[borrower] = borrowerAccrued; - emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex.mantissa); - } - } - - /** - * @notice Calculate XVS accrued by a VAI minter and possibly transfer it to them - * @dev VAI minters will not begin to accrue until after the first interaction with the protocol. - * @param vaiMinter The address of the VAI minter to distribute XVS to - */ - function distributeVAIMinterVenus(address vaiMinter) public { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - if (address(vaiController) != address(0)) { - uint vaiMinterAccrued; - uint vaiMinterDelta; - uint vaiMintIndexMantissa; - uint err; - (err, vaiMinterAccrued, vaiMinterDelta, vaiMintIndexMantissa) = vaiController.calcDistributeVAIMinterVenus( - vaiMinter - ); - if (err == uint(Error.NO_ERROR)) { - venusAccrued[vaiMinter] = vaiMinterAccrued; - emit DistributedVAIMinterVenus(vaiMinter, vaiMinterDelta, vaiMintIndexMantissa); - } - } - } - - /** - * @notice Claim all the xvs accrued by holder in all markets and VAI - * @param holder The address to claim XVS for - */ - function claimVenus(address holder) public { - return claimVenus(holder, allMarkets); - } - - /** - * @notice Claim all the xvs accrued by holder in the specified markets - * @param holder The address to claim XVS for - * @param vTokens The list of markets to claim XVS in - */ - function claimVenus(address holder, VToken[] memory vTokens) public { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, vTokens, true, true); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - */ - function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { - uint j; - if (address(vaiController) != address(0)) { - vaiController.updateVenusVAIMintIndex(); - } - for (j = 0; j < holders.length; j++) { - distributeVAIMinterVenus(holders[j]); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - for (uint i = 0; i < vTokens.length; i++) { - VToken vToken = vTokens[i]; - require(markets[address(vToken)].isListed, "not listed market"); - if (borrowers) { - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - for (j = 0; j < holders.length; j++) { - distributeBorrowerVenus(address(vToken), holders[j], borrowIndex); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - } - if (suppliers) { - updateVenusSupplyIndex(address(vToken)); - for (j = 0; j < holders.length; j++) { - distributeSupplierVenus(address(vToken), holders[j]); - venusAccrued[holders[j]] = grantXVSInternal(holders[j], venusAccrued[holders[j]]); - } - } - } - } - - /** - * @notice Transfer XVS to the user - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param user The address of the user to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - * @return The amount of XVS which was NOT transferred to the user - */ - function grantXVSInternal(address user, uint amount) internal returns (uint) { - XVS xvs = XVS(getXVSAddress()); - uint venusRemaining = xvs.balanceOf(address(this)); - if (amount > 0 && amount <= venusRemaining) { - xvs.transfer(user, amount); - return 0; - } - return amount; - } - - /*** Venus Distribution Admin ***/ - - /** - * @notice Transfer XVS to the recipient - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param recipient The address of the recipient to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - */ - function _grantXVS(address recipient, uint amount) public { - require(adminOrInitializing(), "only admin can grant xvs"); - uint amountLeft = grantXVSInternal(recipient, amount); - require(amountLeft == 0, "insufficient xvs for grant"); - emit VenusGranted(recipient, amount); - } - - /** - * @notice Set the amount of XVS distributed per block to VAI Vault - * @param venusVAIVaultRate_ The amount of XVS wei per block to distribute to VAI Vault - */ - function _setVenusVAIVaultRate(uint venusVAIVaultRate_) public onlyAdmin { - uint oldVenusVAIVaultRate = venusVAIVaultRate; - venusVAIVaultRate = venusVAIVaultRate_; - emit NewVenusVAIVaultRate(oldVenusVAIVaultRate, venusVAIVaultRate_); - } - - /** - * @notice Set the VAI Vault infos - * @param vault_ The address of the VAI Vault - * @param releaseStartBlock_ The start block of release to VAI Vault - * @param minReleaseAmount_ The minimum release amount to VAI Vault - */ - function _setVAIVaultInfo(address vault_, uint256 releaseStartBlock_, uint256 minReleaseAmount_) public onlyAdmin { - vaiVaultAddress = vault_; - releaseStartBlock = releaseStartBlock_; - minReleaseAmount = minReleaseAmount_; - emit NewVAIVaultInfo(vault_, releaseStartBlock_, minReleaseAmount_); - } - - /** - * @notice Set XVS speed for a single market - * @param vToken The market whose XVS speed to update - * @param venusSpeed New XVS speed for market - */ - function _setVenusSpeed(VToken vToken, uint venusSpeed) public { - require(adminOrInitializing(), "only admin can set venus speed"); - setVenusSpeedInternal(vToken, venusSpeed); - } - - /** - * @notice Return all of the markets - * @dev The automatic getter may be used to access an individual market. - * @return The list of market addresses - */ - function getAllMarkets() public view returns (VToken[] memory) { - return allMarkets; - } - - function getBlockNumber() public view returns (uint) { - return block.number; - } - - /** - * @notice Return the address of the XVS token - * @return The address of XVS - */ - function getXVSAddress() public view returns (address) { - return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; - } - - /*** VAI functions ***/ - - /** - * @notice Set the minted VAI amount of the `owner` - * @param owner The address of the account to set - * @param amount The amount of VAI to set to the account - * @return The number of minted VAI by `owner` - */ - function setMintedVAIOf(address owner, uint amount) external onlyProtocolAllowed returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); - // Check caller is vaiController - if (msg.sender != address(vaiController)) { - return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); - } - mintedVAIs[owner] = amount; - - return uint(Error.NO_ERROR); - } - - /** - * @notice Transfer XVS to VAI Vault - */ - function releaseToVault() public { - if (releaseStartBlock == 0 || getBlockNumber() < releaseStartBlock) { - return; - } - - XVS xvs = XVS(getXVSAddress()); - - uint256 xvsBalance = xvs.balanceOf(address(this)); - if (xvsBalance == 0) { - return; - } - - uint256 actualAmount; - uint256 deltaBlocks = sub_(getBlockNumber(), releaseStartBlock); - // releaseAmount = venusVAIVaultRate * deltaBlocks - uint256 _releaseAmount = mul_(venusVAIVaultRate, deltaBlocks); - - if (_releaseAmount < minReleaseAmount) { - return; - } - - if (xvsBalance >= _releaseAmount) { - actualAmount = _releaseAmount; - } else { - actualAmount = xvsBalance; - } - - releaseStartBlock = getBlockNumber(); - - xvs.transfer(vaiVaultAddress, actualAmount); - emit DistributedVAIVaultVenus(actualAmount); - - IVAIVault(vaiVaultAddress).updatePendingRewards(); - } -} diff --git a/contracts/Comptroller/ComptrollerInterface.sol b/contracts/Comptroller/ComptrollerInterface.sol index c8695bc86..a6c8838b9 100644 --- a/contracts/Comptroller/ComptrollerInterface.sol +++ b/contracts/Comptroller/ComptrollerInterface.sol @@ -2,8 +2,9 @@ pragma solidity ^0.5.16; import "../Tokens/VTokens/VToken.sol"; import "../Oracle/PriceOracle.sol"; +import "../Tokens/VAI/VAIControllerInterface.sol"; -contract ComptrollerInterfaceG1 { +contract ComptrollerInterface { /// @notice Indicator that this is a Comptroller contract (for inspection) bool public constant isComptroller = true; @@ -88,27 +89,14 @@ contract ComptrollerInterfaceG1 { ) external view returns (uint, uint); function setMintedVAIOf(address owner, uint amount) external returns (uint); -} - -contract ComptrollerInterfaceG2 is ComptrollerInterfaceG1 { - function liquidateVAICalculateSeizeTokens( - address vTokenCollateral, - uint repayAmount - ) external view returns (uint, uint); -} -contract ComptrollerInterfaceG3 is ComptrollerInterfaceG2 { function liquidateVAICalculateSeizeTokens( address vTokenCollateral, uint repayAmount ) external view returns (uint, uint); -} -contract ComptrollerInterfaceG4 is ComptrollerInterfaceG3 { function getXVSAddress() public view returns (address); -} -contract ComptrollerInterface is ComptrollerInterfaceG4 { function markets(address) external view returns (bool, uint); function oracle() external view returns (PriceOracle); @@ -138,6 +126,16 @@ contract ComptrollerInterface is ComptrollerInterfaceG4 { function venusSupplyState(address) external view returns (uint224, uint32); function approvedDelegates(address borrower, address delegate) external view returns (bool); + + function vaiController() external view returns (VAIControllerInterface); + + function liquidationIncentiveMantissa() external view returns (uint); + + function protocolPaused() external view returns (bool); + + function mintedVAIs(address user) external view returns (uint); + + function vaiMintRate() external view returns (uint); } interface IVAIVault { diff --git a/contracts/Comptroller/ComptrollerStorage.sol b/contracts/Comptroller/ComptrollerStorage.sol index d651f6634..048b390e0 100644 --- a/contracts/Comptroller/ComptrollerStorage.sol +++ b/contracts/Comptroller/ComptrollerStorage.sol @@ -1,9 +1,11 @@ +// SPDX-License-Identifier: BSD-3-Clause + pragma solidity ^0.5.16; -import "../Tokens/VTokens/VToken.sol"; -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VAI/VAIControllerInterface.sol"; -import "./ComptrollerLensInterface.sol"; +import { VToken } from "../Tokens/VTokens/VToken.sol"; +import { PriceOracle } from "../Oracle/PriceOracle.sol"; +import { VAIControllerInterface } from "../Tokens/VAI/VAIControllerInterface.sol"; +import { ComptrollerLensInterface } from "./ComptrollerLensInterface.sol"; contract UnitrollerAdminStorage { /** @@ -36,17 +38,17 @@ contract ComptrollerV1Storage is UnitrollerAdminStorage { /** * @notice Multiplier used to calculate the maximum repayAmount when liquidating a borrow */ - uint public closeFactorMantissa; + uint256 public closeFactorMantissa; /** * @notice Multiplier representing the discount on collateral that a liquidator receives */ - uint public liquidationIncentiveMantissa; + uint256 public liquidationIncentiveMantissa; /** * @notice Max number of assets a single account can participate in (borrow or use as collateral) */ - uint public maxAssets; + uint256 public maxAssets; /** * @notice Per-account mapping of "assets you are in", capped by maxAssets @@ -61,7 +63,7 @@ contract ComptrollerV1Storage is UnitrollerAdminStorage { * For instance, 0.9 to allow borrowing 90% of collateral value. * Must be between 0 and 1, and stored as a mantissa. */ - uint collateralFactorMantissa; + uint256 collateralFactorMantissa; /// @notice Per-market mapping of "accounts in this asset" mapping(address => bool) accountMembership; /// @notice Whether or not this market receives XVS @@ -103,10 +105,10 @@ contract ComptrollerV1Storage is UnitrollerAdminStorage { VToken[] public allMarkets; /// @notice The rate at which the flywheel distributes XVS, per block - uint internal venusRate; + uint256 internal venusRate; /// @notice The portion of venusRate that each market currently receives - mapping(address => uint) internal venusSpeeds; + mapping(address => uint256) internal venusSpeeds; /// @notice The Venus market supply state for each market mapping(address => VenusMarketState) public venusSupplyState; @@ -115,22 +117,22 @@ contract ComptrollerV1Storage is UnitrollerAdminStorage { mapping(address => VenusMarketState) public venusBorrowState; /// @notice The Venus supply index for each market for each supplier as of the last time they accrued XVS - mapping(address => mapping(address => uint)) public venusSupplierIndex; + mapping(address => mapping(address => uint256)) public venusSupplierIndex; /// @notice The Venus borrow index for each market for each borrower as of the last time they accrued XVS - mapping(address => mapping(address => uint)) public venusBorrowerIndex; + mapping(address => mapping(address => uint256)) public venusBorrowerIndex; /// @notice The XVS accrued but not yet transferred to each user - mapping(address => uint) public venusAccrued; + mapping(address => uint256) public venusAccrued; /// @notice The Address of VAIController VAIControllerInterface public vaiController; /// @notice The minted VAI amount to each user - mapping(address => uint) public mintedVAIs; + mapping(address => uint256) public mintedVAIs; /// @notice VAI Mint Rate as a percentage - uint public vaiMintRate; + uint256 public vaiMintRate; /** * @notice The Pause Guardian can pause certain actions as a safety mechanism. @@ -144,12 +146,12 @@ contract ComptrollerV1Storage is UnitrollerAdminStorage { bool public protocolPaused; /// @notice The rate at which the flywheel distributes XVS to VAI Minters, per block (deprecated) - uint private venusVAIRate; + uint256 private venusVAIRate; } contract ComptrollerV2Storage is ComptrollerV1Storage { /// @notice The rate at which the flywheel distributes XVS to VAI Vault, per block - uint public venusVAIVaultRate; + uint256 public venusVAIVaultRate; // address of VAI Vault address public vaiVaultAddress; @@ -166,7 +168,7 @@ contract ComptrollerV3Storage is ComptrollerV2Storage { address public borrowCapGuardian; /// @notice Borrow caps enforced by borrowAllowed for each vToken address. Defaults to zero which corresponds to unlimited borrowing. - mapping(address => uint) public borrowCaps; + mapping(address => uint256) public borrowCaps; } contract ComptrollerV4Storage is ComptrollerV3Storage { @@ -182,10 +184,10 @@ contract ComptrollerV4Storage is ComptrollerV3Storage { contract ComptrollerV5Storage is ComptrollerV4Storage { /// @notice The portion of XVS that each contributor receives per block (deprecated) - mapping(address => uint) private venusContributorSpeeds; + mapping(address => uint256) private venusContributorSpeeds; /// @notice Last block at which a contributor's XVS rewards have been allocated (deprecated) - mapping(address => uint) private lastContributorBlock; + mapping(address => uint256) private lastContributorBlock; } contract ComptrollerV6Storage is ComptrollerV5Storage { @@ -218,15 +220,15 @@ contract ComptrollerV9Storage is ComptrollerV8Storage { } /// @notice True if a certain action is paused on a certain market - mapping(address => mapping(uint => bool)) internal _actionPaused; + mapping(address => mapping(uint256 => bool)) internal _actionPaused; } contract ComptrollerV10Storage is ComptrollerV9Storage { /// @notice The rate at which venus is distributed to the corresponding borrow market (per block) - mapping(address => uint) public venusBorrowSpeeds; + mapping(address => uint256) public venusBorrowSpeeds; /// @notice The rate at which venus is distributed to the corresponding supply market (per block) - mapping(address => uint) public venusSupplySpeeds; + mapping(address => uint256) public venusSupplySpeeds; } contract ComptrollerV11Storage is ComptrollerV10Storage { @@ -236,6 +238,23 @@ contract ComptrollerV11Storage is ComptrollerV10Storage { } contract ComptrollerV12Storage is ComptrollerV11Storage { - /// @notice Flag indicating whether forced liquidation enabled for a market mapping(address => bool) public isForcedLiquidationEnabled; } + +contract ComptrollerV13Storage is ComptrollerV12Storage { + struct FacetAddressAndPosition { + address facetAddress; + uint96 functionSelectorPosition; // position in _facetFunctionSelectors.functionSelectors array + } + + struct FacetFunctionSelectors { + bytes4[] functionSelectors; + uint256 facetAddressPosition; // position of facetAddress in _facetAddresses array + } + + mapping(bytes4 => FacetAddressAndPosition) internal _selectorToFacetAndPosition; + // maps facet addresses to function selectors + mapping(address => FacetFunctionSelectors) internal _facetFunctionSelectors; + // facet addresses + address[] internal _facetAddresses; +} diff --git a/contracts/Comptroller/Diamond/Diamond.sol b/contracts/Comptroller/Diamond/Diamond.sol new file mode 100644 index 000000000..fba759594 --- /dev/null +++ b/contracts/Comptroller/Diamond/Diamond.sol @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; +pragma experimental ABIEncoderV2; + +import { IDiamondCut } from "./interfaces/IDiamondCut.sol"; +import { Unitroller, ComptrollerV13Storage } from "../Unitroller.sol"; + +/** + * @title Diamond + * @author Venus + * @notice This contract contains functions related to facets + */ +contract Diamond is IDiamondCut, ComptrollerV13Storage { + /// @notice Emitted when functions are added, replaced or removed to facets + event DiamondCut(IDiamondCut.FacetCut[] _diamondCut); + + struct Facet { + address facetAddress; + bytes4[] functionSelectors; + } + + /** + * @notice Call _acceptImplementation to accept the diamond proxy as new implementaion + * @param unitroller Address of the unitroller + */ + function _become(Unitroller unitroller) public { + require(msg.sender == unitroller.admin(), "only unitroller admin can"); + require(unitroller._acceptImplementation() == 0, "not authorized"); + } + + /** + * @notice To add function selectors to the facet's mapping + * @dev Allows the contract admin to add function selectors + * @param diamondCut_ IDiamondCut contains facets address, action and function selectors + */ + function diamondCut(IDiamondCut.FacetCut[] memory diamondCut_) public { + require(msg.sender == admin, "only unitroller admin can"); + libDiamondCut(diamondCut_); + } + + /** + * @notice Get all function selectors mapped to the facet address + * @param facet Address of the facet + * @return selectors Array of function selectors + */ + function facetFunctionSelectors(address facet) external view returns (bytes4[] memory) { + return _facetFunctionSelectors[facet].functionSelectors; + } + + /** + * @notice Get facet position in the _facetFunctionSelectors through facet address + * @param facet Address of the facet + * @return Position of the facet + */ + function facetPosition(address facet) external view returns (uint256) { + return _facetFunctionSelectors[facet].facetAddressPosition; + } + + /** + * @notice Get all facet addresses + * @return facetAddresses Array of facet addresses + */ + function facetAddresses() external view returns (address[] memory) { + return _facetAddresses; + } + + /** + * @notice Get facet address and position through function selector + * @param functionSelector function selector + * @return FacetAddressAndPosition facet address and position + */ + function facetAddress( + bytes4 functionSelector + ) external view returns (ComptrollerV13Storage.FacetAddressAndPosition memory) { + return _selectorToFacetAndPosition[functionSelector]; + } + + /** + * @notice Get all facets address and their function selector + * @return facets_ Array of Facet + */ + function facets() external view returns (Facet[] memory) { + uint256 facetsLength = _facetAddresses.length; + Facet[] memory facets_ = new Facet[](facetsLength); + for (uint256 i; i < facetsLength; ++i) { + address facet = _facetAddresses[i]; + facets_[i].facetAddress = facet; + facets_[i].functionSelectors = _facetFunctionSelectors[facet].functionSelectors; + } + return facets_; + } + + /** + * @notice To add function selectors to the facets' mapping + * @param diamondCut_ IDiamondCut contains facets address, action and function selectors + */ + function libDiamondCut(IDiamondCut.FacetCut[] memory diamondCut_) internal { + uint256 diamondCutLength = diamondCut_.length; + for (uint256 facetIndex; facetIndex < diamondCutLength; ++facetIndex) { + IDiamondCut.FacetCutAction action = diamondCut_[facetIndex].action; + if (action == IDiamondCut.FacetCutAction.Add) { + addFunctions(diamondCut_[facetIndex].facetAddress, diamondCut_[facetIndex].functionSelectors); + } else if (action == IDiamondCut.FacetCutAction.Replace) { + replaceFunctions(diamondCut_[facetIndex].facetAddress, diamondCut_[facetIndex].functionSelectors); + } else if (action == IDiamondCut.FacetCutAction.Remove) { + removeFunctions(diamondCut_[facetIndex].facetAddress, diamondCut_[facetIndex].functionSelectors); + } else { + revert("LibDiamondCut: Incorrect FacetCutAction"); + } + } + emit DiamondCut(diamondCut_); + } + + /** + * @notice Add function selectors to the facet's address mapping + * @param facetAddress Address of the facet + * @param functionSelectors Array of function selectors need to add in the mapping + */ + function addFunctions(address facetAddress, bytes4[] memory functionSelectors) internal { + require(functionSelectors.length != 0, "LibDiamondCut: No selectors in facet to cut"); + require(facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); + uint96 selectorPosition = uint96(_facetFunctionSelectors[facetAddress].functionSelectors.length); + // add new facet address if it does not exist + if (selectorPosition == 0) { + addFacet(facetAddress); + } + uint256 functionSelectorsLength = functionSelectors.length; + for (uint256 selectorIndex; selectorIndex < functionSelectorsLength; ++selectorIndex) { + bytes4 selector = functionSelectors[selectorIndex]; + address oldFacetAddress = _selectorToFacetAndPosition[selector].facetAddress; + require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists"); + addFunction(selector, selectorPosition, facetAddress); + ++selectorPosition; + } + } + + /** + * @notice Replace facet's address mapping for function selectors i.e selectors already associate to any other existing facet + * @param facetAddress Address of the facet + * @param functionSelectors Array of function selectors need to replace in the mapping + */ + function replaceFunctions(address facetAddress, bytes4[] memory functionSelectors) internal { + require(functionSelectors.length != 0, "LibDiamondCut: No selectors in facet to cut"); + require(facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); + uint96 selectorPosition = uint96(_facetFunctionSelectors[facetAddress].functionSelectors.length); + // add new facet address if it does not exist + if (selectorPosition == 0) { + addFacet(facetAddress); + } + uint256 functionSelectorsLength = functionSelectors.length; + for (uint256 selectorIndex; selectorIndex < functionSelectorsLength; ++selectorIndex) { + bytes4 selector = functionSelectors[selectorIndex]; + address oldFacetAddress = _selectorToFacetAndPosition[selector].facetAddress; + require(oldFacetAddress != facetAddress, "LibDiamondCut: Can't replace function with same function"); + removeFunction(oldFacetAddress, selector); + addFunction(selector, selectorPosition, facetAddress); + ++selectorPosition; + } + } + + /** + * @notice Remove function selectors to the facet's address mapping + * @param facetAddress Address of the facet + * @param functionSelectors Array of function selectors need to remove in the mapping + */ + function removeFunctions(address facetAddress, bytes4[] memory functionSelectors) internal { + uint256 functionSelectorsLength = functionSelectors.length; + require(functionSelectorsLength != 0, "LibDiamondCut: No selectors in facet to cut"); + // if function does not exist then do nothing and revert + require(facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)"); + for (uint256 selectorIndex; selectorIndex < functionSelectorsLength; ++selectorIndex) { + bytes4 selector = functionSelectors[selectorIndex]; + address oldFacetAddress = _selectorToFacetAndPosition[selector].facetAddress; + removeFunction(oldFacetAddress, selector); + } + } + + /** + * @notice Add new facet to the proxy + * @param facetAddress Address of the facet + */ + function addFacet(address facetAddress) internal { + enforceHasContractCode(facetAddress, "Diamond: New facet has no code"); + _facetFunctionSelectors[facetAddress].facetAddressPosition = _facetAddresses.length; + _facetAddresses.push(facetAddress); + } + + /** + * @notice Add function selector to the facet's address mapping + * @param selector funciton selector need to be added + * @param selectorPosition funciton selector position + * @param facetAddress Address of the facet + */ + function addFunction(bytes4 selector, uint96 selectorPosition, address facetAddress) internal { + _selectorToFacetAndPosition[selector].functionSelectorPosition = selectorPosition; + _facetFunctionSelectors[facetAddress].functionSelectors.push(selector); + _selectorToFacetAndPosition[selector].facetAddress = facetAddress; + } + + /** + * @notice Remove function selector to the facet's address mapping + * @param facetAddress Address of the facet + * @param selector function selectors need to remove in the mapping + */ + function removeFunction(address facetAddress, bytes4 selector) internal { + require(facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist"); + + // replace selector with last selector, then delete last selector + uint256 selectorPosition = _selectorToFacetAndPosition[selector].functionSelectorPosition; + uint256 lastSelectorPosition = _facetFunctionSelectors[facetAddress].functionSelectors.length - 1; + // if not the same then replace selector with lastSelector + if (selectorPosition != lastSelectorPosition) { + bytes4 lastSelector = _facetFunctionSelectors[facetAddress].functionSelectors[lastSelectorPosition]; + _facetFunctionSelectors[facetAddress].functionSelectors[selectorPosition] = lastSelector; + _selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition); + } + // delete the last selector + _facetFunctionSelectors[facetAddress].functionSelectors.pop(); + delete _selectorToFacetAndPosition[selector]; + + // if no more selectors for facet address then delete the facet address + if (lastSelectorPosition == 0) { + // replace facet address with last facet address and delete last facet address + uint256 lastFacetAddressPosition = _facetAddresses.length - 1; + uint256 facetAddressPosition = _facetFunctionSelectors[facetAddress].facetAddressPosition; + if (facetAddressPosition != lastFacetAddressPosition) { + address lastFacetAddress = _facetAddresses[lastFacetAddressPosition]; + _facetAddresses[facetAddressPosition] = lastFacetAddress; + _facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition; + } + _facetAddresses.pop(); + delete _facetFunctionSelectors[facetAddress]; + } + } + + /** + * @dev Ensure that the given address has contract code deployed + * @param _contract The address to check for contract code + * @param _errorMessage The error message to display if the contract code is not deployed + */ + function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { + uint256 contractSize; + assembly { + contractSize := extcodesize(_contract) + } + require(contractSize != 0, _errorMessage); + } + + // Find facet for function that is called and execute the + // function if a facet is found and return any value. + function() external payable { + address facet = _selectorToFacetAndPosition[msg.sig].facetAddress; + require(facet != address(0), "Diamond: Function does not exist"); + // Execute public function from facet using delegatecall and return any value. + assembly { + // copy function selector and any arguments + calldatacopy(0, 0, calldatasize()) + // execute function call using the facet + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + // get any return value + returndatacopy(0, 0, returndatasize()) + // return any return value or error back to the caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/contracts/Comptroller/Diamond/facets/FacetBase.sol b/contracts/Comptroller/Diamond/facets/FacetBase.sol new file mode 100644 index 000000000..6ab134440 --- /dev/null +++ b/contracts/Comptroller/Diamond/facets/FacetBase.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { VToken, ComptrollerErrorReporter, ExponentialNoError } from "../../../Tokens/VTokens/VToken.sol"; +import { IVAIVault } from "../../../Comptroller/ComptrollerInterface.sol"; +import { ComptrollerV13Storage } from "../../../Comptroller/ComptrollerStorage.sol"; +import { IAccessControlManager } from "../../../Governance/IAccessControlManager.sol"; +import { SafeBEP20, IBEP20 } from "../../../Utils/SafeBEP20.sol"; + +/** + * @title FacetBase + * @author Venus + * @notice This facet contract contains functions related to access and checks + */ +contract FacetBase is ComptrollerV13Storage, ExponentialNoError, ComptrollerErrorReporter { + /// @notice Emitted when an account enters a market + event MarketEntered(VToken indexed vToken, address indexed account); + + /// @notice Emitted when XVS is distributed to VAI Vault + event DistributedVAIVaultVenus(uint256 amount); + + using SafeBEP20 for IBEP20; + + /// @notice The initial Venus index for a market + uint224 public constant venusInitialIndex = 1e36; + // closeFactorMantissa must be strictly greater than this value + uint256 internal constant closeFactorMinMantissa = 0.05e18; // 0.05 + // closeFactorMantissa must not exceed this value + uint256 internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 + // No collateralFactorMantissa may exceed this value + uint256 internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 + + /// @notice Reverts if the protocol is paused + function checkProtocolPauseState() internal view { + require(!protocolPaused, "protocol is paused"); + } + + /// @notice Reverts if a certain action is paused on a market + function checkActionPauseState(address market, Action action) internal view { + require(!actionPaused(market, action), "action is paused"); + } + + /// @notice Reverts if the caller is not admin + function ensureAdmin() internal view { + require(msg.sender == admin, "only admin can"); + } + + /// @notice Checks the passed address is nonzero + function ensureNonzeroAddress(address someone) internal pure { + require(someone != address(0), "can't be zero address"); + } + + /// @notice Reverts if the market is not listed + function ensureListed(Market storage market) internal view { + require(market.isListed, "market not listed"); + } + + /// @notice Reverts if the caller is neither admin nor the passed address + function ensureAdminOr(address privilegedAddress) internal view { + require(msg.sender == admin || msg.sender == privilegedAddress, "access denied"); + } + + /// @notice Checks the caller is allowed to call the specified fuction + function ensureAllowed(string memory functionSig) internal view { + require(IAccessControlManager(accessControl).isAllowedToCall(msg.sender, functionSig), "access denied"); + } + + /** + * @notice Checks if a certain action is paused on a market + * @param action Action id + * @param market vToken address + */ + function actionPaused(address market, Action action) public view returns (bool) { + return _actionPaused[market][uint256(action)]; + } + + /** + * @notice Get the latest block number + */ + function getBlockNumber() internal view returns (uint256) { + return block.number; + } + + /** + * @notice Get the latest block number with the safe32 check + */ + function getBlockNumberAsUint32() internal view returns (uint32) { + return safe32(getBlockNumber(), "block # > 32 bits"); + } + + /** + * @notice Transfer XVS to VAI Vault + */ + function releaseToVault() internal { + if (releaseStartBlock == 0 || getBlockNumber() < releaseStartBlock) { + return; + } + + uint256 xvsBalance = IBEP20(getXVSAddress()).balanceOf(address(this)); + if (xvsBalance == 0) { + return; + } + + uint256 actualAmount; + uint256 deltaBlocks = sub_(getBlockNumber(), releaseStartBlock); + // releaseAmount = venusVAIVaultRate * deltaBlocks + uint256 _releaseAmount = mul_(venusVAIVaultRate, deltaBlocks); + + if (xvsBalance >= _releaseAmount) { + actualAmount = _releaseAmount; + } else { + actualAmount = xvsBalance; + } + + if (actualAmount < minReleaseAmount) { + return; + } + + releaseStartBlock = getBlockNumber(); + + IBEP20(getXVSAddress()).safeTransfer(vaiVaultAddress, actualAmount); + emit DistributedVAIVaultVenus(actualAmount); + + IVAIVault(vaiVaultAddress).updatePendingRewards(); + } + + /** + * @notice Return the address of the XVS token + * @return The address of XVS + */ + function getXVSAddress() public pure returns (address) { + return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; + } + + /** + * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed + * @param vTokenModify The market to hypothetically redeem/borrow in + * @param account The account to determine liquidity for + * @param redeemTokens The number of tokens to hypothetically redeem + * @param borrowAmount The amount of underlying to hypothetically borrow + * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, + * without calculating accumulated interest. + * @return (possible error code, + hypothetical account liquidity in excess of collateral requirements, + * hypothetical account shortfall below collateral requirements) + */ + function getHypotheticalAccountLiquidityInternal( + address account, + VToken vTokenModify, + uint256 redeemTokens, + uint256 borrowAmount + ) internal view returns (Error, uint256, uint256) { + (uint256 err, uint256 liquidity, uint256 shortfall) = comptrollerLens.getHypotheticalAccountLiquidity( + address(this), + account, + vTokenModify, + redeemTokens, + borrowAmount + ); + return (Error(err), liquidity, shortfall); + } + + /** + * @notice Add the market to the borrower's "assets in" for liquidity calculations + * @param vToken The market to enter + * @param borrower The address of the account to modify + * @return Success indicator for whether the market was entered + */ + function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { + checkActionPauseState(address(vToken), Action.ENTER_MARKET); + Market storage marketToJoin = markets[address(vToken)]; + ensureListed(marketToJoin); + if (marketToJoin.accountMembership[borrower]) { + // already joined + return Error.NO_ERROR; + } + // survived the gauntlet, add to list + // NOTE: we store these somewhat redundantly as a significant optimization + // this avoids having to iterate through the list for the most common use cases + // that is, only when we need to perform liquidity checks + // and not whenever we want to check if an account is in a particular market + marketToJoin.accountMembership[borrower] = true; + accountAssets[borrower].push(vToken); + + emit MarketEntered(vToken, borrower); + + return Error.NO_ERROR; + } + + /** + * @notice Checks for the user is allowed to redeem tokens + * @param vToken Address of the market + * @param redeemer Address of the user + * @param redeemTokens Amount of tokens to redeem + * @return Success indicator for redeem is allowed or not + */ + function redeemAllowedInternal( + address vToken, + address redeemer, + uint256 redeemTokens + ) internal view returns (uint256) { + ensureListed(markets[vToken]); + /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ + if (!markets[vToken].accountMembership[redeemer]) { + return uint256(Error.NO_ERROR); + } + /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ + (Error err, , uint256 shortfall) = getHypotheticalAccountLiquidityInternal( + redeemer, + VToken(vToken), + redeemTokens, + 0 + ); + if (err != Error.NO_ERROR) { + return uint256(err); + } + if (shortfall != 0) { + return uint256(Error.INSUFFICIENT_LIQUIDITY); + } + return uint256(Error.NO_ERROR); + } +} diff --git a/contracts/Comptroller/Diamond/facets/MarketFacet.sol b/contracts/Comptroller/Diamond/facets/MarketFacet.sol new file mode 100644 index 000000000..835abcab8 --- /dev/null +++ b/contracts/Comptroller/Diamond/facets/MarketFacet.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { IMarketFacet } from "../interfaces/IMarketFacet.sol"; +import { FacetBase, VToken } from "./FacetBase.sol"; + +/** + * @title MarketFacet + * @author Venus + * @dev This facet contains all the methods related to the market's management in the pool + * @notice This facet contract contains functions regarding markets + */ +contract MarketFacet is IMarketFacet, FacetBase { + /// @notice Emitted when an admin supports a market + event MarketListed(VToken indexed vToken); + + /// @notice Emitted when an account exits a market + event MarketExited(VToken indexed vToken, address indexed account); + + /// @notice Emitted when the borrowing delegate rights are updated for an account + event DelegateUpdated(address indexed borrower, address indexed delegate, bool allowDelegatedBorrows); + + /// @notice Indicator that this is a Comptroller contract (for inspection) + function isComptroller() public pure returns (bool) { + return true; + } + + /** + * @notice Returns the assets an account has entered + * @param account The address of the account to pull assets for + * @return A dynamic list with the assets the account has entered + */ + function getAssetsIn(address account) external view returns (VToken[] memory) { + return accountAssets[account]; + } + + /** + * @notice Return all of the markets + * @dev The automatic getter may be used to access an individual market + * @return The list of market addresses + */ + function getAllMarkets() external view returns (VToken[] memory) { + return allMarkets; + } + + /** + * @notice Calculate number of tokens of collateral asset to seize given an underlying amount + * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) + * @param vTokenBorrowed The address of the borrowed vToken + * @param vTokenCollateral The address of the collateral vToken + * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens + * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) + */ + function liquidateCalculateSeizeTokens( + address vTokenBorrowed, + address vTokenCollateral, + uint256 actualRepayAmount + ) external view returns (uint256, uint256) { + (uint256 err, uint256 seizeTokens) = comptrollerLens.liquidateCalculateSeizeTokens( + address(this), + vTokenBorrowed, + vTokenCollateral, + actualRepayAmount + ); + return (err, seizeTokens); + } + + /** + * @notice Calculate number of tokens of collateral asset to seize given an underlying amount + * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) + * @param vTokenCollateral The address of the collateral vToken + * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens + * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) + */ + function liquidateVAICalculateSeizeTokens( + address vTokenCollateral, + uint256 actualRepayAmount + ) external view returns (uint256, uint256) { + (uint256 err, uint256 seizeTokens) = comptrollerLens.liquidateVAICalculateSeizeTokens( + address(this), + vTokenCollateral, + actualRepayAmount + ); + return (err, seizeTokens); + } + + /** + * @notice Returns whether the given account is entered in the given asset + * @param account The address of the account to check + * @param vToken The vToken to check + * @return True if the account is in the asset, otherwise false + */ + function checkMembership(address account, VToken vToken) external view returns (bool) { + return markets[address(vToken)].accountMembership[account]; + } + + /** + * @notice Add assets to be included in account liquidity calculation + * @param vTokens The list of addresses of the vToken markets to be enabled + * @return Success indicator for whether each corresponding market was entered + */ + function enterMarkets(address[] calldata vTokens) external returns (uint256[] memory) { + uint256 len = vTokens.length; + + uint256[] memory results = new uint256[](len); + for (uint256 i; i < len; ++i) { + results[i] = uint256(addToMarketInternal(VToken(vTokens[i]), msg.sender)); + } + + 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 vTokenAddress The address of the asset to be removed + * @return Whether or not the account successfully exited the market + */ + function exitMarket(address vTokenAddress) external returns (uint256) { + checkActionPauseState(vTokenAddress, Action.EXIT_MARKET); + + VToken vToken = VToken(vTokenAddress); + /* Get sender tokensHeld and amountOwed underlying from the vToken */ + (uint256 oErr, uint256 tokensHeld, uint256 amountOwed, ) = vToken.getAccountSnapshot(msg.sender); + require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code + + /* Fail if the sender has a borrow balance */ + if (amountOwed != 0) { + return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); + } + + /* Fail if the sender is not permitted to redeem all of their tokens */ + uint256 allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); + if (allowed != 0) { + return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); + } + + Market storage marketToExit = markets[address(vToken)]; + + /* Return true if the sender is not already ‘in’ the market */ + if (!marketToExit.accountMembership[msg.sender]) { + return uint256(Error.NO_ERROR); + } + + /* Set vToken account membership to false */ + delete marketToExit.accountMembership[msg.sender]; + + /* Delete vToken from the account’s list of assets */ + // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 + VToken[] storage userAssetList = accountAssets[msg.sender]; + uint256 len = userAssetList.length; + uint256 i; + for (; i < len; ++i) { + if (userAssetList[i] == vToken) { + userAssetList[i] = userAssetList[len - 1]; + userAssetList.length--; + break; + } + } + + // We *must* have found the asset in the list or our redundant data structure is broken + assert(i < len); + + emit MarketExited(vToken, msg.sender); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Add the market to the markets mapping and set it as listed + * @dev Allows a privileged role to add and list markets to the Comptroller + * @param vToken The address of the market (token) to list + * @return uint256 0=success, otherwise a failure. (See enum Error for details) + */ + function _supportMarket(VToken vToken) external returns (uint256) { + ensureAllowed("_supportMarket(address)"); + + if (markets[address(vToken)].isListed) { + return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); + } + + vToken.isVToken(); // Sanity check to make sure its really a VToken + + // Note that isVenus is not in active use anymore + Market storage newMarket = markets[address(vToken)]; + newMarket.isListed = true; + newMarket.isVenus = false; + newMarket.collateralFactorMantissa = 0; + + _addMarketInternal(vToken); + _initializeMarket(address(vToken)); + + emit MarketListed(vToken); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Grants or revokes the borrowing delegate rights to / from an account + * If allowed, the delegate will be able to borrow funds on behalf of the sender + * Upon a delegated borrow, the delegate will receive the funds, and the borrower + * will see the debt on their account + * @param delegate The address to update the rights for + * @param allowBorrows Whether to grant (true) or revoke (false) the rights + */ + function updateDelegate(address delegate, bool allowBorrows) external { + _updateDelegate(msg.sender, delegate, allowBorrows); + } + + function _updateDelegate(address borrower, address delegate, bool allowBorrows) internal { + approvedDelegates[borrower][delegate] = allowBorrows; + emit DelegateUpdated(borrower, delegate, allowBorrows); + } + + function _addMarketInternal(VToken vToken) internal { + uint256 allMarketsLength = allMarkets.length; + for (uint256 i; i < allMarketsLength; ++i) { + require(allMarkets[i] != vToken, "already added"); + } + allMarkets.push(vToken); + } + + function _initializeMarket(address vToken) internal { + uint32 blockNumber = getBlockNumberAsUint32(); + + VenusMarketState storage supplyState = venusSupplyState[vToken]; + VenusMarketState storage borrowState = venusBorrowState[vToken]; + + /* + * Update market state indices + */ + if (supplyState.index == 0) { + // Initialize supply state index with default value + supplyState.index = venusInitialIndex; + } + + if (borrowState.index == 0) { + // Initialize borrow state index with default value + borrowState.index = venusInitialIndex; + } + + /* + * Update market state block numbers + */ + supplyState.block = borrowState.block = blockNumber; + } +} diff --git a/contracts/Comptroller/Diamond/facets/PolicyFacet.sol b/contracts/Comptroller/Diamond/facets/PolicyFacet.sol new file mode 100644 index 000000000..43f2c9316 --- /dev/null +++ b/contracts/Comptroller/Diamond/facets/PolicyFacet.sol @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { IPolicyFacet } from "../interfaces/IPolicyFacet.sol"; + +import { XVSRewardsHelper, VToken } from "./XVSRewardsHelper.sol"; + +/** + * @title PolicyFacet + * @author Venus + * @dev This facet contains all the hooks used while transferring the assets + * @notice This facet contract contains all the external pre-hook functions related to vToken + */ +contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { + /// @notice Emitted when a new borrow-side XVS speed is calculated for a market + event VenusBorrowSpeedUpdated(VToken indexed vToken, uint256 newSpeed); + + /// @notice Emitted when a new supply-side XVS speed is calculated for a market + event VenusSupplySpeedUpdated(VToken indexed vToken, uint256 newSpeed); + + /** + * @notice Checks if the account should be allowed to mint tokens in the given market + * @param vToken The market to verify the mint against + * @param minter The account which would get the minted tokens + * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens + * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) + */ + function mintAllowed(address vToken, address minter, uint256 mintAmount) external returns (uint256) { + // Pausing is a very serious situation - we revert to sound the alarms + checkProtocolPauseState(); + checkActionPauseState(vToken, Action.MINT); + ensureListed(markets[vToken]); + + uint256 supplyCap = supplyCaps[vToken]; + require(supplyCap != 0, "market supply cap is 0"); + + uint256 vTokenSupply = VToken(vToken).totalSupply(); + Exp memory exchangeRate = Exp({ mantissa: VToken(vToken).exchangeRateStored() }); + uint256 nextTotalSupply = mul_ScalarTruncateAddUInt(exchangeRate, vTokenSupply, mintAmount); + require(nextTotalSupply <= supplyCap, "market supply cap reached"); + + // Keep the flywheel moving + updateVenusSupplyIndex(vToken); + distributeSupplierVenus(vToken, minter); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Validates mint and reverts on rejection. May emit logs. + * @param vToken Asset being minted + * @param minter The address minting the tokens + * @param actualMintAmount The amount of the underlying asset being minted + * @param mintTokens The number of tokens being minted + */ + function mintVerify(address vToken, address minter, uint256 actualMintAmount, uint256 mintTokens) external {} + + /** + * @notice Checks if the account should be allowed to redeem tokens in the given market + * @param vToken The market to verify the redeem against + * @param redeemer The account which would redeem the tokens + * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market + * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) + */ + function redeemAllowed(address vToken, address redeemer, uint256 redeemTokens) external returns (uint256) { + checkProtocolPauseState(); + checkActionPauseState(vToken, Action.REDEEM); + + uint256 allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); + if (allowed != uint256(Error.NO_ERROR)) { + return allowed; + } + + // Keep the flywheel moving + updateVenusSupplyIndex(vToken); + distributeSupplierVenus(vToken, redeemer); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Validates redeem and reverts on rejection. May emit log + * @param vToken Asset being redeemed + * @param redeemer The address redeeming the tokens + * @param redeemAmount The amount of the underlying asset being redeemed + * @param redeemTokens The number of tokens being redeemed + */ + // solhint-disable-next-line no-unused-vars + function redeemVerify(address vToken, address redeemer, uint256 redeemAmount, uint256 redeemTokens) external pure { + require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); + } + + /** + * @notice Checks if the account should be allowed to borrow the underlying asset of the given market + * @param vToken The market to verify the borrow against + * @param borrower The account which would borrow the asset + * @param borrowAmount The amount of underlying the account would borrow + * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) + */ + function borrowAllowed(address vToken, address borrower, uint256 borrowAmount) external returns (uint256) { + // Pausing is a very serious situation - we revert to sound the alarms + checkProtocolPauseState(); + checkActionPauseState(vToken, Action.BORROW); + + ensureListed(markets[vToken]); + + if (!markets[vToken].accountMembership[borrower]) { + // only vTokens may call borrowAllowed if borrower not in market + require(msg.sender == vToken, "sender must be vToken"); + + // attempt to add borrower to the market + Error err = addToMarketInternal(VToken(vToken), borrower); + if (err != Error.NO_ERROR) { + return uint256(err); + } + } + + if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { + return uint256(Error.PRICE_ERROR); + } + + uint256 borrowCap = borrowCaps[vToken]; + // Borrow cap of 0 corresponds to unlimited borrowing + if (borrowCap != 0) { + uint256 nextTotalBorrows = add_(VToken(vToken).totalBorrows(), borrowAmount); + require(nextTotalBorrows < borrowCap, "market borrow cap reached"); + } + + (Error err, , uint256 shortfall) = getHypotheticalAccountLiquidityInternal( + borrower, + VToken(vToken), + 0, + borrowAmount + ); + if (err != Error.NO_ERROR) { + return uint256(err); + } + if (shortfall != 0) { + return uint256(Error.INSUFFICIENT_LIQUIDITY); + } + + // Keep the flywheel moving + Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); + updateVenusBorrowIndex(vToken, borrowIndex); + distributeBorrowerVenus(vToken, borrower, borrowIndex); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Validates borrow and reverts on rejection. May emit log + * @param vToken Asset whose underlying is being borrowed + * @param borrower The address borrowing the underlying + * @param borrowAmount The amount of the underlying asset requested to borrow + */ + // solhint-disable-next-line no-unused-vars + function borrowVerify(address vToken, address borrower, uint256 borrowAmount) external {} + + /** + * @notice Checks if the account should be allowed to repay a borrow in the given market + * @param vToken The market to verify the repay against + * @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 account would repay + * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) + */ + function repayBorrowAllowed( + address vToken, + // solhint-disable-next-line no-unused-vars + address payer, + address borrower, + // solhint-disable-next-line no-unused-vars + uint256 repayAmount + ) external returns (uint256) { + checkProtocolPauseState(); + checkActionPauseState(vToken, Action.REPAY); + ensureListed(markets[vToken]); + + // Keep the flywheel moving + Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); + updateVenusBorrowIndex(vToken, borrowIndex); + distributeBorrowerVenus(vToken, borrower, borrowIndex); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Validates repayBorrow and reverts on rejection. May emit log + * @param vToken Asset being repaid + * @param payer The address repaying the borrow + * @param borrower The address of the borrower + * @param actualRepayAmount The amount of underlying being repaid + */ + function repayBorrowVerify( + address vToken, + address payer, + address borrower, + uint256 actualRepayAmount, + uint256 borrowerIndex + ) external {} + + /** + * @notice Checks if the liquidation should be allowed to occur + * @param vTokenBorrowed Asset which was borrowed by the borrower + * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid + */ + function liquidateBorrowAllowed( + address vTokenBorrowed, + address vTokenCollateral, + address liquidator, + address borrower, + uint256 repayAmount + ) external view returns (uint256) { + checkProtocolPauseState(); + + // if we want to pause liquidating to vTokenCollateral, we should pause seizing + checkActionPauseState(vTokenBorrowed, Action.LIQUIDATE); + + if (liquidatorContract != address(0) && liquidator != liquidatorContract) { + return uint256(Error.UNAUTHORIZED); + } + + ensureListed(markets[vTokenCollateral]); + + uint256 borrowBalance; + if (address(vTokenBorrowed) != address(vaiController)) { + ensureListed(markets[vTokenBorrowed]); + borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); + } else { + borrowBalance = vaiController.getVAIRepayAmount(borrower); + } + + if (isForcedLiquidationEnabled[vTokenBorrowed]) { + if (repayAmount > borrowBalance) { + return uint(Error.TOO_MUCH_REPAY); + } + return uint(Error.NO_ERROR); + } + + /* The borrower must have shortfall in order to be liquidatable */ + (Error err, , uint256 shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(address(0)), 0, 0); + if (err != Error.NO_ERROR) { + return uint256(err); + } + if (shortfall == 0) { + return uint256(Error.INSUFFICIENT_SHORTFALL); + } + + // The liquidator may not repay more than what is allowed by the closeFactor + //-- maxClose = multipy of closeFactorMantissa and borrowBalance + if (repayAmount > mul_ScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance)) { + return uint256(Error.TOO_MUCH_REPAY); + } + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. + * @param vTokenBorrowed Asset which was borrowed by the borrower + * @param vTokenCollateral 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 actualRepayAmount The amount of underlying being repaid + * @param seizeTokens The amount of collateral token that will be seized + */ + function liquidateBorrowVerify( + address vTokenBorrowed, + address vTokenCollateral, + address liquidator, + address borrower, + uint256 actualRepayAmount, + uint256 seizeTokens + ) external {} + + /** + * @notice Checks if the seizing of assets should be allowed to occur + * @param vTokenCollateral Asset which was used as collateral and will be seized + * @param vTokenBorrowed 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 vTokenCollateral, + address vTokenBorrowed, + address liquidator, + address borrower, + uint256 seizeTokens // solhint-disable-line no-unused-vars + ) external returns (uint256) { + // Pausing is a very serious situation - we revert to sound the alarms + checkProtocolPauseState(); + checkActionPauseState(vTokenCollateral, Action.SEIZE); + + Market storage market = markets[vTokenCollateral]; + + // We've added VAIController as a borrowed token list check for seize + ensureListed(market); + + if (!market.accountMembership[borrower]) { + return uint256(Error.MARKET_NOT_COLLATERAL); + } + + if (address(vTokenBorrowed) != address(vaiController)) { + ensureListed(markets[vTokenBorrowed]); + } + + if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { + return uint256(Error.COMPTROLLER_MISMATCH); + } + + // Keep the flywheel moving + updateVenusSupplyIndex(vTokenCollateral); + distributeSupplierVenus(vTokenCollateral, borrower); + distributeSupplierVenus(vTokenCollateral, liquidator); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Validates seize and reverts on rejection. May emit log + * @param vTokenCollateral Asset which was used as collateral and will be seized + * @param vTokenBorrowed 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 + */ + // solhint-disable-next-line no-unused-vars + function seizeVerify( + address vTokenCollateral, + address vTokenBorrowed, + address liquidator, + address borrower, + uint256 seizeTokens + ) external {} + + /** + * @notice Checks if the account should be allowed to transfer tokens in the given market + * @param vToken The market to verify the transfer against + * @param src The account which sources the tokens + * @param dst The account which receives the tokens + * @param transferTokens The number of vTokens to transfer + * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) + */ + function transferAllowed( + address vToken, + address src, + address dst, + uint256 transferTokens + ) external returns (uint256) { + // Pausing is a very serious situation - we revert to sound the alarms + checkProtocolPauseState(); + checkActionPauseState(vToken, Action.TRANSFER); + + // Currently the only consideration is whether or not + // the src is allowed to redeem this many tokens + uint256 allowed = redeemAllowedInternal(vToken, src, transferTokens); + if (allowed != uint256(Error.NO_ERROR)) { + return allowed; + } + + // Keep the flywheel moving + updateVenusSupplyIndex(vToken); + distributeSupplierVenus(vToken, src); + distributeSupplierVenus(vToken, dst); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Validates transfer and reverts on rejection. May emit log + * @param vToken Asset being transferred + * @param src The account which sources the tokens + * @param dst The account which receives the tokens + * @param transferTokens The number of vTokens to transfer + */ + // solhint-disable-next-line no-unused-vars + function transferVerify(address vToken, address src, address dst, uint256 transferTokens) external {} + + /** + * @notice Determine the current account liquidity wrt collateral requirements + * @return (possible error code (semi-opaque), + account liquidity in excess of collateral requirements, + * account shortfall below collateral requirements) + */ + function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256) { + (Error err, uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidityInternal( + account, + VToken(address(0)), + 0, + 0 + ); + + return (uint256(err), liquidity, shortfall); + } + + /** + * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed + * @param vTokenModify The market to hypothetically redeem/borrow in + * @param account The account to determine liquidity for + * @param redeemTokens The number of tokens to hypothetically redeem + * @param borrowAmount The amount of underlying to hypothetically borrow + * @return (possible error code (semi-opaque), + hypothetical account liquidity in excess of collateral requirements, + * hypothetical account shortfall below collateral requirements) + */ + function getHypotheticalAccountLiquidity( + address account, + address vTokenModify, + uint256 redeemTokens, + uint256 borrowAmount + ) external view returns (uint256, uint256, uint256) { + (Error err, uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidityInternal( + account, + VToken(vTokenModify), + redeemTokens, + borrowAmount + ); + return (uint256(err), liquidity, shortfall); + } + + // setter functionality + /** + * @notice Set XVS speed for a single market + * @dev Allows the contract admin to set XVS speed for a market + * @param vTokens The market whose XVS speed to update + * @param supplySpeeds New XVS speed for supply + * @param borrowSpeeds New XVS speed for borrow + */ + function _setVenusSpeeds( + VToken[] calldata vTokens, + uint256[] calldata supplySpeeds, + uint256[] calldata borrowSpeeds + ) external { + ensureAdmin(); + + uint256 numTokens = vTokens.length; + require(numTokens == supplySpeeds.length && numTokens == borrowSpeeds.length, "invalid input"); + + for (uint256 i; i < numTokens; ++i) { + ensureNonzeroAddress(address(vTokens[i])); + setVenusSpeedInternal(vTokens[i], supplySpeeds[i], borrowSpeeds[i]); + } + } + + function setVenusSpeedInternal(VToken vToken, uint256 supplySpeed, uint256 borrowSpeed) internal { + ensureListed(markets[address(vToken)]); + + if (venusSupplySpeeds[address(vToken)] != supplySpeed) { + // Supply speed updated so let's update supply state to ensure that + // 1. XVS accrued properly for the old speed, and + // 2. XVS accrued at the new speed starts after this block. + + updateVenusSupplyIndex(address(vToken)); + // Update speed and emit event + venusSupplySpeeds[address(vToken)] = supplySpeed; + emit VenusSupplySpeedUpdated(vToken, supplySpeed); + } + + if (venusBorrowSpeeds[address(vToken)] != borrowSpeed) { + // Borrow speed updated so let's update borrow state to ensure that + // 1. XVS accrued properly for the old speed, and + // 2. XVS accrued at the new speed starts after this block. + Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); + updateVenusBorrowIndex(address(vToken), borrowIndex); + + // Update speed and emit event + venusBorrowSpeeds[address(vToken)] = borrowSpeed; + emit VenusBorrowSpeedUpdated(vToken, borrowSpeed); + } + } +} diff --git a/contracts/Comptroller/Diamond/facets/RewardFacet.sol b/contracts/Comptroller/Diamond/facets/RewardFacet.sol new file mode 100644 index 000000000..5a417cb4b --- /dev/null +++ b/contracts/Comptroller/Diamond/facets/RewardFacet.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { IRewardFacet } from "../interfaces/IRewardFacet.sol"; +import { XVSRewardsHelper, VToken } from "./XVSRewardsHelper.sol"; +import { SafeBEP20, IBEP20 } from "../../../Utils/SafeBEP20.sol"; +import { VBep20Interface } from "../../../Tokens/VTokens/VTokenInterfaces.sol"; + +/** + * @title RewardFacet + * @author Venus + * @dev This facet contains all the methods related to the reward functionality + * @notice This facet contract provides the external functions related to all claims and rewards of the protocol + */ +contract RewardFacet is IRewardFacet, XVSRewardsHelper { + /// @notice Emitted when Venus is granted by admin + event VenusGranted(address indexed recipient, uint256 amount); + + using SafeBEP20 for IBEP20; + + /** + * @notice Claim all the xvs accrued by holder in all markets and VAI + * @param holder The address to claim XVS for + */ + function claimVenus(address holder) public { + return claimVenus(holder, allMarkets); + } + + /** + * @notice Claim all the xvs accrued by holder in the specified markets + * @param holder The address to claim XVS for + * @param vTokens The list of markets to claim XVS in + */ + function claimVenus(address holder, VToken[] memory vTokens) public { + address[] memory holders = new address[](1); + holders[0] = holder; + claimVenus(holders, vTokens, true, true); + } + + /** + * @notice Claim all xvs accrued by the holders + * @param holders The addresses to claim XVS for + * @param vTokens The list of markets to claim XVS in + * @param borrowers Whether or not to claim XVS earned by borrowing + * @param suppliers Whether or not to claim XVS earned by supplying + */ + function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { + claimVenus(holders, vTokens, borrowers, suppliers, false); + } + + /** + * @notice Claim all the xvs accrued by holder in all markets, a shorthand for `claimVenus` with collateral set to `true` + * @param holder The address to claim XVS for + */ + function claimVenusAsCollateral(address holder) external { + address[] memory holders = new address[](1); + holders[0] = holder; + claimVenus(holders, allMarkets, true, true, true); + } + + /** + * @notice Transfer XVS to the user with user's shortfall considered + * @dev Note: If there is not enough XVS, we do not perform the transfer all + * @param user The address of the user to transfer XVS to + * @param amount The amount of XVS to (possibly) transfer + * @param shortfall The shortfall of the user + * @param collateral Whether or not we will use user's venus reward as collateral to pay off the debt + * @return The amount of XVS which was NOT transferred to the user + */ + function grantXVSInternal( + address user, + uint256 amount, + uint256 shortfall, + bool collateral + ) internal returns (uint256) { + // If the user is blacklisted, they can't get XVS rewards + require( + user != 0xEF044206Db68E40520BfA82D45419d498b4bc7Bf && + user != 0x7589dD3355DAE848FDbF75044A3495351655cB1A && + user != 0x33df7a7F6D44307E1e5F3B15975b47515e5524c0 && + user != 0x24e77E5b74B30b026E9996e4bc3329c881e24968, + "Blacklisted" + ); + + if (amount == 0 || amount > IBEP20(getXVSAddress()).balanceOf(address(this))) { + return amount; + } + + if (shortfall == 0) { + IBEP20(getXVSAddress()).safeTransfer(user, amount); + return 0; + } + // If user's bankrupt and doesn't use pending xvs as collateral, don't grant + // anything, otherwise, we will transfer the pending xvs as collateral to + // vXVS token and mint vXVS for the user + // + // If mintBehalf failed, don't grant any xvs + require(collateral, "bankrupt"); + + IBEP20(getXVSAddress()).safeApprove(getXVSVTokenAddress(), 0); + IBEP20(getXVSAddress()).safeApprove(getXVSVTokenAddress(), amount); + require( + VBep20Interface(getXVSVTokenAddress()).mintBehalf(user, amount) == uint256(Error.NO_ERROR), + "mint behalf error" + ); + + // set venusAccrued[user] to 0 + return 0; + } + + /*** Venus Distribution Admin ***/ + + /** + * @notice Transfer XVS to the recipient + * @dev Allows the contract admin to transfer XVS to any recipient based on the recipient's shortfall + * Note: If there is not enough XVS, we do not perform the transfer all + * @param recipient The address of the recipient to transfer XVS to + * @param amount The amount of XVS to (possibly) transfer + */ + function _grantXVS(address recipient, uint256 amount) external { + ensureAdmin(); + uint256 amountLeft = grantXVSInternal(recipient, amount, 0, false); + require(amountLeft == 0, "no xvs"); + emit VenusGranted(recipient, amount); + } + + /** + * @notice Return the address of the XVS vToken + * @return The address of XVS vToken + */ + function getXVSVTokenAddress() public pure returns (address) { + return 0x151B1e2635A717bcDc836ECd6FbB62B674FE3E1D; + } + + /** + * @notice Claim all xvs accrued by the holders + * @param holders The addresses to claim XVS for + * @param vTokens The list of markets to claim XVS in + * @param borrowers Whether or not to claim XVS earned by borrowing + * @param suppliers Whether or not to claim XVS earned by supplying + * @param collateral Whether or not to use XVS earned as collateral, only takes effect when the holder has a shortfall + */ + function claimVenus( + address[] memory holders, + VToken[] memory vTokens, + bool borrowers, + bool suppliers, + bool collateral + ) public { + uint256 j; + uint256 holdersLength = holders.length; + uint256 vTokensLength = vTokens.length; + for (uint256 i; i < vTokensLength; ++i) { + VToken vToken = vTokens[i]; + ensureListed(markets[address(vToken)]); + if (borrowers) { + Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); + updateVenusBorrowIndex(address(vToken), borrowIndex); + for (j = 0; j < holdersLength; ++j) { + distributeBorrowerVenus(address(vToken), holders[j], borrowIndex); + } + } + if (suppliers) { + updateVenusSupplyIndex(address(vToken)); + for (j = 0; j < holdersLength; ++j) { + distributeSupplierVenus(address(vToken), holders[j]); + } + } + } + + for (j = 0; j < holdersLength; ++j) { + address holder = holders[j]; + // If there is a positive shortfall, the XVS reward is accrued, + // but won't be granted to this holder + (, , uint256 shortfall) = getHypotheticalAccountLiquidityInternal(holder, VToken(address(0)), 0, 0); + + uint256 value = venusAccrued[holder]; + venusAccrued[holder] = 0; + + uint256 returnAmount = grantXVSInternal(holder, value, shortfall, collateral); + + // returnAmount can only be positive if balance of xvsAddress is less than grant amount(venusAccrued[holder]) + if (returnAmount != 0) { + venusAccrued[holder] = returnAmount; + } + } + } +} diff --git a/contracts/Comptroller/Diamond/facets/SetterFacet.sol b/contracts/Comptroller/Diamond/facets/SetterFacet.sol new file mode 100644 index 000000000..47b9f7c93 --- /dev/null +++ b/contracts/Comptroller/Diamond/facets/SetterFacet.sol @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { ISetterFacet } from "../interfaces/ISetterFacet.sol"; +import { PriceOracle } from "../../../Oracle/PriceOracle.sol"; +import { ComptrollerLensInterface } from "../../ComptrollerLensInterface.sol"; +import { VAIControllerInterface } from "../../../Tokens/VAI/VAIControllerInterface.sol"; +import { FacetBase, VToken } from "./FacetBase.sol"; + +/** + * @title SetterFacet + * @author Venus + * @dev This facet contains all the setters for the states + * @notice This facet contract contains all the configurational setter functions + */ +contract SetterFacet is ISetterFacet, FacetBase { + /// @notice Emitted when close factor is changed by admin + event NewCloseFactor(uint256 oldCloseFactorMantissa, uint256 newCloseFactorMantissa); + + /// @notice Emitted when a collateral factor is changed by admin + event NewCollateralFactor( + VToken indexed vToken, + uint256 oldCollateralFactorMantissa, + uint256 newCollateralFactorMantissa + ); + + /// @notice Emitted when liquidation incentive is changed by admin + event NewLiquidationIncentive(uint256 oldLiquidationIncentiveMantissa, uint256 newLiquidationIncentiveMantissa); + + /// @notice Emitted when price oracle is changed + event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); + + /// @notice Emitted when borrow cap for a vToken is changed + event NewBorrowCap(VToken indexed vToken, uint256 newBorrowCap); + + /// @notice Emitted when VAIController is changed + event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); + + /// @notice Emitted when VAI mint rate is changed by admin + event NewVAIMintRate(uint256 oldVAIMintRate, uint256 newVAIMintRate); + + /// @notice Emitted when protocol state is changed by admin + event ActionProtocolPaused(bool state); + + /// @notice Emitted when treasury guardian is changed + event NewTreasuryGuardian(address oldTreasuryGuardian, address newTreasuryGuardian); + + /// @notice Emitted when treasury address is changed + event NewTreasuryAddress(address oldTreasuryAddress, address newTreasuryAddress); + + /// @notice Emitted when treasury percent is changed + event NewTreasuryPercent(uint256 oldTreasuryPercent, uint256 newTreasuryPercent); + + /// @notice Emitted when liquidator adress is changed + event NewLiquidatorContract(address oldLiquidatorContract, address newLiquidatorContract); + + /// @notice Emitted when ComptrollerLens address is changed + event NewComptrollerLens(address oldComptrollerLens, address newComptrollerLens); + + /// @notice Emitted when supply cap for a vToken is changed + event NewSupplyCap(VToken indexed vToken, uint256 newSupplyCap); + + /// @notice Emitted when access control address is changed by admin + event NewAccessControl(address oldAccessControlAddress, address newAccessControlAddress); + + /// @notice Emitted when pause guardian is changed + event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); + + /// @notice Emitted when an action is paused on a market + event ActionPausedMarket(VToken indexed vToken, Action indexed action, bool pauseState); + + /// @notice Emitted when VAI Vault info is changed + event NewVAIVaultInfo(address indexed vault_, uint256 releaseStartBlock_, uint256 releaseInterval_); + + /// @notice Emitted when Venus VAI Vault rate is changed + event NewVenusVAIVaultRate(uint256 oldVenusVAIVaultRate, uint256 newVenusVAIVaultRate); + + /// @notice Emitted when force liquidation enabled for a market + event IsForcedLiquidationEnabledUpdated(address indexed vToken, bool enable); + + /** + * @notice Compare two addresses to ensure they are different + * @param oldAddress The original address to compare + * @param newAddress The new address to compare + */ + modifier compareAddress(address oldAddress, address newAddress) { + require(oldAddress != newAddress, "old address is same as new address"); + _; + } + + /** + * @notice Compare two values to ensure they are different + * @param oldValue The original value to compare + * @param newValue The new value to compare + */ + modifier compareValue(uint256 oldValue, uint256 newValue) { + require(oldValue != newValue, "old value is same as new value"); + _; + } + + /** + * @notice Sets a new price oracle for the comptroller + * @dev Allows the contract admin to set a new price oracle used by the Comptroller + * @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setPriceOracle( + PriceOracle newOracle + ) external compareAddress(address(oracle), address(newOracle)) returns (uint256) { + // Check caller is admin + ensureAdmin(); + ensureNonzeroAddress(address(newOracle)); + + // Track the old oracle for the comptroller + PriceOracle oldOracle = oracle; + + // Set comptroller's oracle to newOracle + oracle = newOracle; + + // Emit NewPriceOracle(oldOracle, newOracle) + emit NewPriceOracle(oldOracle, newOracle); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Sets the closeFactor used when liquidating borrows + * @dev Allows the contract admin to set the closeFactor used to liquidate borrows + * @param newCloseFactorMantissa New close factor, scaled by 1e18 + * @return uint256 0=success, otherwise will revert + */ + function _setCloseFactor( + uint256 newCloseFactorMantissa + ) external compareValue(closeFactorMantissa, newCloseFactorMantissa) returns (uint256) { + // Check caller is admin + ensureAdmin(); + + Exp memory newCloseFactorExp = Exp({ mantissa: newCloseFactorMantissa }); + + //-- Check close factor <= 0.9 + Exp memory highLimit = Exp({ mantissa: closeFactorMaxMantissa }); + //-- Check close factor >= 0.05 + Exp memory lowLimit = Exp({ mantissa: closeFactorMinMantissa }); + + if (lessThanExp(highLimit, newCloseFactorExp) || greaterThanExp(lowLimit, newCloseFactorExp)) { + return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); + } + + uint256 oldCloseFactorMantissa = closeFactorMantissa; + closeFactorMantissa = newCloseFactorMantissa; + emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Sets the address of the access control of this contract + * @dev Allows the contract admin to set the address of access control of this contract + * @param newAccessControlAddress New address for the access control + * @return uint256 0=success, otherwise will revert + */ + function _setAccessControl( + address newAccessControlAddress + ) external compareAddress(accessControl, newAccessControlAddress) returns (uint256) { + // Check caller is admin + ensureAdmin(); + ensureNonzeroAddress(newAccessControlAddress); + + address oldAccessControlAddress = accessControl; + + accessControl = newAccessControlAddress; + emit NewAccessControl(oldAccessControlAddress, newAccessControlAddress); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Sets the collateralFactor for a market + * @dev Allows a privileged role to set the collateralFactorMantissa + * @param vToken The market to set the factor on + * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 + * @return uint256 0=success, otherwise a failure. (See ErrorReporter for details) + */ + function _setCollateralFactor( + VToken vToken, + uint256 newCollateralFactorMantissa + ) + external + compareValue(markets[address(vToken)].collateralFactorMantissa, newCollateralFactorMantissa) + returns (uint256) + { + // Check caller is allowed by access control manager + ensureAllowed("_setCollateralFactor(address,uint256)"); + ensureNonzeroAddress(address(vToken)); + + // Verify market is listed + Market storage market = markets[address(vToken)]; + ensureListed(market); + + Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); + + //-- Check collateral factor <= 0.9 + Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); + if (lessThanExp(highLimit, newCollateralFactorExp)) { + return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); + } + + // If collateral factor != 0, fail if price == 0 + if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { + return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); + } + + // Set market's collateral factor to new collateral factor, remember old value + uint256 oldCollateralFactorMantissa = market.collateralFactorMantissa; + market.collateralFactorMantissa = newCollateralFactorMantissa; + + // Emit event with asset, old collateral factor, and new collateral factor + emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Sets liquidationIncentive + * @dev Allows a privileged role to set the liquidationIncentiveMantissa + * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 + * @return uint256 0=success, otherwise a failure. (See ErrorReporter for details) + */ + function _setLiquidationIncentive( + uint256 newLiquidationIncentiveMantissa + ) external compareValue(liquidationIncentiveMantissa, newLiquidationIncentiveMantissa) returns (uint256) { + ensureAllowed("_setLiquidationIncentive(uint256)"); + + require(newLiquidationIncentiveMantissa >= 1e18, "incentive < 1e18"); + + // Save current value for use in log + uint256 oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; + // Set liquidation incentive to new incentive + liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; + + // Emit event with old incentive, new incentive + emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Update the address of the liquidator contract + * @dev Allows the contract admin to update the address of liquidator contract + * @param newLiquidatorContract_ The new address of the liquidator contract + */ + function _setLiquidatorContract( + address newLiquidatorContract_ + ) external compareAddress(liquidatorContract, newLiquidatorContract_) { + // Check caller is admin + ensureAdmin(); + ensureNonzeroAddress(newLiquidatorContract_); + address oldLiquidatorContract = liquidatorContract; + liquidatorContract = newLiquidatorContract_; + emit NewLiquidatorContract(oldLiquidatorContract, newLiquidatorContract_); + } + + /** + * @notice Admin function to change the Pause Guardian + * @dev Allows the contract admin to change the Pause Guardian + * @param newPauseGuardian The address of the new Pause Guardian + * @return uint256 0=success, otherwise a failure. (See enum Error for details) + */ + function _setPauseGuardian( + address newPauseGuardian + ) external compareAddress(pauseGuardian, newPauseGuardian) returns (uint256) { + ensureAdmin(); + ensureNonzeroAddress(newPauseGuardian); + + // Save current value for inclusion in log + address oldPauseGuardian = pauseGuardian; + // Store pauseGuardian with value newPauseGuardian + pauseGuardian = newPauseGuardian; + + // Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian) + emit NewPauseGuardian(oldPauseGuardian, newPauseGuardian); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Set the given borrow caps for the given vToken market Borrowing that brings total borrows to or above borrow cap will revert + * @dev Allows a privileged role to set the borrowing cap for a vToken market. A borrow cap of 0 corresponds to unlimited borrowing + * @param vTokens The addresses of the markets (tokens) to change the borrow caps for + * @param newBorrowCaps The new borrow cap values in underlying to be set. A value of 0 corresponds to unlimited borrowing + */ + function _setMarketBorrowCaps(VToken[] calldata vTokens, uint256[] calldata newBorrowCaps) external { + ensureAllowed("_setMarketBorrowCaps(address[],uint256[])"); + + uint256 numMarkets = vTokens.length; + uint256 numBorrowCaps = newBorrowCaps.length; + + require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input"); + + for (uint256 i; i < numMarkets; ++i) { + borrowCaps[address(vTokens[i])] = newBorrowCaps[i]; + emit NewBorrowCap(vTokens[i], newBorrowCaps[i]); + } + } + + /** + * @notice Set the given supply caps for the given vToken market Supply that brings total Supply to or above supply cap will revert + * @dev Allows a privileged role to set the supply cap for a vToken. A supply cap of 0 corresponds to Minting NotAllowed + * @param vTokens The addresses of the markets (tokens) to change the supply caps for + * @param newSupplyCaps The new supply cap values in underlying to be set. A value of 0 corresponds to Minting NotAllowed + */ + function _setMarketSupplyCaps(VToken[] calldata vTokens, uint256[] calldata newSupplyCaps) external { + ensureAllowed("_setMarketSupplyCaps(address[],uint256[])"); + + uint256 numMarkets = vTokens.length; + uint256 numSupplyCaps = newSupplyCaps.length; + + require(numMarkets != 0 && numMarkets == numSupplyCaps, "invalid input"); + + for (uint256 i; i < numMarkets; ++i) { + supplyCaps[address(vTokens[i])] = newSupplyCaps[i]; + emit NewSupplyCap(vTokens[i], newSupplyCaps[i]); + } + } + + /** + * @notice Set whole protocol pause/unpause state + * @dev Allows a privileged role to pause/unpause protocol + * @param state The new state (true=paused, false=unpaused) + * @return bool The updated state of the protocol + */ + function _setProtocolPaused(bool state) external returns (bool) { + ensureAllowed("_setProtocolPaused(bool)"); + + protocolPaused = state; + emit ActionProtocolPaused(state); + return state; + } + + /** + * @notice Pause/unpause certain actions + * @dev Allows a privileged role to pause/unpause the protocol action state + * @param markets_ Markets to pause/unpause the actions on + * @param actions_ List of action ids to pause/unpause + * @param paused_ The new paused state (true=paused, false=unpaused) + */ + function _setActionsPaused(address[] calldata markets_, Action[] calldata actions_, bool paused_) external { + ensureAllowed("_setActionsPaused(address[],uint8[],bool)"); + + uint256 numMarkets = markets_.length; + uint256 numActions = actions_.length; + for (uint256 marketIdx; marketIdx < numMarkets; ++marketIdx) { + for (uint256 actionIdx; actionIdx < numActions; ++actionIdx) { + setActionPausedInternal(markets_[marketIdx], actions_[actionIdx], paused_); + } + } + } + + /** + * @dev Pause/unpause an action on a market + * @param market Market to pause/unpause the action on + * @param action Action id to pause/unpause + * @param paused The new paused state (true=paused, false=unpaused) + */ + function setActionPausedInternal(address market, Action action, bool paused) internal { + ensureListed(markets[market]); + _actionPaused[market][uint256(action)] = paused; + emit ActionPausedMarket(VToken(market), action, paused); + } + + /** + * @notice Sets a new VAI controller + * @dev Admin function to set a new VAI controller + * @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setVAIController( + VAIControllerInterface vaiController_ + ) external compareAddress(address(vaiController), address(vaiController_)) returns (uint256) { + // Check caller is admin + ensureAdmin(); + ensureNonzeroAddress(address(vaiController_)); + + VAIControllerInterface oldVaiController = vaiController; + vaiController = vaiController_; + emit NewVAIController(oldVaiController, vaiController_); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Set the VAI mint rate + * @param newVAIMintRate The new VAI mint rate to be set + * @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setVAIMintRate( + uint256 newVAIMintRate + ) external compareValue(vaiMintRate, newVAIMintRate) returns (uint256) { + // Check caller is admin + ensureAdmin(); + uint256 oldVAIMintRate = vaiMintRate; + vaiMintRate = newVAIMintRate; + emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Set the minted VAI amount of the `owner` + * @param owner The address of the account to set + * @param amount The amount of VAI to set to the account + * @return The number of minted VAI by `owner` + */ + function setMintedVAIOf(address owner, uint256 amount) external returns (uint256) { + checkProtocolPauseState(); + + // Pausing is a very serious situation - we revert to sound the alarms + require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); + // Check caller is vaiController + if (msg.sender != address(vaiController)) { + return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); + } + mintedVAIs[owner] = amount; + return uint256(Error.NO_ERROR); + } + + /** + * @notice Set the treasury data. + * @param newTreasuryGuardian The new address of the treasury guardian to be set + * @param newTreasuryAddress The new address of the treasury to be set + * @param newTreasuryPercent The new treasury percent to be set + * @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setTreasuryData( + address newTreasuryGuardian, + address newTreasuryAddress, + uint256 newTreasuryPercent + ) external returns (uint256) { + // Check caller is admin + ensureAdminOr(treasuryGuardian); + + require(newTreasuryPercent < 1e18, "percent >= 100%"); + ensureNonzeroAddress(newTreasuryGuardian); + ensureNonzeroAddress(newTreasuryAddress); + + address oldTreasuryGuardian = treasuryGuardian; + address oldTreasuryAddress = treasuryAddress; + uint256 oldTreasuryPercent = treasuryPercent; + + treasuryGuardian = newTreasuryGuardian; + treasuryAddress = newTreasuryAddress; + treasuryPercent = newTreasuryPercent; + + emit NewTreasuryGuardian(oldTreasuryGuardian, newTreasuryGuardian); + emit NewTreasuryAddress(oldTreasuryAddress, newTreasuryAddress); + emit NewTreasuryPercent(oldTreasuryPercent, newTreasuryPercent); + + return uint256(Error.NO_ERROR); + } + + /*** Venus Distribution ***/ + + /** + * @dev Set ComptrollerLens contract address + * @param comptrollerLens_ The new ComptrollerLens contract address to be set + * @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setComptrollerLens( + ComptrollerLensInterface comptrollerLens_ + ) external compareAddress(address(comptrollerLens), address(comptrollerLens_)) returns (uint256) { + ensureAdmin(); + ensureNonzeroAddress(address(comptrollerLens_)); + address oldComptrollerLens = address(comptrollerLens); + comptrollerLens = comptrollerLens_; + emit NewComptrollerLens(oldComptrollerLens, address(comptrollerLens)); + + return uint256(Error.NO_ERROR); + } + + /** + * @notice Set the amount of XVS distributed per block to VAI Vault + * @param venusVAIVaultRate_ The amount of XVS wei per block to distribute to VAI Vault + */ + function _setVenusVAIVaultRate( + uint256 venusVAIVaultRate_ + ) external compareValue(venusVAIVaultRate, venusVAIVaultRate_) { + ensureAdmin(); + if (vaiVaultAddress != address(0)) { + releaseToVault(); + } + uint256 oldVenusVAIVaultRate = venusVAIVaultRate; + venusVAIVaultRate = venusVAIVaultRate_; + emit NewVenusVAIVaultRate(oldVenusVAIVaultRate, venusVAIVaultRate_); + } + + /** + * @notice Set the VAI Vault infos + * @param vault_ The address of the VAI Vault + * @param releaseStartBlock_ The start block of release to VAI Vault + * @param minReleaseAmount_ The minimum release amount to VAI Vault + */ + function _setVAIVaultInfo( + address vault_, + uint256 releaseStartBlock_, + uint256 minReleaseAmount_ + ) external compareAddress(vaiVaultAddress, vault_) { + ensureAdmin(); + ensureNonzeroAddress(vault_); + if (vaiVaultAddress != address(0)) { + releaseToVault(); + } + + vaiVaultAddress = vault_; + releaseStartBlock = releaseStartBlock_; + minReleaseAmount = minReleaseAmount_; + emit NewVAIVaultInfo(vault_, releaseStartBlock_, minReleaseAmount_); + } + + /** + * @notice Enables forced liquidations for a market. If forced liquidation is enabled, + * borrows in the market may be liquidated regardless of the account liquidity + * @param vTokenBorrowed Borrowed vToken + * @param enable Whether to enable forced liquidations + */ + function _setForcedLiquidation(address vTokenBorrowed, bool enable) external { + ensureAllowed("_setForcedLiquidation(address,bool)"); + if (vTokenBorrowed != address(vaiController)) { + ensureListed(markets[vTokenBorrowed]); + } + isForcedLiquidationEnabled[address(vTokenBorrowed)] = enable; + emit IsForcedLiquidationEnabledUpdated(vTokenBorrowed, enable); + } +} diff --git a/contracts/Comptroller/Diamond/facets/XVSRewardsHelper.sol b/contracts/Comptroller/Diamond/facets/XVSRewardsHelper.sol new file mode 100644 index 000000000..76eda442a --- /dev/null +++ b/contracts/Comptroller/Diamond/facets/XVSRewardsHelper.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { FacetBase, VToken } from "./FacetBase.sol"; + +/** + * @title XVSRewardsHelper + * @author Venus + * @dev This contract contains internal functions used in RewardFacet and PolicyFacet + * @notice This facet contract contains the shared functions used by the RewardFacet and PolicyFacet + */ +contract XVSRewardsHelper is FacetBase { + /// @notice Emitted when XVS is distributed to a borrower + event DistributedBorrowerVenus( + VToken indexed vToken, + address indexed borrower, + uint256 venusDelta, + uint256 venusBorrowIndex + ); + + /// @notice Emitted when XVS is distributed to a supplier + event DistributedSupplierVenus( + VToken indexed vToken, + address indexed supplier, + uint256 venusDelta, + uint256 venusSupplyIndex + ); + + /** + * @notice Accrue XVS to the market by updating the borrow index + * @param vToken The market whose borrow index to update + */ + function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { + VenusMarketState storage borrowState = venusBorrowState[vToken]; + uint256 borrowSpeed = venusBorrowSpeeds[vToken]; + uint32 blockNumber = getBlockNumberAsUint32(); + uint256 deltaBlocks = sub_(blockNumber, borrowState.block); + if (deltaBlocks != 0 && borrowSpeed != 0) { + uint256 borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); + uint256 accruedVenus = mul_(deltaBlocks, borrowSpeed); + Double memory ratio = borrowAmount != 0 ? fraction(accruedVenus, borrowAmount) : Double({ mantissa: 0 }); + borrowState.index = safe224(add_(Double({ mantissa: borrowState.index }), ratio).mantissa, "224"); + borrowState.block = blockNumber; + } else if (deltaBlocks != 0) { + borrowState.block = blockNumber; + } + } + + /** + * @notice Accrue XVS to the market by updating the supply index + * @param vToken The market whose supply index to update + */ + function updateVenusSupplyIndex(address vToken) internal { + VenusMarketState storage supplyState = venusSupplyState[vToken]; + uint256 supplySpeed = venusSupplySpeeds[vToken]; + uint32 blockNumber = getBlockNumberAsUint32(); + + uint256 deltaBlocks = sub_(blockNumber, supplyState.block); + if (deltaBlocks != 0 && supplySpeed != 0) { + uint256 supplyTokens = VToken(vToken).totalSupply(); + uint256 accruedVenus = mul_(deltaBlocks, supplySpeed); + Double memory ratio = supplyTokens != 0 ? fraction(accruedVenus, supplyTokens) : Double({ mantissa: 0 }); + supplyState.index = safe224(add_(Double({ mantissa: supplyState.index }), ratio).mantissa, "224"); + supplyState.block = blockNumber; + } else if (deltaBlocks != 0) { + supplyState.block = blockNumber; + } + } + + /** + * @notice Calculate XVS accrued by a supplier and possibly transfer it to them + * @param vToken The market in which the supplier is interacting + * @param supplier The address of the supplier to distribute XVS to + */ + function distributeSupplierVenus(address vToken, address supplier) internal { + if (address(vaiVaultAddress) != address(0)) { + releaseToVault(); + } + uint256 supplyIndex = venusSupplyState[vToken].index; + uint256 supplierIndex = venusSupplierIndex[vToken][supplier]; + // Update supplier's index to the current index since we are distributing accrued XVS + venusSupplierIndex[vToken][supplier] = supplyIndex; + if (supplierIndex == 0 && supplyIndex >= venusInitialIndex) { + // Covers the case where users supplied tokens before the market's supply state index was set. + // Rewards the user with XVS accrued from the start of when supplier rewards were first + // set for the market. + supplierIndex = venusInitialIndex; + } + // Calculate change in the cumulative sum of the XVS per vToken accrued + Double memory deltaIndex = Double({ mantissa: sub_(supplyIndex, supplierIndex) }); + // Multiply of supplierTokens and supplierDelta + uint256 supplierDelta = mul_(VToken(vToken).balanceOf(supplier), deltaIndex); + // Addition of supplierAccrued and supplierDelta + venusAccrued[supplier] = add_(venusAccrued[supplier], supplierDelta); + emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex); + } + + /** + * @notice Calculate XVS accrued by a borrower and possibly transfer it to them + * @dev Borrowers will not begin to accrue until after the first interaction with the protocol + * @param vToken The market in which the borrower is interacting + * @param borrower The address of the borrower to distribute XVS to + */ + function distributeBorrowerVenus(address vToken, address borrower, Exp memory marketBorrowIndex) internal { + if (address(vaiVaultAddress) != address(0)) { + releaseToVault(); + } + uint256 borrowIndex = venusBorrowState[vToken].index; + uint256 borrowerIndex = venusBorrowerIndex[vToken][borrower]; + // Update borrowers's index to the current index since we are distributing accrued XVS + venusBorrowerIndex[vToken][borrower] = borrowIndex; + if (borrowerIndex == 0 && borrowIndex >= venusInitialIndex) { + // Covers the case where users borrowed tokens before the market's borrow state index was set. + // Rewards the user with XVS accrued from the start of when borrower rewards were first + // set for the market. + borrowerIndex = venusInitialIndex; + } + // Calculate change in the cumulative sum of the XVS per borrowed unit accrued + Double memory deltaIndex = Double({ mantissa: sub_(borrowIndex, borrowerIndex) }); + uint256 borrowerDelta = mul_(div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex), deltaIndex); + venusAccrued[borrower] = add_(venusAccrued[borrower], borrowerDelta); + emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex); + } +} diff --git a/contracts/Comptroller/Diamond/interfaces/IDiamondCut.sol b/contracts/Comptroller/Diamond/interfaces/IDiamondCut.sol new file mode 100644 index 000000000..75b06563e --- /dev/null +++ b/contracts/Comptroller/Diamond/interfaces/IDiamondCut.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; +pragma experimental ABIEncoderV2; + +interface IDiamondCut { + enum FacetCutAction { + Add, + Replace, + Remove + } + // Add=0, Replace=1, Remove=2 + + struct FacetCut { + address facetAddress; + FacetCutAction action; + bytes4[] functionSelectors; + } + + /// @notice Add/replace/remove any number of functions and optionally execute + /// a function with delegatecall + /// @param _diamondCut Contains the facet addresses and function selectors + function diamondCut(FacetCut[] calldata _diamondCut) external; +} diff --git a/contracts/Comptroller/Diamond/interfaces/IMarketFacet.sol b/contracts/Comptroller/Diamond/interfaces/IMarketFacet.sol new file mode 100644 index 000000000..684d76f21 --- /dev/null +++ b/contracts/Comptroller/Diamond/interfaces/IMarketFacet.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { VToken } from "../../../Tokens/VTokens/VToken.sol"; + +interface IMarketFacet { + function isComptroller() external pure returns (bool); + + function liquidateCalculateSeizeTokens( + address vTokenBorrowed, + address vTokenCollateral, + uint256 actualRepayAmount + ) external view returns (uint256, uint256); + + function liquidateVAICalculateSeizeTokens( + address vTokenCollateral, + uint256 actualRepayAmount + ) external view returns (uint256, uint256); + + function checkMembership(address account, VToken vToken) external view returns (bool); + + function enterMarkets(address[] calldata vTokens) external returns (uint256[] memory); + + function exitMarket(address vToken) external returns (uint256); + + function _supportMarket(VToken vToken) external returns (uint256); + + function getAssetsIn(address account) external view returns (VToken[] memory); + + function getAllMarkets() external view returns (VToken[] memory); + + function updateDelegate(address delegate, bool allowBorrows) external; +} diff --git a/contracts/Comptroller/Diamond/interfaces/IPolicyFacet.sol b/contracts/Comptroller/Diamond/interfaces/IPolicyFacet.sol new file mode 100644 index 000000000..728ba5562 --- /dev/null +++ b/contracts/Comptroller/Diamond/interfaces/IPolicyFacet.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { VToken } from "../../../Tokens/VTokens/VToken.sol"; + +interface IPolicyFacet { + function mintAllowed(address vToken, address minter, uint256 mintAmount) external returns (uint256); + + function mintVerify(address vToken, address minter, uint256 mintAmount, uint256 mintTokens) external; + + function redeemAllowed(address vToken, address redeemer, uint256 redeemTokens) external returns (uint256); + + function redeemVerify(address vToken, address redeemer, uint256 redeemAmount, uint256 redeemTokens) external pure; + + function borrowAllowed(address vToken, address borrower, uint256 borrowAmount) external returns (uint256); + + function borrowVerify(address vToken, address borrower, uint256 borrowAmount) external; + + function repayBorrowAllowed( + address vToken, + address payer, + address borrower, + uint256 repayAmount + ) external returns (uint256); + + function repayBorrowVerify( + address vToken, + address payer, + address borrower, + uint256 repayAmount, + uint256 borrowerIndex + ) external; + + function liquidateBorrowAllowed( + address vTokenBorrowed, + address vTokenCollateral, + address liquidator, + address borrower, + uint256 repayAmount + ) external view returns (uint256); + + function liquidateBorrowVerify( + address vTokenBorrowed, + address vTokenCollateral, + address liquidator, + address borrower, + uint256 repayAmount, + uint256 seizeTokens + ) external; + + function seizeAllowed( + address vTokenCollateral, + address vTokenBorrowed, + address liquidator, + address borrower, + uint256 seizeTokens + ) external returns (uint256); + + function seizeVerify( + address vTokenCollateral, + address vTokenBorrowed, + address liquidator, + address borrower, + uint256 seizeTokens + ) external; + + function transferAllowed( + address vToken, + address src, + address dst, + uint256 transferTokens + ) external returns (uint256); + + function transferVerify(address vToken, address src, address dst, uint256 transferTokens) external; + + function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256); + + function getHypotheticalAccountLiquidity( + address account, + address vTokenModify, + uint256 redeemTokens, + uint256 borrowAmount + ) external view returns (uint256, uint256, uint256); + + function _setVenusSpeeds( + VToken[] calldata vTokens, + uint256[] calldata supplySpeeds, + uint256[] calldata borrowSpeeds + ) external; +} diff --git a/contracts/Comptroller/Diamond/interfaces/IRewardFacet.sol b/contracts/Comptroller/Diamond/interfaces/IRewardFacet.sol new file mode 100644 index 000000000..db2dbcc00 --- /dev/null +++ b/contracts/Comptroller/Diamond/interfaces/IRewardFacet.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { VToken } from "../../../Tokens/VTokens/VToken.sol"; +import { ComptrollerV13Storage } from "../../ComptrollerStorage.sol"; + +interface IRewardFacet { + function claimVenus(address holder) external; + + function claimVenus(address holder, VToken[] calldata vTokens) external; + + function claimVenus(address[] calldata holders, VToken[] calldata vTokens, bool borrowers, bool suppliers) external; + + function claimVenusAsCollateral(address holder) external; + + function _grantXVS(address recipient, uint256 amount) external; + + function getXVSAddress() external pure returns (address); + + function getXVSVTokenAddress() external pure returns (address); + + function actionPaused(address market, ComptrollerV13Storage.Action action) external view returns (bool); + + function claimVenus( + address[] calldata holders, + VToken[] calldata vTokens, + bool borrowers, + bool suppliers, + bool collateral + ) external; +} diff --git a/contracts/Comptroller/Diamond/interfaces/ISetterFacet.sol b/contracts/Comptroller/Diamond/interfaces/ISetterFacet.sol new file mode 100644 index 000000000..abb5ad0f4 --- /dev/null +++ b/contracts/Comptroller/Diamond/interfaces/ISetterFacet.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity 0.5.16; + +import { PriceOracle } from "../../../Oracle/PriceOracle.sol"; +import { VToken } from "../../../Tokens/VTokens/VToken.sol"; +import { ComptrollerV13Storage } from "../../ComptrollerStorage.sol"; +import { VAIControllerInterface } from "../../../Tokens/VAI/VAIController.sol"; +import { ComptrollerLensInterface } from "../../../Comptroller/ComptrollerLensInterface.sol"; + +interface ISetterFacet { + function _setPriceOracle(PriceOracle newOracle) external returns (uint256); + + function _setCloseFactor(uint256 newCloseFactorMantissa) external returns (uint256); + + function _setAccessControl(address newAccessControlAddress) external returns (uint256); + + function _setCollateralFactor(VToken vToken, uint256 newCollateralFactorMantissa) external returns (uint256); + + function _setLiquidationIncentive(uint256 newLiquidationIncentiveMantissa) external returns (uint256); + + function _setLiquidatorContract(address newLiquidatorContract_) external; + + function _setPauseGuardian(address newPauseGuardian) external returns (uint256); + + function _setMarketBorrowCaps(VToken[] calldata vTokens, uint256[] calldata newBorrowCaps) external; + + function _setMarketSupplyCaps(VToken[] calldata vTokens, uint256[] calldata newSupplyCaps) external; + + function _setProtocolPaused(bool state) external returns (bool); + + function _setActionsPaused( + address[] calldata markets, + ComptrollerV13Storage.Action[] calldata actions, + bool paused + ) external; + + function _setVAIController(VAIControllerInterface vaiController_) external returns (uint256); + + function _setVAIMintRate(uint256 newVAIMintRate) external returns (uint256); + + function setMintedVAIOf(address owner, uint256 amount) external returns (uint256); + + function _setTreasuryData( + address newTreasuryGuardian, + address newTreasuryAddress, + uint256 newTreasuryPercent + ) external returns (uint256); + + function _setComptrollerLens(ComptrollerLensInterface comptrollerLens_) external returns (uint256); + + function _setVenusVAIVaultRate(uint256 venusVAIVaultRate_) external; + + function _setVAIVaultInfo(address vault_, uint256 releaseStartBlock_, uint256 minReleaseAmount_) external; + + function _setForcedLiquidation(address vToken, bool enable) external; +} diff --git a/contracts/Comptroller/UpdatedComptroller.sol b/contracts/Comptroller/UpdatedComptroller.sol deleted file mode 100644 index 68c1ff938..000000000 --- a/contracts/Comptroller/UpdatedComptroller.sol +++ /dev/null @@ -1,1517 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Oracle/PriceOracle.sol"; -import "../Tokens/VTokens/VToken.sol"; -import "../Utils/ErrorReporter.sol"; -import "../Tokens/XVS/XVS.sol"; -import "../Tokens/VAI/VAI.sol"; -import "../Governance/IAccessControlManager.sol"; -import "./ComptrollerLensInterface.sol"; -import "./UpdatedComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Venus's Comptroller Contract - * @dev Name of this comptorller is updated to UpdatedComptroller as tests were failing - * due to the two contracts name by Comptorller. At the time of deployment of this - * contract to mainnet it should be nammed as Comptorller - * @author Venus - */ -// -contract UpdatedComptroller is - ComptrollerV10Storage, - UpdatedComptrollerInterfaceG2, - ComptrollerErrorReporter, - ExponentialNoError -{ - /// @notice Emitted when an admin supports a market - event MarketListed(VToken vToken); - - /// @notice Emitted when an account enters a market - event MarketEntered(VToken vToken, address account); - - /// @notice Emitted when an account exits a market - event MarketExited(VToken vToken, address account); - - /// @notice Emitted when close factor is changed by admin - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /// @notice Emitted when a collateral factor is changed by admin - event NewCollateralFactor(VToken vToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /// @notice Emitted when liquidation incentive is changed by admin - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /// @notice Emitted when price oracle is changed - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /// @notice Emitted when VAI Vault info is changed - event NewVAIVaultInfo(address vault_, uint releaseStartBlock_, uint releaseInterval_); - - /// @notice Emitted when pause guardian is changed - event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); - - /// @notice Emitted when an action is paused on a market - event ActionPausedMarket(VToken indexed vToken, Action indexed action, bool pauseState); - - /// @notice Emitted when Venus VAI Vault rate is changed - event NewVenusVAIVaultRate(uint oldVenusVAIVaultRate, uint newVenusVAIVaultRate); - - /// @notice Emitted when a new borrow-side XVS speed is calculated for a market - event VenusBorrowSpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when a new supply-side XVS speed is calculated for a market - event VenusSupplySpeedUpdated(VToken indexed vToken, uint newSpeed); - - /// @notice Emitted when XVS is distributed to a supplier - event DistributedSupplierVenus( - VToken indexed vToken, - address indexed supplier, - uint venusDelta, - uint venusSupplyIndex - ); - - /// @notice Emitted when XVS is distributed to a borrower - event DistributedBorrowerVenus( - VToken indexed vToken, - address indexed borrower, - uint venusDelta, - uint venusBorrowIndex - ); - - /// @notice Emitted when XVS is distributed to VAI Vault - event DistributedVAIVaultVenus(uint amount); - - /// @notice Emitted when VAIController is changed - event NewVAIController(VAIControllerInterface oldVAIController, VAIControllerInterface newVAIController); - - /// @notice Emitted when VAI mint rate is changed by admin - event NewVAIMintRate(uint oldVAIMintRate, uint newVAIMintRate); - - /// @notice Emitted when protocol state is changed by admin - event ActionProtocolPaused(bool state); - - /// @notice Emitted when borrow cap for a vToken is changed - event NewBorrowCap(VToken indexed vToken, uint newBorrowCap); - - /// @notice Emitted when treasury guardian is changed - event NewTreasuryGuardian(address oldTreasuryGuardian, address newTreasuryGuardian); - - /// @notice Emitted when treasury address is changed - event NewTreasuryAddress(address oldTreasuryAddress, address newTreasuryAddress); - - /// @notice Emitted when treasury percent is changed - event NewTreasuryPercent(uint oldTreasuryPercent, uint newTreasuryPercent); - - // @notice Emitted when liquidator adress is changed - event NewLiquidatorContract(address oldLiquidatorContract, address newLiquidatorContract); - - /// @notice Emitted when Venus is granted by admin - event VenusGranted(address recipient, uint amount); - - /// @notice Emitted whe ComptrollerLens address is changed - event NewComptrollerLens(address oldComptrollerLens, address newComptrollerLens); - - /// @notice Emitted when supply cap for a vToken is changed - event NewSupplyCap(VToken indexed vToken, uint newSupplyCap); - - /// @notice Emitted when access control address is changed by admin - event NewAccessControl(address oldAccessControlAddress, address newAccessControlAddress); - - /// @notice The initial Venus index for a market - uint224 public constant venusInitialIndex = 1e36; - - // closeFactorMantissa must be strictly greater than this value - uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05 - - // closeFactorMantissa must not exceed this value - uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9 - - constructor() public { - admin = msg.sender; - } - - /// @notice Reverts if the protocol is paused - function checkProtocolPauseState() private view { - require(!protocolPaused, "protocol is paused"); - } - - /// @notice Reverts if a certain action is paused on a market - function checkActionPauseState(address market, Action action) private view { - require(!actionPaused(market, action), "action is paused"); - } - - /// @notice Reverts if the caller is not admin - function ensureAdmin() private view { - require(msg.sender == admin, "only admin can"); - } - - /// @notice Checks the passed address is nonzero - function ensureNonzeroAddress(address someone) private pure { - require(someone != address(0), "can't be zero address"); - } - - /// @notice Reverts if the market is not listed - function ensureListed(Market storage market) private view { - require(market.isListed, "market not listed"); - } - - /// @notice Reverts if the caller is neither admin nor the passed address - function ensureAdminOr(address privilegedAddress) private view { - require(msg.sender == admin || msg.sender == privilegedAddress, "access denied"); - } - - function ensureAllowed(string memory functionSig) private view { - require(IAccessControlManager(accessControl).isAllowedToCall(msg.sender, functionSig), "access denied"); - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (VToken[] memory) { - return accountAssets[account]; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param vToken The vToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, VToken vToken) external view returns (bool) { - return markets[address(vToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param vTokens The list of addresses of the vToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory) { - uint len = vTokens.length; - - uint[] memory results = new uint[](len); - for (uint i; i < len; ++i) { - results[i] = uint(addToMarketInternal(VToken(vTokens[i]), msg.sender)); - } - - return results; - } - - /** - * @notice Add the market to the borrower's "assets in" for liquidity calculations - * @param vToken The market to enter - * @param borrower The address of the account to modify - * @return Success indicator for whether the market was entered - */ - function addToMarketInternal(VToken vToken, address borrower) internal returns (Error) { - checkActionPauseState(address(vToken), Action.ENTER_MARKET); - - Market storage marketToJoin = markets[address(vToken)]; - ensureListed(marketToJoin); - - if (marketToJoin.accountMembership[borrower]) { - // already joined - return Error.NO_ERROR; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[borrower] = true; - accountAssets[borrower].push(vToken); - - emit MarketEntered(vToken, borrower); - - return Error.NO_ERROR; - } - - /** - * @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 vTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address vTokenAddress) external returns (uint) { - checkActionPauseState(vTokenAddress, Action.EXIT_MARKET); - - VToken vToken = VToken(vTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the vToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = vToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(vTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(vToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set vToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete vToken from the account’s list of assets */ - // In order to delete vToken, copy last item in list to location of item to be removed, reduce length by 1 - VToken[] storage userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint i; - for (; i < len; ++i) { - if (userAssetList[i] == vToken) { - userAssetList[i] = userAssetList[len - 1]; - userAssetList.length--; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(i < len); - - emit MarketExited(vToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param vToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.MINT); - ensureListed(markets[vToken]); - - uint256 supplyCap = supplyCaps[vToken]; - require(supplyCap != 0, "market supply cap is 0"); - - uint256 vTokenSupply = VToken(vToken).totalSupply(); - Exp memory exchangeRate = Exp({ mantissa: VToken(vToken).exchangeRateStored() }); - uint256 nextTotalSupply = mul_ScalarTruncateAddUInt(exchangeRate, vTokenSupply, mintAmount); - require(nextTotalSupply <= supplyCap, "market supply cap reached"); - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, minter); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param vToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of vTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint) { - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.REDEEM); - - uint allowed = redeemAllowedInternal(vToken, redeemer, redeemTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, redeemer); - - return uint(Error.NO_ERROR); - } - - function redeemAllowedInternal(address vToken, address redeemer, uint redeemTokens) internal view returns (uint) { - ensureListed(markets[vToken]); - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[vToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - redeemer, - VToken(vToken), - redeemTokens, - 0 - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param vToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.BORROW); - - ensureListed(markets[vToken]); - - if (!markets[vToken].accountMembership[borrower]) { - // only vTokens may call borrowAllowed if borrower not in market - require(msg.sender == vToken, "sender must be vToken"); - - // attempt to add borrower to the market - Error err = addToMarketInternal(VToken(vToken), borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - } - - if (oracle.getUnderlyingPrice(VToken(vToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - uint borrowCap = borrowCaps[vToken]; - // Borrow cap of 0 corresponds to unlimited borrowing - if (borrowCap != 0) { - uint nextTotalBorrows = add_(VToken(vToken).totalBorrows(), borrowAmount); - require(nextTotalBorrows < borrowCap, "market borrow cap reached"); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal( - borrower, - VToken(vToken), - 0, - borrowAmount - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall != 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param vToken The market to verify the repay against - * @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 account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address vToken, - address payer, // solhint-disable-line no-unused-vars - address borrower, - uint repayAmount // solhint-disable-line no-unused-vars - ) external returns (uint) { - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.REPAY); - ensureListed(markets[vToken]); - - // Keep the flywheel moving - Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() }); - updateVenusBorrowIndex(vToken, borrowIndex); - distributeBorrowerVenus(vToken, borrower, borrowIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param vTokenBorrowed Asset which was borrowed by the borrower - * @param vTokenCollateral 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 repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external returns (uint) { - checkProtocolPauseState(); - - // if we want to pause liquidating to vTokenCollateral, we should pause seizing - checkActionPauseState(vTokenBorrowed, Action.LIQUIDATE); - - if (liquidatorContract != address(0) && liquidator != liquidatorContract) { - return uint(Error.UNAUTHORIZED); - } - - ensureListed(markets[vTokenCollateral]); - if (address(vTokenBorrowed) != address(vaiController)) { - ensureListed(markets[vTokenBorrowed]); - } - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, VToken(0), 0, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - /* The liquidator may not repay more than what is allowed by the closeFactor */ - uint borrowBalance; - if (address(vTokenBorrowed) != address(vaiController)) { - borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower); - } else { - borrowBalance = vaiController.getVAIRepayAmount(borrower); - } - // maxClose = multipy of closeFactorMantissa and borrowBalance - if (repayAmount > mul_ScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance)) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param vTokenCollateral Asset which was used as collateral and will be seized - * @param vTokenBorrowed 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 vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens // solhint-disable-line no-unused-vars - ) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vTokenCollateral, Action.SEIZE); - - // We've added VAIController as a borrowed token list check for seize - ensureListed(markets[vTokenCollateral]); - if (address(vTokenBorrowed) != address(vaiController)) { - ensureListed(markets[vTokenBorrowed]); - } - - if (VToken(vTokenCollateral).comptroller() != VToken(vTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vTokenCollateral); - distributeSupplierVenus(vTokenCollateral, borrower); - distributeSupplierVenus(vTokenCollateral, liquidator); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param vToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of vTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint) { - // Pausing is a very serious situation - we revert to sound the alarms - checkProtocolPauseState(); - checkActionPauseState(vToken, Action.TRANSFER); - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - uint allowed = redeemAllowedInternal(vToken, src, transferTokens); - if (allowed != uint(Error.NO_ERROR)) { - return allowed; - } - - // Keep the flywheel moving - updateVenusSupplyIndex(vToken); - distributeSupplierVenus(vToken, src); - distributeSupplierVenus(vToken, dst); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) external view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, VToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @return (possible error code (semi-opaque), - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidity( - address account, - address vTokenModify, - uint redeemTokens, - uint borrowAmount - ) external view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal( - account, - VToken(vTokenModify), - redeemTokens, - borrowAmount - ); - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param vTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidityInternal( - address account, - VToken vTokenModify, - uint redeemTokens, - uint borrowAmount - ) internal view returns (Error, uint, uint) { - (uint err, uint liquidity, uint shortfall) = comptrollerLens.getHypotheticalAccountLiquidity( - address(this), - account, - vTokenModify, - redeemTokens, - borrowAmount - ); - return (Error(err), liquidity, shortfall); - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenBorrowed The address of the borrowed vToken - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - (uint err, uint seizeTokens) = comptrollerLens.liquidateCalculateSeizeTokens( - address(this), - vTokenBorrowed, - vTokenCollateral, - actualRepayAmount - ); - return (err, seizeTokens); - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in vToken.liquidateBorrowFresh) - * @param vTokenCollateral The address of the collateral vToken - * @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens - * @return (errorCode, number of vTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateVAICalculateSeizeTokens( - address vTokenCollateral, - uint actualRepayAmount - ) external view returns (uint, uint) { - (uint err, uint seizeTokens) = comptrollerLens.liquidateVAICalculateSeizeTokens( - address(this), - vTokenCollateral, - actualRepayAmount - ); - return (err, seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) external returns (uint) { - // Check caller is admin - ensureAdmin(); - ensureNonzeroAddress(address(newOracle)); - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise will revert - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) { - // Check caller is admin - ensureAdmin(); - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the address of the access control of this contract - * @dev Admin function to set the access control address - * @param newAccessControlAddress New address for the access control - * @return uint 0=success, otherwise will revert - */ - function _setAccessControl(address newAccessControlAddress) external returns (uint) { - // Check caller is admin - ensureAdmin(); - ensureNonzeroAddress(newAccessControlAddress); - - address oldAccessControlAddress = accessControl; - accessControl = newAccessControlAddress; - emit NewAccessControl(oldAccessControlAddress, accessControl); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Restricted function to set per-market collateralFactor - * @param vToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(VToken vToken, uint newCollateralFactorMantissa) external returns (uint) { - // Check caller is allowed by access control manager - ensureAllowed("_setCollateralFactor(address,uint256)"); - ensureNonzeroAddress(address(vToken)); - - // Verify market is listed - Market storage market = markets[address(vToken)]; - ensureListed(market); - - Exp memory newCollateralFactorExp = Exp({ mantissa: newCollateralFactorMantissa }); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({ mantissa: collateralFactorMaxMantissa }); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(vToken) == 0) { - return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - ensureAllowed("_setLiquidationIncentive(uint256)"); - - require(newLiquidationIncentiveMantissa >= 1e18, "incentive must be over 1e18"); - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - function _setLiquidatorContract(address newLiquidatorContract_) external { - // Check caller is admin - ensureAdmin(); - address oldLiquidatorContract = liquidatorContract; - liquidatorContract = newLiquidatorContract_; - emit NewLiquidatorContract(oldLiquidatorContract, newLiquidatorContract_); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param vToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(VToken vToken) external returns (uint) { - ensureAllowed("_supportMarket(address)"); - - if (markets[address(vToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - vToken.isVToken(); // Sanity check to make sure its really a VToken - - // Note that isVenus is not in active use anymore - markets[address(vToken)] = Market({ isListed: true, isVenus: false, collateralFactorMantissa: 0 }); - - _addMarketInternal(vToken); - _initializeMarket(address(vToken)); - - emit MarketListed(vToken); - - return uint(Error.NO_ERROR); - } - - function _addMarketInternal(VToken vToken) internal { - for (uint i; i < allMarkets.length; ++i) { - require(allMarkets[i] != vToken, "market already added"); - } - allMarkets.push(vToken); - } - - function _initializeMarket(address vToken) internal { - uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits"); - - VenusMarketState storage supplyState = venusSupplyState[vToken]; - VenusMarketState storage borrowState = venusBorrowState[vToken]; - - /* - * Update market state indices - */ - if (supplyState.index == 0) { - // Initialize supply state index with default value - supplyState.index = venusInitialIndex; - } - - if (borrowState.index == 0) { - // Initialize borrow state index with default value - borrowState.index = venusInitialIndex; - } - - /* - * Update market state block numbers - */ - supplyState.block = borrowState.block = blockNumber; - } - - /** - * @notice Admin function to change the Pause Guardian - * @param newPauseGuardian The address of the new Pause Guardian - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _setPauseGuardian(address newPauseGuardian) external returns (uint) { - ensureAdmin(); - ensureNonzeroAddress(newPauseGuardian); - - // Save current value for inclusion in log - address oldPauseGuardian = pauseGuardian; - - // Store pauseGuardian with value newPauseGuardian - pauseGuardian = newPauseGuardian; - - // Emit NewPauseGuardian(OldPauseGuardian, NewPauseGuardian) - emit NewPauseGuardian(oldPauseGuardian, newPauseGuardian); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Set the given borrow caps for the given vToken markets. Borrowing that brings total borrows to or above borrow cap will revert. - * @dev Access is controled by ACM. A borrow cap of 0 corresponds to unlimited borrowing. - * @param vTokens The addresses of the markets (tokens) to change the borrow caps for - * @param newBorrowCaps The new borrow cap values in underlying to be set. A value of 0 corresponds to unlimited borrowing. - */ - function _setMarketBorrowCaps(VToken[] calldata vTokens, uint[] calldata newBorrowCaps) external { - ensureAllowed("_setMarketBorrowCaps(address[],uint256[])"); - - uint numMarkets = vTokens.length; - uint numBorrowCaps = newBorrowCaps.length; - - require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input"); - - for (uint i; i < numMarkets; ++i) { - borrowCaps[address(vTokens[i])] = newBorrowCaps[i]; - emit NewBorrowCap(vTokens[i], newBorrowCaps[i]); - } - } - - /** - * @notice Set the given supply caps for the given vToken markets. Supply that brings total Supply to or above supply cap will revert. - * @dev Admin function to set the supply caps. A supply cap of 0 corresponds to Minting NotAllowed. - * @param vTokens The addresses of the markets (tokens) to change the supply caps for - * @param newSupplyCaps The new supply cap values in underlying to be set. A value of 0 corresponds to Minting NotAllowed. - */ - function _setMarketSupplyCaps(VToken[] calldata vTokens, uint256[] calldata newSupplyCaps) external { - ensureAllowed("_setMarketSupplyCaps(address[],uint256[])"); - - uint numMarkets = vTokens.length; - uint numSupplyCaps = newSupplyCaps.length; - - require(numMarkets != 0 && numMarkets == numSupplyCaps, "invalid input"); - - for (uint i; i < numMarkets; ++i) { - supplyCaps[address(vTokens[i])] = newSupplyCaps[i]; - emit NewSupplyCap(vTokens[i], newSupplyCaps[i]); - } - } - - /** - * @notice Set whole protocol pause/unpause state - */ - function _setProtocolPaused(bool state) external returns (bool) { - ensureAllowed("_setProtocolPaused(bool)"); - - protocolPaused = state; - emit ActionProtocolPaused(state); - return state; - } - - /** - * @notice Pause/unpause certain actions - * @param markets Markets to pause/unpause the actions on - * @param actions List of action ids to pause/unpause - * @param paused The new paused state (true=paused, false=unpaused) - */ - function _setActionsPaused(address[] calldata markets, Action[] calldata actions, bool paused) external { - ensureAllowed("_setActionsPaused(address[],uint256[],bool)"); - - uint256 numMarkets = markets.length; - uint256 numActions = actions.length; - for (uint marketIdx; marketIdx < numMarkets; ++marketIdx) { - for (uint actionIdx; actionIdx < numActions; ++actionIdx) { - setActionPausedInternal(markets[marketIdx], actions[actionIdx], paused); - } - } - } - - /** - * @dev Pause/unpause an action on a market - * @param market Market to pause/unpause the action on - * @param action Action id to pause/unpause - * @param paused The new paused state (true=paused, false=unpaused) - */ - function setActionPausedInternal(address market, Action action, bool paused) internal { - ensureListed(markets[market]); - _actionPaused[market][uint(action)] = paused; - emit ActionPausedMarket(VToken(market), action, paused); - } - - /** - * @notice Sets a new VAI controller - * @dev Admin function to set a new VAI controller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setVAIController(VAIControllerInterface vaiController_) external returns (uint) { - // Check caller is admin - ensureAdmin(); - ensureNonzeroAddress(address(vaiController_)); - - VAIControllerInterface oldVaiController = vaiController; - vaiController = vaiController_; - emit NewVAIController(oldVaiController, vaiController_); - - return uint(Error.NO_ERROR); - } - - function _setVAIMintRate(uint newVAIMintRate) external returns (uint) { - // Check caller is admin - ensureAdmin(); - uint oldVAIMintRate = vaiMintRate; - vaiMintRate = newVAIMintRate; - emit NewVAIMintRate(oldVAIMintRate, newVAIMintRate); - - return uint(Error.NO_ERROR); - } - - function _setTreasuryData( - address newTreasuryGuardian, - address newTreasuryAddress, - uint newTreasuryPercent - ) external returns (uint) { - // Check caller is admin - ensureAdminOr(treasuryGuardian); - - require(newTreasuryPercent < 1e18, "treasury percent cap overflow"); - ensureNonzeroAddress(newTreasuryGuardian); - ensureNonzeroAddress(newTreasuryAddress); - - address oldTreasuryGuardian = treasuryGuardian; - address oldTreasuryAddress = treasuryAddress; - uint oldTreasuryPercent = treasuryPercent; - - treasuryGuardian = newTreasuryGuardian; - treasuryAddress = newTreasuryAddress; - treasuryPercent = newTreasuryPercent; - - emit NewTreasuryGuardian(oldTreasuryGuardian, newTreasuryGuardian); - emit NewTreasuryAddress(oldTreasuryAddress, newTreasuryAddress); - emit NewTreasuryPercent(oldTreasuryPercent, newTreasuryPercent); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller) external { - require(msg.sender == unitroller.admin(), "only unitroller admin can"); - require(unitroller._acceptImplementation() == 0, "not authorized"); - - // TODO: Remove this post upgrade - // Should have to change UpdatedComptroller to Comptroller - UpdatedComptroller(address(unitroller))._upgradeSplitVenusRewards(); - } - - function _upgradeSplitVenusRewards() external { - require(msg.sender == comptrollerImplementation, "only brains can become itself"); - - uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits"); - - // venusSpeeds -> venusBorrowSpeeds & venusSupplySpeeds t - for (uint256 i; i < allMarkets.length; ++i) { - venusBorrowSpeeds[address(allMarkets[i])] = venusSupplySpeeds[address(allMarkets[i])] = venusSpeeds[ - address(allMarkets[i]) - ]; - delete venusSpeeds[address(allMarkets[i])]; - - /* - * Ensure supply and borrow state indices are all set. If not set, update to default value - */ - VenusMarketState storage supplyState = venusSupplyState[address(allMarkets[i])]; - VenusMarketState storage borrowState = venusBorrowState[address(allMarkets[i])]; - - if (supplyState.index == 0) { - // Initialize supply state index with default value - supplyState.index = venusInitialIndex; - supplyState.block = blockNumber; - } - - if (borrowState.index == 0) { - // Initialize borrow state index with default value - borrowState.index = venusInitialIndex; - borrowState.block = blockNumber; - } - } - } - - /*** Venus Distribution ***/ - - function setVenusSpeedInternal(VToken vToken, uint supplySpeed, uint borrowSpeed) internal { - ensureListed(markets[address(vToken)]); - - if (venusSupplySpeeds[address(vToken)] != supplySpeed) { - // Supply speed updated so let's update supply state to ensure that - // 1. XVS accrued properly for the old speed, and - // 2. XVS accrued at the new speed starts after this block. - - updateVenusSupplyIndex(address(vToken)); - // Update speed and emit event - venusSupplySpeeds[address(vToken)] = supplySpeed; - emit VenusSupplySpeedUpdated(vToken, supplySpeed); - } - - if (venusBorrowSpeeds[address(vToken)] != borrowSpeed) { - // Borrow speed updated so let's update borrow state to ensure that - // 1. XVS accrued properly for the old speed, and - // 2. XVS accrued at the new speed starts after this block. - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - - // Update speed and emit event - venusBorrowSpeeds[address(vToken)] = borrowSpeed; - emit VenusBorrowSpeedUpdated(vToken, borrowSpeed); - } - } - - /** - * @dev Set ComptrollerLens contract address - */ - function _setComptrollerLens(ComptrollerLensInterface comptrollerLens_) external returns (uint) { - ensureAdmin(); - ensureNonzeroAddress(address(comptrollerLens_)); - address oldComptrollerLens = address(comptrollerLens); - comptrollerLens = comptrollerLens_; - emit NewComptrollerLens(oldComptrollerLens, address(comptrollerLens)); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Accrue XVS to the market by updating the supply index - * @param vToken The market whose supply index to update - */ - function updateVenusSupplyIndex(address vToken) internal { - VenusMarketState storage supplyState = venusSupplyState[vToken]; - uint supplySpeed = venusSupplySpeeds[vToken]; - uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits"); - uint deltaBlocks = sub_(uint(blockNumber), uint(supplyState.block)); - if (deltaBlocks > 0 && supplySpeed > 0) { - uint supplyTokens = VToken(vToken).totalSupply(); - uint venusAccrued = mul_(deltaBlocks, supplySpeed); - Double memory ratio = supplyTokens > 0 ? fraction(venusAccrued, supplyTokens) : Double({ mantissa: 0 }); - supplyState.index = safe224( - add_(Double({ mantissa: supplyState.index }), ratio).mantissa, - "new index exceeds 224 bits" - ); - supplyState.block = blockNumber; - } else if (deltaBlocks > 0) { - supplyState.block = blockNumber; - } - } - - /** - * @notice Accrue XVS to the market by updating the borrow index - * @param vToken The market whose borrow index to update - */ - function updateVenusBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal { - VenusMarketState storage borrowState = venusBorrowState[vToken]; - uint borrowSpeed = venusBorrowSpeeds[vToken]; - uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits"); - uint deltaBlocks = sub_(uint(blockNumber), uint(borrowState.block)); - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex); - uint venusAccrued = mul_(deltaBlocks, borrowSpeed); - Double memory ratio = borrowAmount > 0 ? fraction(venusAccrued, borrowAmount) : Double({ mantissa: 0 }); - borrowState.index = safe224( - add_(Double({ mantissa: borrowState.index }), ratio).mantissa, - "new index exceeds 224 bits" - ); - borrowState.block = blockNumber; - } else if (deltaBlocks > 0) { - borrowState.block = blockNumber; - } - } - - /** - * @notice Calculate XVS accrued by a supplier and possibly transfer it to them - * @param vToken The market in which the supplier is interacting - * @param supplier The address of the supplier to distribute XVS to - */ - function distributeSupplierVenus(address vToken, address supplier) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - uint supplyIndex = venusSupplyState[vToken].index; - uint supplierIndex = venusSupplierIndex[vToken][supplier]; - - // Update supplier's index to the current index since we are distributing accrued XVS - venusSupplierIndex[vToken][supplier] = supplyIndex; - - if (supplierIndex == 0 && supplyIndex >= venusInitialIndex) { - // Covers the case where users supplied tokens before the market's supply state index was set. - // Rewards the user with XVS accrued from the start of when supplier rewards were first - // set for the market. - supplierIndex = venusInitialIndex; - } - - // Calculate change in the cumulative sum of the XVS per vToken accrued - Double memory deltaIndex = Double({ mantissa: sub_(supplyIndex, supplierIndex) }); - - // Multiply of supplierTokens and supplierDelta - uint supplierDelta = mul_(VToken(vToken).balanceOf(supplier), deltaIndex); - - // Addition of supplierAccrued and supplierDelta - venusAccrued[supplier] = add_(venusAccrued[supplier], supplierDelta); - - emit DistributedSupplierVenus(VToken(vToken), supplier, supplierDelta, supplyIndex); - } - - /** - * @notice Calculate XVS accrued by a borrower and possibly transfer it to them - * @dev Borrowers will not begin to accrue until after the first interaction with the protocol. - * @param vToken The market in which the borrower is interacting - * @param borrower The address of the borrower to distribute XVS to - */ - function distributeBorrowerVenus(address vToken, address borrower, Exp memory marketBorrowIndex) internal { - if (address(vaiVaultAddress) != address(0)) { - releaseToVault(); - } - - uint borrowIndex = venusBorrowState[vToken].index; - uint borrowerIndex = venusBorrowerIndex[vToken][borrower]; - - // Update borrowers's index to the current index since we are distributing accrued XVS - venusBorrowerIndex[vToken][borrower] = borrowIndex; - - if (borrowerIndex == 0 && borrowIndex >= venusInitialIndex) { - // Covers the case where users borrowed tokens before the market's borrow state index was set. - // Rewards the user with XVS accrued from the start of when borrower rewards were first - // set for the market. - borrowerIndex = venusInitialIndex; - } - - // Calculate change in the cumulative sum of the XVS per borrowed unit accrued - Double memory deltaIndex = Double({ mantissa: sub_(borrowIndex, borrowerIndex) }); - - uint borrowerDelta = mul_(div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex), deltaIndex); - - venusAccrued[borrower] = add_(venusAccrued[borrower], borrowerDelta); - - emit DistributedBorrowerVenus(VToken(vToken), borrower, borrowerDelta, borrowIndex); - } - - /** - * @notice Claim all the xvs accrued by holder in all markets and VAI - * @param holder The address to claim XVS for - */ - function claimVenus(address holder) public { - return claimVenus(holder, allMarkets); - } - - /** - * @notice Claim all the xvs accrued by holder in the specified markets - * @param holder The address to claim XVS for - * @param vTokens The list of markets to claim XVS in - */ - function claimVenus(address holder, VToken[] memory vTokens) public { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, vTokens, true, true); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - */ - function claimVenus(address[] memory holders, VToken[] memory vTokens, bool borrowers, bool suppliers) public { - claimVenus(holders, vTokens, borrowers, suppliers, false); - } - - /** - * @notice Claim all xvs accrued by the holders - * @param holders The addresses to claim XVS for - * @param vTokens The list of markets to claim XVS in - * @param borrowers Whether or not to claim XVS earned by borrowing - * @param suppliers Whether or not to claim XVS earned by supplying - * @param collateral Whether or not to use XVS earned as collateral, only takes effect when the holder has a shortfall - */ - function claimVenus( - address[] memory holders, - VToken[] memory vTokens, - bool borrowers, - bool suppliers, - bool collateral - ) public { - uint j; - uint256 holdersLength = holders.length; - for (uint i; i < vTokens.length; ++i) { - VToken vToken = vTokens[i]; - ensureListed(markets[address(vToken)]); - if (borrowers) { - Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() }); - updateVenusBorrowIndex(address(vToken), borrowIndex); - for (j = 0; j < holdersLength; ++j) { - distributeBorrowerVenus(address(vToken), holders[j], borrowIndex); - } - } - if (suppliers) { - updateVenusSupplyIndex(address(vToken)); - for (j = 0; j < holdersLength; ++j) { - distributeSupplierVenus(address(vToken), holders[j]); - } - } - } - - for (j = 0; j < holdersLength; ++j) { - address holder = holders[j]; - // If there is a positive shortfall, the XVS reward is accrued, - // but won't be granted to this holder - (, , uint shortfall) = getHypotheticalAccountLiquidityInternal(holder, VToken(0), 0, 0); - venusAccrued[holder] = grantXVSInternal(holder, venusAccrued[holder], shortfall, collateral); - } - } - - /** - * @notice Claim all the xvs accrued by holder in all markets, a shorthand for `claimVenus` with collateral set to `true` - * @param holder The address to claim XVS for - */ - function claimVenusAsCollateral(address holder) external { - address[] memory holders = new address[](1); - holders[0] = holder; - claimVenus(holders, allMarkets, true, true, true); - } - - /** - * @notice Transfer XVS to the user with user's shortfall considered - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param user The address of the user to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - * @param shortfall The shortfall of the user - * @param collateral Whether or not we will use user's venus reward as collateral to pay off the debt - * @return The amount of XVS which was NOT transferred to the user - */ - function grantXVSInternal(address user, uint amount, uint shortfall, bool collateral) internal returns (uint) { - // If the user is blacklisted, they can't get XVS rewards - require( - user != 0xEF044206Db68E40520BfA82D45419d498b4bc7Bf && - user != 0x7589dD3355DAE848FDbF75044A3495351655cB1A && - user != 0x33df7a7F6D44307E1e5F3B15975b47515e5524c0 && - user != 0x24e77E5b74B30b026E9996e4bc3329c881e24968, - "Blacklisted" - ); - - XVS xvs = XVS(getXVSAddress()); - - if (amount == 0 || amount > xvs.balanceOf(address(this))) { - return amount; - } - - if (shortfall == 0) { - xvs.transfer(user, amount); - return 0; - } - // If user's bankrupt and doesn't use pending xvs as collateral, don't grant - // anything, otherwise, we will transfer the pending xvs as collateral to - // vXVS token and mint vXVS for the user. - // - // If mintBehalf failed, don't grant any xvs - require(collateral, "bankrupt accounts can only collateralize their pending xvs rewards"); - - xvs.approve(getXVSVTokenAddress(), amount); - require( - VBep20Interface(getXVSVTokenAddress()).mintBehalf(user, amount) == uint(Error.NO_ERROR), - "mint behalf error during collateralize xvs" - ); - - // set venusAccrue[user] to 0 - return 0; - } - - /*** Venus Distribution Admin ***/ - - /** - * @notice Transfer XVS to the recipient - * @dev Note: If there is not enough XVS, we do not perform the transfer all. - * @param recipient The address of the recipient to transfer XVS to - * @param amount The amount of XVS to (possibly) transfer - */ - function _grantXVS(address recipient, uint amount) external { - ensureAdminOr(comptrollerImplementation); - uint amountLeft = grantXVSInternal(recipient, amount, 0, false); - require(amountLeft == 0, "insufficient xvs for grant"); - emit VenusGranted(recipient, amount); - } - - /** - * @notice Set the amount of XVS distributed per block to VAI Vault - * @param venusVAIVaultRate_ The amount of XVS wei per block to distribute to VAI Vault - */ - function _setVenusVAIVaultRate(uint venusVAIVaultRate_) external { - ensureAdmin(); - - uint oldVenusVAIVaultRate = venusVAIVaultRate; - venusVAIVaultRate = venusVAIVaultRate_; - emit NewVenusVAIVaultRate(oldVenusVAIVaultRate, venusVAIVaultRate_); - } - - /** - * @notice Set the VAI Vault infos - * @param vault_ The address of the VAI Vault - * @param releaseStartBlock_ The start block of release to VAI Vault - * @param minReleaseAmount_ The minimum release amount to VAI Vault - */ - function _setVAIVaultInfo(address vault_, uint256 releaseStartBlock_, uint256 minReleaseAmount_) external { - ensureAdmin(); - ensureNonzeroAddress(vault_); - - vaiVaultAddress = vault_; - releaseStartBlock = releaseStartBlock_; - minReleaseAmount = minReleaseAmount_; - emit NewVAIVaultInfo(vault_, releaseStartBlock_, minReleaseAmount_); - } - - /** - * @notice Set XVS speed for a single market - * @param vTokens The market whose XVS speed to update - * @param supplySpeeds New XVS speed for supply - * @param borrowSpeeds New XVS speed for borrow - */ - function _setVenusSpeeds( - VToken[] calldata vTokens, - uint[] calldata supplySpeeds, - uint[] calldata borrowSpeeds - ) external { - ensureAdminOr(comptrollerImplementation); - - uint numTokens = vTokens.length; - require( - numTokens == supplySpeeds.length && numTokens == borrowSpeeds.length, - "Comptroller::_setVenusSpeeds invalid input" - ); - - for (uint i; i < numTokens; ++i) { - ensureNonzeroAddress(address(vTokens[i])); - setVenusSpeedInternal(vTokens[i], supplySpeeds[i], borrowSpeeds[i]); - } - } - - /** - * @notice Return all of the markets - * @dev The automatic getter may be used to access an individual market. - * @return The list of market addresses - */ - function getAllMarkets() external view returns (VToken[] memory) { - return allMarkets; - } - - function getBlockNumber() public view returns (uint) { - return block.number; - } - - /** - * @notice Return the address of the XVS token - * @return The address of XVS - */ - function getXVSAddress() public view returns (address) { - return 0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63; - } - - /** - * @notice Return the address of the XVS vToken - * @return The address of XVS vToken - */ - function getXVSVTokenAddress() public view returns (address) { - return 0x151B1e2635A717bcDc836ECd6FbB62B674FE3E1D; - } - - /** - * @notice Checks if a certain action is paused on a market - * @param action Action id - * @param market vToken address - */ - function actionPaused(address market, Action action) public view returns (bool) { - return _actionPaused[market][uint(action)]; - } - - /*** VAI functions ***/ - - /** - * @notice Set the minted VAI amount of the `owner` - * @param owner The address of the account to set - * @param amount The amount of VAI to set to the account - * @return The number of minted VAI by `owner` - */ - function setMintedVAIOf(address owner, uint amount) external returns (uint) { - checkProtocolPauseState(); - - // Pausing is a very serious situation - we revert to sound the alarms - require(!mintVAIGuardianPaused && !repayVAIGuardianPaused, "VAI is paused"); - // Check caller is vaiController - if (msg.sender != address(vaiController)) { - return fail(Error.REJECTION, FailureInfo.SET_MINTED_VAI_REJECTION); - } - mintedVAIs[owner] = amount; - - return uint(Error.NO_ERROR); - } - - /** - * @notice Transfer XVS to VAI Vault - */ - function releaseToVault() public { - if (releaseStartBlock == 0 || getBlockNumber() < releaseStartBlock) { - return; - } - - XVS xvs = XVS(getXVSAddress()); - - uint256 xvsBalance = xvs.balanceOf(address(this)); - if (xvsBalance == 0) { - return; - } - - uint256 actualAmount; - uint256 deltaBlocks = sub_(getBlockNumber(), releaseStartBlock); - // releaseAmount = venusVAIVaultRate * deltaBlocks - uint256 _releaseAmount = mul_(venusVAIVaultRate, deltaBlocks); - - if (xvsBalance >= _releaseAmount) { - actualAmount = _releaseAmount; - } else { - actualAmount = xvsBalance; - } - - if (actualAmount < minReleaseAmount) { - return; - } - - releaseStartBlock = getBlockNumber(); - - xvs.transfer(vaiVaultAddress, actualAmount); - emit DistributedVAIVaultVenus(actualAmount); - - IVAIVault(vaiVaultAddress).updatePendingRewards(); - } -} diff --git a/contracts/Comptroller/UpdatedComptrollerInterface.sol b/contracts/Comptroller/UpdatedComptrollerInterface.sol deleted file mode 100644 index 3e476c32e..000000000 --- a/contracts/Comptroller/UpdatedComptrollerInterface.sol +++ /dev/null @@ -1,93 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Tokens/VTokens/VToken.sol"; -import "../Oracle/PriceOracle.sol"; - -contract UpdatedComptrollerInterfaceG1 { - /// @notice Indicator that this is a Comptroller contract (for inspection) - bool public constant isComptroller = true; - - /*** Assets You Are In ***/ - - function enterMarkets(address[] calldata vTokens) external returns (uint[] memory); - - function exitMarket(address vToken) external returns (uint); - - /*** Policy Hooks ***/ - - function mintAllowed(address vToken, address minter, uint mintAmount) external returns (uint); - - function redeemAllowed(address vToken, address redeemer, uint redeemTokens) external returns (uint); - - function borrowAllowed(address vToken, address borrower, uint borrowAmount) external returns (uint); - - function repayBorrowAllowed( - address vToken, - address payer, - address borrower, - uint repayAmount - ) external returns (uint); - - function liquidateBorrowAllowed( - address vTokenBorrowed, - address vTokenCollateral, - address liquidator, - address borrower, - uint repayAmount - ) external returns (uint); - - function seizeAllowed( - address vTokenCollateral, - address vTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens - ) external returns (uint); - - function transferAllowed(address vToken, address src, address dst, uint transferTokens) external returns (uint); - - /*** Liquidity/Liquidation Calculations ***/ - - function liquidateCalculateSeizeTokens( - address vTokenBorrowed, - address vTokenCollateral, - uint repayAmount - ) external view returns (uint, uint); - - function setMintedVAIOf(address owner, uint amount) external returns (uint); -} - -contract UpdatedComptrollerInterfaceG2 is UpdatedComptrollerInterfaceG1 { - function liquidateVAICalculateSeizeTokens( - address vTokenCollateral, - uint repayAmount - ) external view returns (uint, uint); -} - -contract UpdatedComptrollerInterface is UpdatedComptrollerInterfaceG2 { - function markets(address) external view returns (bool, uint); - - function oracle() external view returns (PriceOracle); - - function getAccountLiquidity(address) external view returns (uint, uint, uint); - - function getAssetsIn(address) external view returns (VToken[] memory); - - function claimVenus(address) external; - - function venusAccrued(address) external view returns (uint); - - function venusSpeeds(address) external view returns (uint); - - function getAllMarkets() external view returns (VToken[] memory); - - function venusSupplierIndex(address, address) external view returns (uint); - - function venusInitialIndex() external view returns (uint224); - - function venusBorrowerIndex(address, address) external view returns (uint); - - function venusBorrowState(address) external view returns (uint224, uint32); - - function venusSupplyState(address) external view returns (uint224, uint32); -} diff --git a/contracts/Lens/ComptrollerLens.sol b/contracts/Lens/ComptrollerLens.sol index 5e082275c..33598495a 100644 --- a/contracts/Lens/ComptrollerLens.sol +++ b/contracts/Lens/ComptrollerLens.sol @@ -6,7 +6,8 @@ import "../Tokens/VTokens/VToken.sol"; import "../Tokens/EIP20Interface.sol"; import "../Oracle/PriceOracle.sol"; import "../Utils/ErrorReporter.sol"; -import "../Comptroller/Comptroller.sol"; +import "../Comptroller/ComptrollerInterface.sol"; +import "../Comptroller/ComptrollerLensInterface.sol"; import "../Tokens/VAI/VAIControllerInterface.sol"; /** @@ -49,8 +50,12 @@ contract ComptrollerLens is ComptrollerLensInterface, ComptrollerErrorReporter, uint actualRepayAmount ) external view returns (uint, uint) { /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = Comptroller(comptroller).oracle().getUnderlyingPrice(VToken(vTokenBorrowed)); - uint priceCollateralMantissa = Comptroller(comptroller).oracle().getUnderlyingPrice(VToken(vTokenCollateral)); + uint priceBorrowedMantissa = ComptrollerInterface(comptroller).oracle().getUnderlyingPrice( + VToken(vTokenBorrowed) + ); + uint priceCollateralMantissa = ComptrollerInterface(comptroller).oracle().getUnderlyingPrice( + VToken(vTokenCollateral) + ); if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) { return (uint(Error.PRICE_ERROR), 0); } @@ -68,7 +73,7 @@ contract ComptrollerLens is ComptrollerLensInterface, ComptrollerErrorReporter, Exp memory ratio; numerator = mul_( - Exp({ mantissa: Comptroller(comptroller).liquidationIncentiveMantissa() }), + Exp({ mantissa: ComptrollerInterface(comptroller).liquidationIncentiveMantissa() }), Exp({ mantissa: priceBorrowedMantissa }) ); denominator = mul_(Exp({ mantissa: priceCollateralMantissa }), Exp({ mantissa: exchangeRateMantissa })); @@ -93,7 +98,9 @@ contract ComptrollerLens is ComptrollerLensInterface, ComptrollerErrorReporter, ) external view returns (uint, uint) { /* Read oracle prices for borrowed and collateral markets */ uint priceBorrowedMantissa = 1e18; // Note: this is VAI - uint priceCollateralMantissa = Comptroller(comptroller).oracle().getUnderlyingPrice(VToken(vTokenCollateral)); + uint priceCollateralMantissa = ComptrollerInterface(comptroller).oracle().getUnderlyingPrice( + VToken(vTokenCollateral) + ); if (priceCollateralMantissa == 0) { return (uint(Error.PRICE_ERROR), 0); } @@ -111,7 +118,7 @@ contract ComptrollerLens is ComptrollerLensInterface, ComptrollerErrorReporter, Exp memory ratio; numerator = mul_( - Exp({ mantissa: Comptroller(comptroller).liquidationIncentiveMantissa() }), + Exp({ mantissa: ComptrollerInterface(comptroller).liquidationIncentiveMantissa() }), Exp({ mantissa: priceBorrowedMantissa }) ); denominator = mul_(Exp({ mantissa: priceCollateralMantissa }), Exp({ mantissa: exchangeRateMantissa })); @@ -143,7 +150,7 @@ contract ComptrollerLens is ComptrollerLensInterface, ComptrollerErrorReporter, uint oErr; // For each asset the account is in - VToken[] memory assets = Comptroller(comptroller).getAssetsIn(account); + VToken[] memory assets = ComptrollerInterface(comptroller).getAssetsIn(account); uint assetsCount = assets.length; for (uint i = 0; i < assetsCount; ++i) { VToken asset = assets[i]; @@ -156,12 +163,12 @@ contract ComptrollerLens is ComptrollerLensInterface, ComptrollerErrorReporter, // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades return (uint(Error.SNAPSHOT_ERROR), 0, 0); } - (, uint collateralFactorMantissa, ) = Comptroller(comptroller).markets(address(asset)); + (, uint collateralFactorMantissa) = ComptrollerInterface(comptroller).markets(address(asset)); vars.collateralFactor = Exp({ mantissa: collateralFactorMantissa }); vars.exchangeRate = Exp({ mantissa: vars.exchangeRateMantissa }); // Get the normalized price of the asset - vars.oraclePriceMantissa = Comptroller(comptroller).oracle().getUnderlyingPrice(asset); + vars.oraclePriceMantissa = ComptrollerInterface(comptroller).oracle().getUnderlyingPrice(asset); if (vars.oraclePriceMantissa == 0) { return (uint(Error.PRICE_ERROR), 0, 0); } @@ -200,7 +207,7 @@ contract ComptrollerLens is ComptrollerLensInterface, ComptrollerErrorReporter, } } - VAIControllerInterface vaiController = Comptroller(comptroller).vaiController(); + VAIControllerInterface vaiController = ComptrollerInterface(comptroller).vaiController(); if (address(vaiController) != address(0)) { vars.sumBorrowPlusEffects = add_(vars.sumBorrowPlusEffects, vaiController.getVAIRepayAmount(account)); diff --git a/contracts/Lens/InterestRateModelLens.sol b/contracts/Lens/InterestRateModelLens.sol deleted file mode 100644 index cf9893ed9..000000000 --- a/contracts/Lens/InterestRateModelLens.sol +++ /dev/null @@ -1,51 +0,0 @@ -pragma solidity ^0.5.16; -pragma experimental ABIEncoderV2; - -import "../InterestRateModels/InterestRateModel.sol"; -import "../Utils/SafeMath.sol"; - -/** - * @title InterestRateModelLens Contract - * @author Venus - * @notice Lens for querying interest rate model simulations - */ -contract InterestRateModelLens { - using SafeMath for uint256; - - struct SimulationResponse { - uint256[] borrowSimulation; - uint256[] supplySimulation; - } - - /** - * @notice Simulate interest rate curve fo a specific interest rate model given a reference borrow amount and reserve factor - * @param referenceAmountInWei Borrow amount to use in simulation - * @param interestRateModel Address for interest rate model to simulate - * @param reserveFactorMantissa Reserve Factor to use in simulation - * @return SimulationResponse struct with array of borrow simulations and an array of supply simulations - */ - function getSimulationResponse( - uint referenceAmountInWei, - address interestRateModel, - uint reserveFactorMantissa - ) external view returns (SimulationResponse memory) { - InterestRateModel ir = InterestRateModel(interestRateModel); - - uint256[] memory borrowSimulation = new uint256[](100); - uint256[] memory supplySimulation = new uint256[](100); - - uint borrow = referenceAmountInWei; - uint reserves = 0; - - for (uint percent_Factor = 1; percent_Factor <= 100; ++percent_Factor) { - uint cash = (percent_Factor.mul(referenceAmountInWei)).div(1e2); - uint256 borrowRate = ir.getBorrowRate(cash, borrow, reserves); - borrowSimulation[percent_Factor - 1] = borrowRate; - - uint256 supplyRate = ir.getSupplyRate(cash, borrow, reserves, reserveFactorMantissa); - supplySimulation[percent_Factor - 1] = supplyRate; - } - - return SimulationResponse({ borrowSimulation: borrowSimulation, supplySimulation: supplySimulation }); - } -} diff --git a/contracts/Lens/SnapshotLens.sol b/contracts/Lens/SnapshotLens.sol index d44d94a02..9bad3b7fc 100644 --- a/contracts/Lens/SnapshotLens.sol +++ b/contracts/Lens/SnapshotLens.sol @@ -3,7 +3,7 @@ pragma experimental ABIEncoderV2; import "../Tokens/VTokens/VToken.sol"; import "../Utils/SafeMath.sol"; -import "../Comptroller/Comptroller.sol"; +import "../Comptroller/ComptrollerInterface.sol"; import "../Tokens/EIP20Interface.sol"; import "../Tokens/VTokens/VBep20.sol"; @@ -55,7 +55,7 @@ contract SnapshotLens is ExponentialNoError { address comptrollerAddress ) public returns (AccountSnapshot[] memory) { // For each asset the account is in - VToken[] memory assets = Comptroller(comptrollerAddress).getAllMarkets(); + VToken[] memory assets = ComptrollerInterface(comptrollerAddress).getAllMarkets(); AccountSnapshot[] memory accountSnapshots = new AccountSnapshot[](assets.length); for (uint256 i = 0; i < assets.length; ++i) { accountSnapshots[i] = getAccountSnapshot(account, comptrollerAddress, assets[i]); @@ -64,7 +64,7 @@ contract SnapshotLens is ExponentialNoError { } function isACollateral(address account, address asset, address comptrollerAddress) public view returns (bool) { - VToken[] memory assetsAsCollateral = Comptroller(comptrollerAddress).getAssetsIn(account); + VToken[] memory assetsAsCollateral = ComptrollerInterface(comptrollerAddress).getAssetsIn(account); for (uint256 j = 0; j < assetsAsCollateral.length; ++j) { if (address(assetsAsCollateral[j]) == asset) { return true; @@ -87,13 +87,11 @@ contract SnapshotLens is ExponentialNoError { require(oErr == 0, "Snapshot Error"); vars.exchangeRate = Exp({ mantissa: vars.exchangeRateMantissa }); - Comptroller comptrollerInstance = Comptroller(comptrollerAddress); - - (, uint collateralFactorMantissa, ) = comptrollerInstance.markets(address(vToken)); + (, uint collateralFactorMantissa) = ComptrollerInterface(comptrollerAddress).markets(address(vToken)); vars.collateralFactor = Exp({ mantissa: collateralFactorMantissa }); // Get the normalized price of the asset - vars.oraclePriceMantissa = comptrollerInstance.oracle().getUnderlyingPrice(vToken); + vars.oraclePriceMantissa = ComptrollerInterface(comptrollerAddress).oracle().getUnderlyingPrice(vToken); vars.oraclePrice = Exp({ mantissa: vars.oraclePriceMantissa }); // Pre-compute a conversion factor from tokens -> bnb (normalized price value) diff --git a/contracts/Swap/interfaces/CustomErrors.sol b/contracts/Swap/interfaces/CustomErrors.sol index 355873a3f..d36e106ef 100644 --- a/contracts/Swap/interfaces/CustomErrors.sol +++ b/contracts/Swap/interfaces/CustomErrors.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.13; + // ************** // *** ERRORS *** // ************** diff --git a/contracts/Tokens/VAI/VAIController.sol b/contracts/Tokens/VAI/VAIController.sol index 5998a641e..442e44e52 100644 --- a/contracts/Tokens/VAI/VAIController.sol +++ b/contracts/Tokens/VAI/VAIController.sol @@ -4,7 +4,7 @@ import "../../Oracle/PriceOracle.sol"; import "../../Utils/ErrorReporter.sol"; import "../../Utils/Exponential.sol"; import "../../Comptroller/ComptrollerStorage.sol"; -import { Comptroller } from "../../Comptroller/Comptroller.sol"; +import "../../Comptroller/ComptrollerInterface.sol"; import "../../Governance/IAccessControlManager.sol"; import "../VTokens/VToken.sol"; import "./VAIControllerStorage.sol"; @@ -21,7 +21,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex uint public constant INITIAL_VAI_MINT_INDEX = 1e18; /// @notice Emitted when Comptroller is changed - event NewComptroller(Comptroller oldComptroller, Comptroller newComptroller); + event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller); /// @notice Event emitted when VAI is minted event MintVAI(address minter, uint mintVAIAmount); @@ -100,7 +100,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex function mintVAI(uint mintVAIAmount) external nonReentrant returns (uint) { if (address(comptroller) != address(0)) { require(mintVAIAmount > 0, "mintVAIAmount cannot be zero"); - require(!Comptroller(address(comptroller)).protocolPaused(), "protocol is paused"); + require(!comptroller.protocolPaused(), "protocol is paused"); accrueVAIInterest(); @@ -128,7 +128,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex } // Calculate the minted balance based on interest index - uint totalMintedVAI = Comptroller(address(comptroller)).mintedVAIs(minter); + uint totalMintedVAI = comptroller.mintedVAIs(minter); if (totalMintedVAI > 0) { uint256 repayAmount = getVAIRepayAmount(minter); @@ -201,7 +201,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex require(repayVAIAmount > 0, "repayVAIAmount cannt be zero"); - require(!Comptroller(address(comptroller)).protocolPaused(), "protocol is paused"); + require(!comptroller.protocolPaused(), "protocol is paused"); return repayVAIFresh(msg.sender, msg.sender, repayVAIAmount); } @@ -227,7 +227,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex bool success = VAI(getVAIAddress()).transferFrom(payer, receiver, partOfCurrentInterest); require(success == true, "failed to transfer VAI fee"); - uint vaiBalanceBorrower = Comptroller(address(comptroller)).mintedVAIs(borrower); + uint vaiBalanceBorrower = comptroller.mintedVAIs(borrower); uint accountVAINew; (mErr, accountVAINew) = subUInt(vaiBalanceBorrower, burn); @@ -260,7 +260,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex uint repayAmount, VTokenInterface vTokenCollateral ) external nonReentrant returns (uint, uint) { - require(!Comptroller(address(comptroller)).protocolPaused(), "protocol is paused"); + require(!comptroller.protocolPaused(), "protocol is paused"); uint error = vTokenCollateral.accrueInterest(); if (error != uint(Error.NO_ERROR)) { @@ -376,13 +376,13 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex * @dev Admin function to set a new comptroller * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) */ - function _setComptroller(Comptroller comptroller_) external returns (uint) { + function _setComptroller(ComptrollerInterface comptroller_) external returns (uint) { // Check caller is admin if (msg.sender != admin) { return fail(Error.UNAUTHORIZED, FailureInfo.SET_COMPTROLLER_OWNER_CHECK); } - Comptroller oldComptroller = comptroller; + ComptrollerInterface oldComptroller = comptroller; comptroller = comptroller_; emit NewComptroller(oldComptroller, comptroller_); @@ -411,8 +411,8 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex // solhint-disable-next-line code-complexity function getMintableVAI(address minter) public view returns (uint, uint) { - PriceOracle oracle = Comptroller(address(comptroller)).oracle(); - VToken[] memory enteredMarkets = Comptroller(address(comptroller)).getAssetsIn(minter); + PriceOracle oracle = comptroller.oracle(); + VToken[] memory enteredMarkets = comptroller.getAssetsIn(minter); AccountAmountLocalVars memory vars; // Holds all our calculation results @@ -450,7 +450,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex return (uint(Error.MATH_ERROR), 0); } - (, uint collateralFactorMantissa, ) = Comptroller(address(comptroller)).markets(address(enteredMarkets[i])); + (, uint collateralFactorMantissa) = comptroller.markets(address(enteredMarkets[i])); (vars.mErr, vars.marketSupply) = mulUInt(vars.marketSupply, collateralFactorMantissa); if (vars.mErr != MathError.NO_ERROR) { return (uint(Error.MATH_ERROR), 0); @@ -477,7 +477,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex } } - uint totalMintedVAI = Comptroller(address(comptroller)).mintedVAIs(minter); + uint totalMintedVAI = comptroller.mintedVAIs(minter); uint256 repayAmount = 0; if (totalMintedVAI > 0) { @@ -489,7 +489,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex return (uint(Error.MATH_ERROR), 0); } - (vars.mErr, accountMintableVAI) = mulUInt(vars.sumSupply, Comptroller(address(comptroller)).vaiMintRate()); + (vars.mErr, accountMintableVAI) = mulUInt(vars.sumSupply, comptroller.vaiMintRate()); require(vars.mErr == MathError.NO_ERROR, "VAI_MINT_AMOUNT_CALCULATION_FAILED"); (vars.mErr, accountMintableVAI) = divUInt(accountMintableVAI, 10000); @@ -531,7 +531,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex } function getVAIRepayRate() public view returns (uint) { - PriceOracle oracle = Comptroller(address(comptroller)).oracle(); + PriceOracle oracle = comptroller.oracle(); MathError mErr; if (baseRateMantissa > 0) { @@ -596,7 +596,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex MathError mErr; uint delta; - uint amount = Comptroller(address(comptroller)).mintedVAIs(account); + uint amount = comptroller.mintedVAIs(account); uint interest = pastVAIInterest[account]; uint totalMintedVAI; uint newInterest; @@ -630,7 +630,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex uint256 totalRepayAmount = getVAIRepayAmount(borrower); uint currentInterest; - (mErr, currentInterest) = subUInt(totalRepayAmount, Comptroller(address(comptroller)).mintedVAIs(borrower)); + (mErr, currentInterest) = subUInt(totalRepayAmount, comptroller.mintedVAIs(borrower)); require(mErr == MathError.NO_ERROR, "VAI_BURN_AMOUNT_CALCULATION_FAILED"); (mErr, currentInterest) = addUInt(pastVAIInterest[borrower], currentInterest); @@ -753,7 +753,7 @@ contract VAIController is VAIControllerStorageG2, VAIControllerErrorReporter, Ex emit NewVAIMintCap(old, _mintCap); } - function getBlockNumber() public view returns (uint) { + function getBlockNumber() internal view returns (uint) { return block.number; } diff --git a/contracts/Tokens/VAI/VAIControllerInterface.sol b/contracts/Tokens/VAI/VAIControllerInterface.sol index 9a305f3f8..6cbb7bee8 100644 --- a/contracts/Tokens/VAI/VAIControllerInterface.sol +++ b/contracts/Tokens/VAI/VAIControllerInterface.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.16; -import "../VTokens/VToken.sol"; +import "../VTokens/VTokenInterfaces.sol"; contract VAIControllerInterface { function getVAIAddress() public view returns (address); diff --git a/contracts/Tokens/VAI/VAIControllerStorage.sol b/contracts/Tokens/VAI/VAIControllerStorage.sol index 2dbd38a19..71a5bfaa5 100644 --- a/contracts/Tokens/VAI/VAIControllerStorage.sol +++ b/contracts/Tokens/VAI/VAIControllerStorage.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.16; -import { Comptroller } from "../../Comptroller/Comptroller.sol"; +import "../../Comptroller/ComptrollerInterface.sol"; contract VAIUnitrollerAdminStorage { /** @@ -25,7 +25,7 @@ contract VAIUnitrollerAdminStorage { } contract VAIControllerStorageG1 is VAIUnitrollerAdminStorage { - Comptroller public comptroller; + ComptrollerInterface public comptroller; struct VenusVAIState { /// @notice The last updated venusVAIMintIndex diff --git a/contracts/Utils/ErrorReporter.sol b/contracts/Utils/ErrorReporter.sol index e28874209..70542f397 100644 --- a/contracts/Utils/ErrorReporter.sol +++ b/contracts/Utils/ErrorReporter.sol @@ -20,7 +20,8 @@ contract ComptrollerErrorReporter { SNAPSHOT_ERROR, TOO_MANY_ASSETS, TOO_MUCH_REPAY, - INSUFFICIENT_BALANCE_FOR_VAI + INSUFFICIENT_BALANCE_FOR_VAI, + MARKET_NOT_COLLATERAL } enum FailureInfo { diff --git a/contracts/test/ComptrollerHarness.sol b/contracts/test/ComptrollerHarness.sol index 7b742df11..3bfef64af 100644 --- a/contracts/test/ComptrollerHarness.sol +++ b/contracts/test/ComptrollerHarness.sol @@ -1,26 +1,15 @@ pragma solidity ^0.5.16; -import "../Comptroller/Comptroller.sol"; +import "./ComptrollerMock.sol"; import "../Oracle/PriceOracle.sol"; +import "../Comptroller/Unitroller.sol"; -contract ComptrollerKovan is Comptroller { - function getXVSAddress() public view returns (address) { - return 0x61460874a7196d6a22D1eE4922473664b3E95270; - } -} - -contract ComptrollerRopsten is Comptroller { - function getXVSAddress() public view returns (address) { - return 0x1Fe16De955718CFAb7A44605458AB023838C2793; - } -} - -contract ComptrollerHarness is Comptroller { +contract ComptrollerHarness is ComptrollerMock { address internal xvsAddress; address internal vXVSAddress; uint public blockNumber; - constructor() public Comptroller() {} + constructor() public ComptrollerMock() {} function setVenusSupplyState(address vToken, uint224 index, uint32 blockNumber_) public { venusSupplyState[vToken].index = index; @@ -40,18 +29,10 @@ contract ComptrollerHarness is Comptroller { xvsAddress = xvsAddress_; } - function getXVSAddress() public view returns (address) { - return xvsAddress; - } - function setXVSVTokenAddress(address vXVSAddress_) public { vXVSAddress = vXVSAddress_; } - function getXVSVTokenAddress() public view returns (address) { - return vXVSAddress; - } - /** * @notice Set the amount of XVS distributed per block * @param venusRate_ The amount of XVS wei per block to distribute @@ -189,413 +170,6 @@ contract ComptrollerHarness is Comptroller { } } -contract ComptrollerBorked { - function _become(Unitroller unitroller) public { - require(msg.sender == unitroller.admin(), "only unitroller admin can change brains"); - unitroller._acceptImplementation(); - } -} - -contract BoolComptroller is ComptrollerInterface { - bool internal allowMint = true; - bool internal allowRedeem = true; - bool internal allowBorrow = true; - bool internal allowRepayBorrow = true; - bool internal allowLiquidateBorrow = true; - bool internal allowSeize = true; - bool internal allowTransfer = true; - - bool internal verifyMint = true; - bool internal verifyRedeem = true; - bool internal verifyBorrow = true; - bool internal verifyRepayBorrow = true; - bool internal verifyLiquidateBorrow = true; - bool internal verifySeize = true; - bool internal verifyTransfer = true; - uint public liquidationIncentiveMantissa = 11e17; - bool internal failCalculateSeizeTokens; - uint internal calculatedSeizeTokens; - - bool public protocolPaused = false; - - mapping(address => uint) public mintedVAIs; - bool internal vaiFailCalculateSeizeTokens; - uint internal vaiCalculatedSeizeTokens; - - uint internal noError = 0; - uint internal opaqueError = noError + 11; // an arbitrary, opaque error code - - address public treasuryGuardian; - address public treasuryAddress; - uint public treasuryPercent; - address public liquidatorContract; - - /*** Assets You Are In ***/ - - function enterMarkets(address[] calldata _vTokens) external returns (uint[] memory) { - _vTokens; - uint[] memory ret; - return ret; - } - - function exitMarket(address _vToken) external returns (uint) { - _vToken; - return noError; - } - - /*** Policy Hooks ***/ - - function mintAllowed(address _vToken, address _minter, uint _mintAmount) external returns (uint) { - _vToken; - _minter; - _mintAmount; - return allowMint ? noError : opaqueError; - } - - function mintVerify(address _vToken, address _minter, uint _mintAmount, uint _mintTokens) external { - _vToken; - _minter; - _mintAmount; - _mintTokens; - require(verifyMint, "mintVerify rejected mint"); - } - - function redeemAllowed(address _vToken, address _redeemer, uint _redeemTokens) external returns (uint) { - _vToken; - _redeemer; - _redeemTokens; - return allowRedeem ? noError : opaqueError; - } - - function redeemVerify(address _vToken, address _redeemer, uint _redeemAmount, uint _redeemTokens) external { - _vToken; - _redeemer; - _redeemAmount; - _redeemTokens; - require(verifyRedeem, "redeemVerify rejected redeem"); - } - - function borrowAllowed(address _vToken, address _borrower, uint _borrowAmount) external returns (uint) { - _vToken; - _borrower; - _borrowAmount; - return allowBorrow ? noError : opaqueError; - } - - function borrowVerify(address _vToken, address _borrower, uint _borrowAmount) external { - _vToken; - _borrower; - _borrowAmount; - require(verifyBorrow, "borrowVerify rejected borrow"); - } - - function repayBorrowAllowed( - address _vToken, - address _payer, - address _borrower, - uint _repayAmount - ) external returns (uint) { - _vToken; - _payer; - _borrower; - _repayAmount; - return allowRepayBorrow ? noError : opaqueError; - } - - function repayBorrowVerify( - address _vToken, - address _payer, - address _borrower, - uint _repayAmount, - uint _borrowerIndex - ) external { - _vToken; - _payer; - _borrower; - _repayAmount; - _borrowerIndex; - require(verifyRepayBorrow, "repayBorrowVerify rejected repayBorrow"); - } - - function _setLiquidatorContract(address liquidatorContract_) external { - liquidatorContract = liquidatorContract_; - } - - function liquidateBorrowAllowed( - address _vTokenBorrowed, - address _vTokenCollateral, - address _liquidator, - address _borrower, - uint _repayAmount - ) external returns (uint) { - _vTokenBorrowed; - _vTokenCollateral; - _borrower; - _repayAmount; - if (liquidatorContract != address(0) && liquidatorContract != _liquidator) { - return opaqueError; - } - return allowLiquidateBorrow ? noError : opaqueError; - } - - function liquidateBorrowVerify( - address _vTokenBorrowed, - address _vTokenCollateral, - address _liquidator, - address _borrower, - uint _repayAmount, - uint _seizeTokens - ) external { - _vTokenBorrowed; - _vTokenCollateral; - _liquidator; - _borrower; - _repayAmount; - _seizeTokens; - require(verifyLiquidateBorrow, "liquidateBorrowVerify rejected liquidateBorrow"); - } - - function seizeAllowed( - address _vTokenCollateral, - address _vTokenBorrowed, - address _borrower, - address _liquidator, - uint _seizeTokens - ) external returns (uint) { - _vTokenCollateral; - _vTokenBorrowed; - _liquidator; - _borrower; - _seizeTokens; - return allowSeize ? noError : opaqueError; - } - - function seizeVerify( - address _vTokenCollateral, - address _vTokenBorrowed, - address _liquidator, - address _borrower, - uint _seizeTokens - ) external { - _vTokenCollateral; - _vTokenBorrowed; - _liquidator; - _borrower; - _seizeTokens; - require(verifySeize, "seizeVerify rejected seize"); - } - - function transferAllowed( - address _vToken, - address _src, - address _dst, - uint _transferTokens - ) external returns (uint) { - _vToken; - _src; - _dst; - _transferTokens; - return allowTransfer ? noError : opaqueError; - } - - function transferVerify(address _vToken, address _src, address _dst, uint _transferTokens) external { - _vToken; - _src; - _dst; - _transferTokens; - require(verifyTransfer, "transferVerify rejected transfer"); - } - - /*** Special Liquidation Calculation ***/ - - function liquidateCalculateSeizeTokens( - address _vTokenBorrowed, - address _vTokenCollateral, - uint _repayAmount - ) external view returns (uint, uint) { - _vTokenBorrowed; - _vTokenCollateral; - _repayAmount; - return failCalculateSeizeTokens ? (opaqueError, 0) : (noError, calculatedSeizeTokens); - } - - /*** Special Liquidation Calculation ***/ - - function liquidateVAICalculateSeizeTokens( - address _vTokenCollateral, - uint _repayAmount - ) external view returns (uint, uint) { - _vTokenCollateral; - _repayAmount; - return vaiFailCalculateSeizeTokens ? (opaqueError, 0) : (noError, vaiCalculatedSeizeTokens); - } - - /**** Mock Settors ****/ - - /*** Policy Hooks ***/ - - function setMintAllowed(bool allowMint_) public { - allowMint = allowMint_; - } - - function setMintVerify(bool verifyMint_) public { - verifyMint = verifyMint_; - } - - function setRedeemAllowed(bool allowRedeem_) public { - allowRedeem = allowRedeem_; - } - - function setRedeemVerify(bool verifyRedeem_) public { - verifyRedeem = verifyRedeem_; - } - - function setBorrowAllowed(bool allowBorrow_) public { - allowBorrow = allowBorrow_; - } - - function setBorrowVerify(bool verifyBorrow_) public { - verifyBorrow = verifyBorrow_; - } - - function setRepayBorrowAllowed(bool allowRepayBorrow_) public { - allowRepayBorrow = allowRepayBorrow_; - } - - function setRepayBorrowVerify(bool verifyRepayBorrow_) public { - verifyRepayBorrow = verifyRepayBorrow_; - } - - function setLiquidateBorrowAllowed(bool allowLiquidateBorrow_) public { - allowLiquidateBorrow = allowLiquidateBorrow_; - } - - function setLiquidateBorrowVerify(bool verifyLiquidateBorrow_) public { - verifyLiquidateBorrow = verifyLiquidateBorrow_; - } - - function setSeizeAllowed(bool allowSeize_) public { - allowSeize = allowSeize_; - } - - function setSeizeVerify(bool verifySeize_) public { - verifySeize = verifySeize_; - } - - function setTransferAllowed(bool allowTransfer_) public { - allowTransfer = allowTransfer_; - } - - function setTransferVerify(bool verifyTransfer_) public { - verifyTransfer = verifyTransfer_; - } - - /*** Liquidity/Liquidation Calculations ***/ - function setAnnouncedLiquidationIncentiveMantissa(uint mantissa_) external { - liquidationIncentiveMantissa = mantissa_; - } - - /*** Liquidity/Liquidation Calculations ***/ - - function setCalculatedSeizeTokens(uint seizeTokens_) public { - calculatedSeizeTokens = seizeTokens_; - } - - function setFailCalculateSeizeTokens(bool shouldFail) public { - failCalculateSeizeTokens = shouldFail; - } - - function setVAICalculatedSeizeTokens(uint vaiSeizeTokens_) public { - vaiCalculatedSeizeTokens = vaiSeizeTokens_; - } - - function setVAIFailCalculateSeizeTokens(bool vaiShouldFail) public { - vaiFailCalculateSeizeTokens = vaiShouldFail; - } - - function harnessSetMintedVAIOf(address owner, uint amount) external returns (uint) { - mintedVAIs[owner] = amount; - return noError; - } - - // function mintedVAIs(address owner) external pure returns (uint) { - // owner; - // return 1e18; - // } - - function setMintedVAIOf(address owner, uint amount) external returns (uint) { - owner; - amount; - return noError; - } - - function vaiMintRate() external pure returns (uint) { - return 1e18; - } - - function setTreasuryData(address treasuryGuardian_, address treasuryAddress_, uint treasuryPercent_) external { - treasuryGuardian = treasuryGuardian_; - treasuryAddress = treasuryAddress_; - treasuryPercent = treasuryPercent_; - } - - function _setMarketSupplyCaps(VToken[] calldata vTokens, uint[] calldata newSupplyCaps) external {} - - /*** Functions from ComptrollerInterface not implemented by BoolComptroller ***/ - - function markets(address) external view returns (bool, uint) { - revert(); - } - - function oracle() external view returns (PriceOracle) { - revert(); - } - - function getAccountLiquidity(address) external view returns (uint, uint, uint) { - revert(); - } - - function getAssetsIn(address) external view returns (VToken[] memory) { - revert(); - } - - function claimVenus(address) external { - revert(); - } - - function venusAccrued(address) external view returns (uint) { - revert(); - } - - function venusSpeeds(address) external view returns (uint) { - revert(); - } - - function getAllMarkets() external view returns (VToken[] memory) { - revert(); - } - - function venusSupplierIndex(address, address) external view returns (uint) { - revert(); - } - - function venusInitialIndex() external view returns (uint224) { - revert(); - } - - function venusBorrowerIndex(address, address) external view returns (uint) { - revert(); - } - - function venusBorrowState(address) external view returns (uint224, uint32) { - revert(); - } - - function venusSupplyState(address) external view returns (uint224, uint32) { - revert(); - } -} - contract EchoTypesComptroller is UnitrollerAdminStorage { function stringy(string memory s) public pure returns (string memory) { return s; diff --git a/contracts/test/ComptrollerMock.sol b/contracts/test/ComptrollerMock.sol new file mode 100644 index 000000000..2a7f44e5c --- /dev/null +++ b/contracts/test/ComptrollerMock.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.5.16; + +import "../Comptroller/Diamond/facets/MarketFacet.sol"; +import "../Comptroller/Diamond/facets/PolicyFacet.sol"; +import "../Comptroller/Diamond/facets/RewardFacet.sol"; +import "../Comptroller/Diamond/facets/SetterFacet.sol"; +import "../Comptroller/Unitroller.sol"; + +// This contract contains all methods of Comptroller implementation in different facets at one place for testing purpose +// This contract does not have diamond functionality(i.e delegate call to facets methods) +contract ComptrollerMock is MarketFacet, PolicyFacet, RewardFacet, SetterFacet { + constructor() public { + admin = msg.sender; + } + + function _become(Unitroller unitroller) public { + require(msg.sender == unitroller.admin(), "only unitroller admin can"); + require(unitroller._acceptImplementation() == 0, "not authorized"); + } + + function _setComptrollerLens(ComptrollerLensInterface comptrollerLens_) external returns (uint) { + ensureAdmin(); + ensureNonzeroAddress(address(comptrollerLens_)); + address oldComptrollerLens = address(comptrollerLens); + comptrollerLens = comptrollerLens_; + emit NewComptrollerLens(oldComptrollerLens, address(comptrollerLens)); + + return uint(Error.NO_ERROR); + } +} diff --git a/contracts/test/ComptrollerScenario.sol b/contracts/test/ComptrollerScenario.sol index 5a4e20fd8..5663dc805 100644 --- a/contracts/test/ComptrollerScenario.sol +++ b/contracts/test/ComptrollerScenario.sol @@ -1,21 +1,21 @@ pragma solidity ^0.5.16; -import "../Comptroller/Comptroller.sol"; +import "./ComptrollerMock.sol"; -contract ComptrollerScenario is Comptroller { +contract ComptrollerScenario is ComptrollerMock { uint public blockNumber; address public xvsAddress; address public vaiAddress; - constructor() public Comptroller() {} + constructor() public ComptrollerMock() {} function setXVSAddress(address xvsAddress_) public { xvsAddress = xvsAddress_; } - function getXVSAddress() public view returns (address) { - return xvsAddress; - } + // function getXVSAddress() public view returns (address) { + // return xvsAddress; + // } function setVAIAddress(address vaiAddress_) public { vaiAddress = vaiAddress_; diff --git a/contracts/test/ComptrollerScenarioG1.sol b/contracts/test/ComptrollerScenarioG1.sol deleted file mode 100644 index 371e3082b..000000000 --- a/contracts/test/ComptrollerScenarioG1.sol +++ /dev/null @@ -1,89 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Comptroller/ComptrollerG1.sol"; - -contract ComptrollerScenarioG1 is ComptrollerG1 { - uint public blockNumber; - address public xvsAddress; - address public vaiAddress; - /// @notice Supply caps enforced by mintAllowed for each vToken address. Defaults to zero which corresponds to minting notAllowed - mapping(address => uint) public supplyCaps; - - constructor() public ComptrollerG1() {} - - function setXVSAddress(address xvsAddress_) public { - xvsAddress = xvsAddress_; - } - - function getXVSAddress() public view returns (address) { - return xvsAddress; - } - - function setVAIAddress(address vaiAddress_) public { - vaiAddress = vaiAddress_; - } - - function getVAIAddress() public view returns (address) { - return vaiAddress; - } - - function membershipLength(VToken vToken) public view returns (uint) { - return accountAssets[address(vToken)].length; - } - - function fastForward(uint blocks) public returns (uint) { - blockNumber += blocks; - - return blockNumber; - } - - function setBlockNumber(uint number) public { - blockNumber = number; - } - - function getBlockNumber() public view returns (uint) { - return blockNumber; - } - - function getVenusMarkets() public view returns (address[] memory) { - uint m = allMarkets.length; - uint n = 0; - for (uint i = 0; i < m; i++) { - if (markets[address(allMarkets[i])].isVenus) { - n++; - } - } - - address[] memory venusMarkets = new address[](n); - uint k = 0; - for (uint i = 0; i < m; i++) { - if (markets[address(allMarkets[i])].isVenus) { - venusMarkets[k++] = address(allMarkets[i]); - } - } - return venusMarkets; - } - - function unlist(VToken vToken) public { - markets[address(vToken)].isListed = false; - } - - /** - * @notice Set the given supply caps for the given vToken markets. Supply that brings total Supply to or above supply cap will revert. - * @dev Admin function to set the supply caps. A supply cap of 0 corresponds to Minting NotAllowed. - * @param vTokens The addresses of the markets (tokens) to change the supply caps for - * @param newSupplyCaps The new supply cap values in underlying to be set. A value of 0 corresponds to Minting NotAllowed. - */ - function _setMarketSupplyCaps(VToken[] calldata vTokens, uint[] calldata newSupplyCaps) external { - require(msg.sender == admin, "only admin can set supply caps"); - - uint numMarkets = vTokens.length; - uint numSupplyCaps = newSupplyCaps.length; - - require(numMarkets != 0 && numMarkets == numSupplyCaps, "invalid input"); - - for (uint i = 0; i < numMarkets; i++) { - supplyCaps[address(vTokens[i])] = newSupplyCaps[i]; - } - } -} diff --git a/contracts/test/ComptrollerScenarioG2.sol b/contracts/test/ComptrollerScenarioG2.sol deleted file mode 100644 index 233c2ff21..000000000 --- a/contracts/test/ComptrollerScenarioG2.sol +++ /dev/null @@ -1,51 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Comptroller/ComptrollerG2.sol"; - -contract ComptrollerScenarioG2 is ComptrollerG2 { - uint public blockNumber; - /// @notice Supply caps enforced by mintAllowed for each vToken address. Defaults to zero which corresponds to minting notAllowed - mapping(address => uint) public supplyCaps; - - constructor() public ComptrollerG2() {} - - function fastForward(uint blocks) public returns (uint) { - blockNumber += blocks; - return blockNumber; - } - - function setBlockNumber(uint number) public { - blockNumber = number; - } - - function membershipLength(VToken vToken) public view returns (uint) { - return accountAssets[address(vToken)].length; - } - - function unlist(VToken vToken) public { - markets[address(vToken)].isListed = false; - } - - function setVenusSpeed(address vToken, uint venusSpeed) public { - venusSpeeds[vToken] = venusSpeed; - } - - /** - * @notice Set the given supply caps for the given vToken markets. Supply that brings total Supply to or above supply cap will revert. - * @dev Admin function to set the supply caps. A supply cap of 0 corresponds to Minting NotAllowed. - * @param vTokens The addresses of the markets (tokens) to change the supply caps for - * @param newSupplyCaps The new supply cap values in underlying to be set. A value of 0 corresponds to Minting NotAllowed. - */ - function _setMarketSupplyCaps(VToken[] calldata vTokens, uint[] calldata newSupplyCaps) external { - require(msg.sender == admin, "only admin can set supply caps"); - - uint numMarkets = vTokens.length; - uint numSupplyCaps = newSupplyCaps.length; - - require(numMarkets != 0 && numMarkets == numSupplyCaps, "invalid input"); - - for (uint i = 0; i < numMarkets; i++) { - supplyCaps[address(vTokens[i])] = newSupplyCaps[i]; - } - } -} diff --git a/contracts/test/ComptrollerScenarioG3.sol b/contracts/test/ComptrollerScenarioG3.sol deleted file mode 100644 index 4dbeab94d..000000000 --- a/contracts/test/ComptrollerScenarioG3.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Comptroller/ComptrollerG3.sol"; - -contract ComptrollerScenarioG3 is ComptrollerG3 { - uint public blockNumber; - - constructor() public ComptrollerG3() {} - - function fastForward(uint blocks) public returns (uint) { - blockNumber += blocks; - return blockNumber; - } - - function setBlockNumber(uint number) public { - blockNumber = number; - } - - function membershipLength(VToken vToken) public view returns (uint) { - return accountAssets[address(vToken)].length; - } - - function unlist(VToken vToken) public { - markets[address(vToken)].isListed = false; - } - - function setVenusSpeed(address vToken, uint venusSpeed) public { - venusSpeeds[vToken] = venusSpeed; - } -} diff --git a/contracts/test/ComptrollerScenarioG4.sol b/contracts/test/ComptrollerScenarioG4.sol deleted file mode 100644 index 8d4c9292c..000000000 --- a/contracts/test/ComptrollerScenarioG4.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Comptroller/ComptrollerG4.sol"; - -contract ComptrollerScenarioG4 is ComptrollerG4 { - uint public blockNumber; - - constructor() public ComptrollerG4() {} - - function fastForward(uint blocks) public returns (uint) { - blockNumber += blocks; - return blockNumber; - } - - function setBlockNumber(uint number) public { - blockNumber = number; - } - - function membershipLength(VToken vToken) public view returns (uint) { - return accountAssets[address(vToken)].length; - } - - function unlist(VToken vToken) public { - markets[address(vToken)].isListed = false; - } - - function setVenusSpeed(address vToken, uint venusSpeed) public { - venusSpeeds[vToken] = venusSpeed; - } -} diff --git a/contracts/test/ComptrollerScenarioG5.sol b/contracts/test/ComptrollerScenarioG5.sol deleted file mode 100644 index 22db0db9c..000000000 --- a/contracts/test/ComptrollerScenarioG5.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.5.16; - -import "../../contracts/Comptroller/ComptrollerG5.sol"; - -contract ComptrollerScenarioG5 is ComptrollerG5 { - uint public blockNumber; - - constructor() public ComptrollerG5() {} - - function fastForward(uint blocks) public returns (uint) { - blockNumber += blocks; - return blockNumber; - } - - function setBlockNumber(uint number) public { - blockNumber = number; - } - - function membershipLength(VToken vToken) public view returns (uint) { - return accountAssets[address(vToken)].length; - } - - function unlist(VToken vToken) public { - markets[address(vToken)].isListed = false; - } - - function setVenusSpeed(address vToken, uint venusSpeed) public { - venusSpeeds[vToken] = venusSpeed; - } -} diff --git a/contracts/test/DiamondHarness.sol b/contracts/test/DiamondHarness.sol new file mode 100644 index 000000000..eb80c1dd7 --- /dev/null +++ b/contracts/test/DiamondHarness.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.5.16; +pragma experimental ABIEncoderV2; + +import "../Comptroller/Diamond/Diamond.sol"; + +contract DiamondHarness is Diamond { + function getFacetAddress(bytes4 sig) public view returns (address) { + address facet = _selectorToFacetAndPosition[sig].facetAddress; + require(facet != address(0), "Diamond: Function does not exist"); + return facet; + } +} diff --git a/contracts/test/DiamondHarnessInterface.sol b/contracts/test/DiamondHarnessInterface.sol new file mode 100644 index 000000000..4f7cf9ce0 --- /dev/null +++ b/contracts/test/DiamondHarnessInterface.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.5.16; +pragma experimental ABIEncoderV2; + +contract DiamondHarnessInterface { + enum FacetCutAction { + Add, + Replace, + Remove + } + + struct FacetCut { + address facetAddress; + FacetCutAction action; + bytes4[] functionSelectors; + } + + struct FacetAddressAndPosition { + address facetAddress; + uint96 functionSelectorPosition; + } + + struct Facet { + address facetAddress; + bytes4[] functionSelectors; + } + + function getFacetAddress(bytes4 sig) public view returns (address); + + function diamondCut(FacetCut[] calldata _diamondCut) external; + + function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory _facetFunctionSelectors); + + function facetPosition(address _facet) external view returns (uint256); + + function facetAddresses() external view returns (address[] memory facetAddresses_); + + function facets() external view returns (Facet[] memory facets); + + function facetAddress(bytes4 _functionSelector) external view returns (FacetAddressAndPosition memory); +} diff --git a/contracts/test/TetherInterface.sol b/contracts/test/TetherInterface.sol deleted file mode 100644 index 311e60e9b..000000000 --- a/contracts/test/TetherInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Tokens/EIP20Interface.sol"; - -contract TetherInterface is EIP20Interface { - function setParams(uint newBasisPoints, uint newMaxFee) external; -} diff --git a/contracts/test/VAIControllerHarness.sol b/contracts/test/VAIControllerHarness.sol index 2332c013c..f5139fede 100644 --- a/contracts/test/VAIControllerHarness.sol +++ b/contracts/test/VAIControllerHarness.sol @@ -56,7 +56,7 @@ contract VAIControllerHarness is VAIController { blocksPerYear = number; } - function getBlockNumber() public view returns (uint) { + function getBlockNumber() internal view returns (uint) { return blockNumber; } diff --git a/contracts/test/VAIControllerScenario.sol b/contracts/test/VAIControllerScenario.sol deleted file mode 100644 index 8b5a6d9f4..000000000 --- a/contracts/test/VAIControllerScenario.sol +++ /dev/null @@ -1,28 +0,0 @@ -pragma solidity ^0.5.16; - -import "../Tokens/VAI/VAIController.sol"; -import "./ComptrollerScenario.sol"; - -contract VAIControllerScenario is VAIController { - uint internal blockNumber; - address public xvsAddress; - address public vaiAddress; - - constructor() public VAIController() {} - - function setVAIAddress(address vaiAddress_) public { - vaiAddress = vaiAddress_; - } - - function getVAIAddress() public view returns (address) { - return vaiAddress; - } - - function setBlockNumber(uint number) public { - blockNumber = number; - } - - function getBlockNumber() public view returns (uint) { - return blockNumber; - } -} diff --git a/contracts/test/XVSVaultHarness.sol b/contracts/test/XVSVaultHarness.sol index 579cae0c8..f22c8e6ec 100644 --- a/contracts/test/XVSVaultHarness.sol +++ b/contracts/test/XVSVaultHarness.sol @@ -4,7 +4,7 @@ contract XVSVaultHarness { constructor() public {} // solhint-disable-next-line no-unused-vars - function getPriorVotes(address account, uint256 blockNumber, uint256 votePower) external view returns (uint256) { + function getPriorVotes(address account, uint256 blockNumber, uint256 votePower) external pure returns (uint256) { return votePower; } } diff --git a/deploy/001-comptroller.ts b/deploy/001-comptroller.ts index d7c4b79b1..05c7e12c8 100644 --- a/deploy/001-comptroller.ts +++ b/deploy/001-comptroller.ts @@ -14,7 +14,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { autoMine: true, }); - await deploy("Comptroller", { + await deploy("ComptrollerMock", { from: deployer, log: true, autoMine: true, diff --git a/deploy/004-support-markets.ts b/deploy/004-support-markets.ts index 8fdb77b68..dda36f019 100644 --- a/deploy/004-support-markets.ts +++ b/deploy/004-support-markets.ts @@ -10,7 +10,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const vETH = await deployments.get("vETH"); const comptrollerDeployment = await deployments.get("Comptroller"); - const comptroller = await ethers.getContractAt("Comptroller", comptrollerDeployment.address); + const comptroller = await ethers.getContractAt("ComptrollerMock", comptrollerDeployment.address); await comptroller.connect(deployerSigner)._supportMarket(vUSDC.address); await comptroller.connect(deployerSigner)._supportMarket(vETH.address); diff --git a/hardhat.config.ts b/hardhat.config.ts index e82ec9dd1..6fa9dbda9 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -13,6 +13,7 @@ import { HardhatUserConfig, task } from "hardhat/config"; import "solidity-coverage"; import "solidity-docgen"; +require("hardhat-contract-sizer"); require("dotenv").config(); const BSCSCAN_API_KEY = process.env.BSCSCAN_API_KEY; @@ -154,7 +155,7 @@ function isFork() { blockNumber: 21068448, }, accounts: { - accountsBalance: "1000000000000000000", + accountsBalance: "1000000000000000000000", }, live: false, } diff --git a/package.json b/package.json index 0ba44db31..153f6c549 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,8 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-jest": "^27.1.2", "ethers": "^5.6.9", - "hardhat": "^2.16.1", + "hardhat": "^2.10.1", + "hardhat-contract-sizer": "^2.8.0", "hardhat-deploy": "^0.11.14", "hardhat-deploy-ethers": "^0.3.0-beta.13", "hardhat-gas-reporter": "^1.0.8", diff --git a/script/deploy/comptroller/diamond.ts b/script/deploy/comptroller/diamond.ts new file mode 100644 index 000000000..88849533c --- /dev/null +++ b/script/deploy/comptroller/diamond.ts @@ -0,0 +1,82 @@ +import { ethers } from "hardhat"; + +const FacetCutAction = { Add: 0, Replace: 1, Remove: 2 }; + +// get function selectors from ABI +function getSelectors(contract: any) { + const signatures = Object.keys(contract.interface.functions); + const selectors: any = signatures.reduce((acc: any, val) => { + if (val !== "init(bytes)") { + acc.push(contract.interface.getSighash(val)); + } + return acc; + }, []); + selectors.contract = contract; + selectors.remove = remove; + selectors.get = get; + return selectors; +} + +// get function selector from function signature +function getSelector(func) { + const abiInterface = new ethers.utils.Interface([func]); + return abiInterface.getSighash(ethers.utils.Fragment.from(func)); +} + +// used with getSelectors to remove selectors from an array of selectors +// functionNames argument is an array of function signatures +function remove(functionNames) { + const selectors = this.filter(v => { + for (const functionName of functionNames) { + if (v === this.contract.interface.getSighash(functionName)) { + return false; + } + } + return true; + }); + selectors.contract = this.contract; + selectors.remove = this.remove; + selectors.get = this.get; + return selectors; +} + +// used with getSelectors to get selectors from an array of selectors +// functionNames argument is an array of function signatures +function get(functionNames) { + const selectors = this.filter(v => { + for (const functionName of functionNames) { + if (v === this.contract.interface.getSighash(functionName)) { + return true; + } + } + return false; + }); + selectors.contract = this.contract; + selectors.remove = this.remove; + selectors.get = this.get; + return selectors; +} + +// remove selectors using an array of signatures +function removeSelectors(selectors, signatures) { + const iface = new ethers.utils.Interface(signatures.map(v => "function " + v)); + const removeSelectors = signatures.map(v => iface.getSighash(v)); + selectors = selectors.filter(v => !removeSelectors.includes(v)); + return selectors; +} + +// find a particular address position in the return value of diamondLoupeFacet.facets() +function findAddressPositionInFacets(facetAddress, facets) { + for (let i = 0; i < facets.length; i++) { + if (facets[i].facetAddress === facetAddress) { + return i; + } + } +} + +exports.getSelectors = getSelectors; +exports.getSelector = getSelector; +exports.FacetCutAction = FacetCutAction; +exports.remove = remove; +exports.removeSelectors = removeSelectors; +exports.findAddressPositionInFacets = findAddressPositionInFacets; diff --git a/script/deploy/comptroller/facet-cut-params-generator.ts b/script/deploy/comptroller/facet-cut-params-generator.ts new file mode 100644 index 000000000..00a1e1c67 --- /dev/null +++ b/script/deploy/comptroller/facet-cut-params-generator.ts @@ -0,0 +1,109 @@ +import fs from "fs"; +import { ethers } from "hardhat"; + +import { FacetCutAction, getSelectors } from "./diamond"; + +/** + * This script is used to generate the cut-params which will be used in diamond proxy vip + * to add diamond facets + */ + +// Insert the addresses of the deployed facets to generate thecut params according for the same. +const facetsAddresses = { + MarketFacet: "", + PolicyFacet: "", + RewardFacet: "", + SetterFacet: "", +}; + +// Set actions to the cut params to perform +// i.e. Add, Remove, Replace function selectors in the mapping. +const facetsActions = { + MarketFacet: FacetCutAction.Add, + PolicyFacet: FacetCutAction.Add, + RewardFacet: FacetCutAction.Add, + SetterFacet: FacetCutAction.Add, +}; + +// Set interfaces for the setters to generate function selectors from +const FacetsInterfaces = { + MarketFacet: "IMarketFacet", + PolicyFacet: "IPolicyFacet", + RewardFacet: "IRewardFacet", + SetterFacet: "ISetterFacet", +}; + +// Facets for which cute params need to generate +const FacetNames = ["MarketFacet", "PolicyFacet", "RewardFacet", "SetterFacet"]; + +// Name of the file to write the cut-params +const jsonFileName = "cur-params-test"; + +async function generateCutParams() { + const cut: any = []; + + for (const FacetName of FacetNames) { + const FacetInterface = await ethers.getContractAt(FacetsInterfaces[FacetName], facetsAddresses[FacetName]); + + switch (facetsActions[FacetName]) { + case FacetCutAction.Add: + cut.push({ + facetAddress: facetsAddresses[FacetName], + action: FacetCutAction.Add, + functionSelectors: getSelectors(FacetInterface), + }); + break; + case FacetCutAction.Remove: + cut.push({ + facetAddress: ethers.constants.AddressZero, + action: FacetCutAction.Remove, + functionSelectors: getSelectors(FacetInterface), + }); + break; + case FacetCutAction.Replace: + cut.push({ + facetAddress: facetsAddresses[FacetName], + action: FacetCutAction.Replace, + functionSelectors: getSelectors(FacetInterface), + }); + break; + default: + break; + } + } + + function getFunctionSelector(selectors: any) { + const functionSelector: any = []; + for (let i = 0; i < selectors.length; i++) { + if (selectors[i][0] == "0") { + functionSelector.push(selectors[i]); + } else { + break; + } + } + return functionSelector; + } + + function makeCutParam(cut: any) { + const cutParams = []; + for (let i = 0; i < cut.length; i++) { + const arr: any = new Array(3); + arr[0] = cut[i].facetAddress; + arr[1] = cut[i].action; + arr[2] = getFunctionSelector(cut[i].functionSelectors); + cutParams.push(arr); + } + return cutParams; + } + const cutParams = { cutParams: makeCutParam(cut) }; + + fs.writeFileSync(`./${jsonFileName}.json`, JSON.stringify(cutParams, null, 4)); + return cutParams; +} + +generateCutParams() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); diff --git a/script/hardhat/fork/govUpdateTest.ts b/script/hardhat/fork/govUpdateTest.ts index 0d06697cb..5335ef45a 100644 --- a/script/hardhat/fork/govUpdateTest.ts +++ b/script/hardhat/fork/govUpdateTest.ts @@ -4,7 +4,7 @@ import { expect } from "chai"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; -import { Comptroller, GovernorBravoDelegate, IAccessControlManager } from "../../../typechain"; +import { ComptrollerInterface, GovernorBravoDelegate, IAccessControlManager } from "../../../typechain"; import { TimelockInterface } from "../../../typechain/contracts/Governance/GovernorAlpha2.sol"; import { getCalldatas, setForkBlock } from "./vip-framework/utils"; @@ -51,7 +51,7 @@ const PROPOSAL_TYPES = { CRITICAL: 2, }; -let comptrollerProxy: Comptroller; +let comptrollerProxy: ComptrollerInterface; let accessControl: IAccessControlManager; let governorProxy: GovernorBravoDelegate; let proposer: SignerWithAddress; @@ -74,7 +74,7 @@ const governanceFixture = async (): Promise => { governorAdmin = await initMainnetUser("0x1c2cac6ec528c20800b2fe734820d87b581eaa6b", ethers.utils.parseEther("1.0")); aclAdmin = await initMainnetUser(NORMAL_VIP_TIMELOCK, ethers.utils.parseEther("1.0")); - comptrollerProxy = await ethers.getContractAt("Comptroller", COMPTROLLER_PROXY_MAINNET); + comptrollerProxy = await ethers.getContractAt("ComptrollerInterface", COMPTROLLER_PROXY_MAINNET); const Timelock = await ethers.getContractFactory("Timelock"); timeLockFastTrack = await Timelock.deploy(GOVERNOR_PROXY_MAINNET, TIMELOCK_DELAYS_MAINNET.FAST_TRACK); timeLockCritical = await Timelock.deploy(GOVERNOR_PROXY_MAINNET, TIMELOCK_DELAYS_MAINNET.CRITICAL); diff --git a/script/hardhat/fork/vaiStabilityFeeTest.ts b/script/hardhat/fork/vaiStabilityFeeTest.ts index d995d5278..285200b33 100644 --- a/script/hardhat/fork/vaiStabilityFeeTest.ts +++ b/script/hardhat/fork/vaiStabilityFeeTest.ts @@ -5,7 +5,7 @@ import { BigNumber, BigNumberish } from "ethers"; import { parseEther, parseUnits } from "ethers/lib/utils"; import { ethers, network } from "hardhat"; -import { Comptroller, Comptroller__factory, VAIController, VAIController__factory } from "../../../typechain"; +import { ComptrollerMock, Comptroller__factory, VAIController, VAIController__factory } from "../../../typechain"; import { forking, testVip } from "./vip-framework"; import GOVERNOR_V3_ABI from "./vip-framework/abi/governorV3Abi.json"; import { initMainnetUser } from "./vip-framework/utils"; @@ -122,7 +122,7 @@ forking(24265539, () => { describe("VIP-80 Post-upgrade behavior", async () => { const BLOCKS_PER_YEAR = 10512000n; const interestPerBlock = parseUnits("0.01", 18).div(BLOCKS_PER_YEAR); - let comptroller: Comptroller; + let comptroller: ComptrollerMock; let vaiController: VAIController; let vaiUser: SignerWithAddress; @@ -132,7 +132,7 @@ forking(24265539, () => { }; const postUpgradeFixture = async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER_PROXY); + comptroller = await ethers.getContractAt("ComptrollerMock", COMPTROLLER_PROXY); vaiController = await ethers.getContractAt("VAIController", VAI_CONTROLLER_PROXY); const someVaiUserAddress = "0x5c062b3b0486f61789d680cae37909b92c0dacc5"; vaiUser = await initMainnetUser(someVaiUserAddress, parseEther("1.0")); diff --git a/script/hardhat/fork/vip-101.ts b/script/hardhat/fork/vip-101.ts index 5cb7cf2b7..78407f704 100644 --- a/script/hardhat/fork/vip-101.ts +++ b/script/hardhat/fork/vip-101.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; -import { Comptroller } from "../../../typechain"; +import { ComptrollerInterface } from "../../../typechain"; import { forking, pretendExecutingVip, testVip } from "./vip-framework"; import { ProposalType } from "./vip-framework/types"; import { makeProposal } from "./vip-framework/utils"; @@ -70,10 +70,10 @@ const vip101 = () => { }; forking(26107552, () => { - let comptroller: Comptroller; + let comptroller: ComptrollerInterface; before(async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER); + comptroller = await ethers.getContractAt("ComptrollerInterface", COMPTROLLER); }); describe("PRE-VIP behavior", async () => { @@ -114,10 +114,10 @@ forking(26107552, () => { }); forking(26107552, () => { - let comptroller: Comptroller; + let comptroller: ComptrollerInterface; before(async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER); + comptroller = await ethers.getContractAt("ComptrollerInterface", COMPTROLLER); await pretendExecutingVip(vip101()); }); diff --git a/script/hardhat/fork/vip-103.ts b/script/hardhat/fork/vip-103.ts index 781869480..90155b1be 100644 --- a/script/hardhat/fork/vip-103.ts +++ b/script/hardhat/fork/vip-103.ts @@ -78,7 +78,7 @@ forking(26544741, () => { let comptroller: any; before(async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER); + comptroller = await ethers.getContractAt("ComptrollerInterface", COMPTROLLER); await pretendExecutingVip(vip103()); }); diff --git a/script/hardhat/fork/vip-104.ts b/script/hardhat/fork/vip-104.ts index 9ae333069..a1463668f 100644 --- a/script/hardhat/fork/vip-104.ts +++ b/script/hardhat/fork/vip-104.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; -import { Comptroller } from "../../../typechain"; +import { ComptrollerInterface } from "../../../typechain"; import COMPTROLLER_ABI from "./vip-104/comptroller.json"; import { forking, pretendExecutingVip, testVip } from "./vip-framework"; import { ProposalType } from "./vip-framework/types"; @@ -73,10 +73,10 @@ export const vip104 = () => { }; forking(26881099, () => { - let comptroller: Comptroller; + let comptroller: ComptrollerInterface; before(async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER); + comptroller = await ethers.getContractAt("ComptrollerInterface", COMPTROLLER); }); describe("Pre-VIP behavior", async () => { @@ -130,10 +130,10 @@ forking(26881099, () => { }); forking(26881099, () => { - let comptroller: Comptroller; + let comptroller: ComptrollerInterface; before(async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER); + comptroller = await ethers.getContractAt("ComptrollerInterface", COMPTROLLER); await pretendExecutingVip(vip104()); }); diff --git a/script/hardhat/fork/vip-98.ts b/script/hardhat/fork/vip-98.ts index 62ebcade6..96b261071 100644 --- a/script/hardhat/fork/vip-98.ts +++ b/script/hardhat/fork/vip-98.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; -import { Comptroller, IERC20Upgradeable, PriceOracle, VBep20 } from "../../../typechain"; +import { ComptrollerInterface, IERC20Upgradeable, PriceOracle, VBep20 } from "../../../typechain"; import { forking, pretendExecutingVip, testVip } from "./vip-framework"; import { ProposalType } from "./vip-framework/types"; import { makeProposal } from "./vip-framework/utils"; @@ -144,14 +144,14 @@ forking(25892445, () => { }); forking(25892445, () => { - let comptroller: Comptroller; + let comptroller: ComptrollerInterface; let trx: IERC20Upgradeable; let vTrxOld: VBep20; let vTrx: VBep20; let oracle: PriceOracle; before(async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER); + comptroller = await ethers.getContractAt("ComptrollerInterface", COMPTROLLER); const oracleAddress = await comptroller.oracle(); oracle = await ethers.getContractAt("PriceOracle", oracleAddress); trx = await ethers.getContractAt("IERC20Upgradeable", NEW_TRX); diff --git a/script/hardhat/fork/vip-99.ts b/script/hardhat/fork/vip-99.ts index c4c3fc47f..7b0f09fde 100644 --- a/script/hardhat/fork/vip-99.ts +++ b/script/hardhat/fork/vip-99.ts @@ -5,7 +5,7 @@ import { parseEther, parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { - Comptroller, + ComptrollerMock, IERC20Upgradeable, PriceOracle, SwapDebtDelegate, @@ -89,7 +89,7 @@ forking(25918391, () => { // Ressetting the fork to prevent oracle prices from getting stale forking(25918391, () => { - let comptroller: Comptroller; + let comptroller: ComptrollerMock; let busd: IERC20Upgradeable; let usdt: IERC20Upgradeable; let btc: IERC20Upgradeable; @@ -102,7 +102,7 @@ forking(25918391, () => { let oracle: PriceOracle; before(async () => { - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER); + comptroller = await ethers.getContractAt("ComptrollerMock", COMPTROLLER); [vBUSD, vUSDC, vUSDT, vBTC, vETH] = await Promise.all( [VBUSD, VUSDC, VUSDT, VBTC, VETH].map((address: string) => { return ethers.getContractAt("VBep20Delegate", address); diff --git a/script/hardhat/fork/vip-framework/utils.ts b/script/hardhat/fork/vip-framework/utils.ts index a32671ab4..f263ac714 100644 --- a/script/hardhat/fork/vip-framework/utils.ts +++ b/script/hardhat/fork/vip-framework/utils.ts @@ -3,6 +3,7 @@ import { impersonateAccount, setBalance } from "@nomicfoundation/hardhat-network import { NumberLike } from "@nomicfoundation/hardhat-network-helpers/dist/src/types"; import { expect } from "chai"; import { ContractInterface, TransactionResponse } from "ethers"; +import { ParamType } from "ethers/lib/utils"; import { ethers, network } from "hardhat"; import { Command, Proposal, ProposalMeta, ProposalType } from "./types"; @@ -28,22 +29,23 @@ export function getCalldatas({ signatures, params }: { signatures: string[]; par }); } -const getArgs = (func: string) => { - if (func === "") return []; - // First match everything inside the function argument parens. - const match = func.match(/.*?\(([^)]*)\)/); - const args = match ? match[1] : ""; - // Split the arguments string into an array comma delimited. - return args - .split(",") - .map(arg => { - // Ensure no inline comments are parsed and trim the whitespace. - return arg.replace(/\/\*.*\*\//, "").trim(); - }) - .filter(arg => { - // Ensure no undefined values are added. - return arg; - }); +const formatParamType = (paramType: ParamType): string => { + if (paramType.type === "tuple") { + return `tuple(${paramType.components.map(formatParamType).join(", ")})`; + } + + if (paramType.type === "tuple[]") { + return `tuple(${paramType.components.map(formatParamType).join(", ")})[]`; + } + + return paramType.type; +}; + +const getArgs = (signature: string) => { + if (signature === "") return []; + const fragment = ethers.utils.FunctionFragment.from(signature); + + return fragment.inputs.map(formatParamType); }; export const initMainnetUser = async (user: string, balance: NumberLike) => { @@ -67,7 +69,7 @@ export const setMaxStalePeriodInOracle = async ( comptrollerAddress: string, maxStalePeriodInSeconds: number = 31536000 /* 1 year */, ) => { - const comptroller = await ethers.getContractAt("Comptroller", comptrollerAddress); + const comptroller = await ethers.getContractAt("ComptrollerInterface", comptrollerAddress); const oracle = await ethers.getContractAt("VenusChainlinkOracle", await comptroller.oracle()); const oracleAdmin = await initMainnetUser(await oracle.admin(), ethers.utils.parseEther("1.0")); diff --git a/tests/hardhat/Comptroller/XVSSpeeds.ts b/tests/hardhat/Comptroller/Diamond/XVSSpeeds.ts similarity index 64% rename from tests/hardhat/Comptroller/XVSSpeeds.ts rename to tests/hardhat/Comptroller/Diamond/XVSSpeeds.ts index b9c066d15..6b31f18c1 100644 --- a/tests/hardhat/Comptroller/XVSSpeeds.ts +++ b/tests/hardhat/Comptroller/Diamond/XVSSpeeds.ts @@ -1,30 +1,36 @@ -import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; +import { FakeContract, smock } from "@defi-wonderland/smock"; import chai from "chai"; import { ethers, network } from "hardhat"; -import { Comptroller, Comptroller__factory, IAccessControlManager, VToken } from "../../../typechain"; -import { convertToUnit } from "./../../../helpers/utils"; +import { convertToUnit } from "../../../../helpers/utils"; +import { ComptrollerMock, IAccessControlManager, Unitroller, VToken } from "../../../../typechain"; +import { deployDiamond } from "./scripts/deploy"; const { expect } = chai; chai.use(smock.matchers); describe("Comptroller", () => { - let comptroller: MockContract; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; let accessControl: FakeContract; let vToken1: FakeContract; let vToken2: FakeContract; beforeEach(async () => { - const ComptrollerFactory = await smock.mock("Comptroller"); - comptroller = await ComptrollerFactory.deploy(); - accessControl = await smock.fake("AccessControlManager"); - vToken1 = await smock.fake("VToken"); - vToken2 = await smock.fake("VToken"); + const result = await deployDiamond(""); + unitroller = result.unitroller; - await accessControl.isAllowedToCall.returns(true); - await vToken1.isVToken.returns(true); - await vToken2.isVToken.returns(true); - await vToken1.borrowIndex.returns(convertToUnit(0.7, 18)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + accessControl = await smock.fake( + "contracts/Governance/IAccessControlManager.sol:IAccessControlManager", + ); + vToken1 = await smock.fake("contracts/Tokens/VTokens/VToken.sol:VToken"); + vToken2 = await smock.fake("contracts/Tokens/VTokens/VToken.sol:VToken"); + + accessControl.isAllowedToCall.returns(true); + vToken1.isVToken.returns(true); + vToken2.isVToken.returns(true); + vToken1.borrowIndex.returns(convertToUnit(0.7, 18)); await comptroller._setAccessControl(accessControl.address); await comptroller._supportMarket(vToken1.address); @@ -33,11 +39,10 @@ describe("Comptroller", () => { describe("_initializeMarket", () => { it("Supply and borrow state after initializing the market in the pool", async () => { - const supplyRate = await (await comptroller.venusSupplyState(vToken1.address)).toString().split(",")[0]; - const borrowRate = await (await comptroller.venusBorrowState(vToken1.address)).toString().split(",")[0]; - - expect(supplyRate).equal(convertToUnit(1, 36)); - expect(borrowRate).equal(convertToUnit(1, 36)); + const borrowRate = await comptroller.venusSupplyState(vToken1.address); + const supplyRate = await comptroller.venusBorrowState(vToken1.address); + expect(supplyRate.index).equal(convertToUnit(1, 36)); + expect(borrowRate.index).equal(convertToUnit(1, 36)); }); }); @@ -104,20 +109,24 @@ describe("Comptroller", () => { // Mining 1000 blocks await network.provider.send("hardhat_mine", ["0x3e8", "0x3c"]); // Getting the last block for the updated speeds - const supplySpeedBlock1 = await (await comptroller.venusSupplyState(vToken1.address)).toString().split(",")[1]; - const borrowSpeedBlock1 = await (await comptroller.venusBorrowState(vToken1.address)).toString().split(",")[1]; + const supplySpeedBlock1 = await comptroller.venusSupplyState(vToken1.address); + const borrowSpeedBlock1 = await comptroller.venusBorrowState(vToken1.address); // Updating the speeds to non-zero await comptroller._setVenusSpeeds([vToken1.address], [convertToUnit(1, 16)], [convertToUnit(1, 20)]); // Getting the last block for the updated speeds - const supplySpeedBlock2 = await (await comptroller.venusSupplyState(vToken1.address)).toString().split(",")[1]; - const borrowSpeedBlock2 = await (await comptroller.venusBorrowState(vToken1.address)).toString().split(",")[1]; + const supplySpeedBlock2 = await comptroller.venusSupplyState(vToken1.address); + const borrowSpeedBlock2 = await comptroller.venusBorrowState(vToken1.address); // latest Block const blockNumber2 = await ethers.provider.getBlock("latest"); - expect(blockNumber2.number - blockNumber1.number).equal(Number(supplySpeedBlock2) - Number(supplySpeedBlock1)); - expect(blockNumber2.number - blockNumber1.number).equal(Number(borrowSpeedBlock2) - Number(borrowSpeedBlock1)); + expect(blockNumber2.number - blockNumber1.number).equal( + Number(supplySpeedBlock2.block) - Number(supplySpeedBlock1.block), + ); + expect(blockNumber2.number - blockNumber1.number).equal( + Number(borrowSpeedBlock2.block) - Number(borrowSpeedBlock1.block), + ); }); }); }); diff --git a/tests/hardhat/Comptroller/accessControl.ts b/tests/hardhat/Comptroller/Diamond/accessControl.ts similarity index 66% rename from tests/hardhat/Comptroller/accessControl.ts rename to tests/hardhat/Comptroller/Diamond/accessControl.ts index d6cda8f8c..bf0c42e2c 100644 --- a/tests/hardhat/Comptroller/accessControl.ts +++ b/tests/hardhat/Comptroller/Diamond/accessControl.ts @@ -1,44 +1,56 @@ -import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; +import { FakeContract, smock } from "@defi-wonderland/smock"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import chai from "chai"; -import { Signer, constants } from "ethers"; +import { constants } from "ethers"; import { ethers } from "hardhat"; -import { Comptroller, Comptroller__factory, IAccessControlManager } from "../../../typechain"; +import { ComptrollerMock, IAccessControlManager } from "../../../typechain"; +import { deployDiamond } from "./scripts/deploy"; const { expect } = chai; chai.use(smock.matchers); describe("Comptroller", () => { - let user: Signer; + let user: SignerWithAddress; let userAddress: string; - let comptroller: MockContract; + let unitroller: ComptrollerMock; let accessControl: FakeContract; + let comptroller: ComptrollerMock; beforeEach(async () => { const signers = await ethers.getSigners(); user = signers[1]; - userAddress = await user.getAddress(); - const ComptrollerFactory = await smock.mock("Comptroller"); - comptroller = await ComptrollerFactory.deploy(); - accessControl = await smock.fake("AccessControlManager"); + userAddress = user.address; + accessControl = await smock.fake( + "contracts/Governance/IAccessControlManager.sol:IAccessControlManager", + ); + const result = await deployDiamond(""); + unitroller = result.unitroller; + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); }); describe("_setAccessControlManager", () => { it("Reverts if called by non-admin", async () => { - await expect(comptroller.connect(user)._setAccessControl(accessControl.address)).to.be.revertedWith( - "only admin can", - ); + await expect(comptroller.connect(user)._setAccessControl(userAddress)).to.be.revertedWith("only admin can"); }); it("Reverts if ACM is zero address", async () => { - await expect(comptroller._setAccessControl(constants.AddressZero)).to.be.revertedWith("can't be zero address"); + await expect(comptroller._setAccessControl(constants.AddressZero)).to.be.revertedWith( + "old address is same as new address", + ); }); it("Sets ACM address in storage", async () => { - await expect(await comptroller._setAccessControl(accessControl.address)) - .to.emit(comptroller, "NewAccessControl") + expect(await comptroller._setAccessControl(accessControl.address)) + .to.emit(unitroller, "NewAccessControl") .withArgs(constants.AddressZero, accessControl.address); - expect(await comptroller.getVariable("accessControl")).to.equal(accessControl.address); + }); + + it("should revert on same value", async () => { + await comptroller._setAccessControl(accessControl.address); + await expect(comptroller._setAccessControl(accessControl.address)).to.be.revertedWith( + "old address is same as new address", + ); }); }); @@ -50,20 +62,28 @@ describe("Comptroller", () => { describe("setCollateralFactor", () => { it("Should have AccessControl", async () => { await expect( - comptroller.connect(user)._setCollateralFactor(ethers.constants.AddressZero, 0), + comptroller.connect(user)._setCollateralFactor(ethers.constants.AddressZero, 1), ).to.be.revertedWith("access denied"); expect(accessControl.isAllowedToCall).to.be.calledOnceWith( userAddress, "_setCollateralFactor(address,uint256)", ); }); + + it("Should revert for same values", async () => { + await expect(comptroller._setCollateralFactor(ethers.constants.AddressZero, 0)).to.be.revertedWith( + "old value is same as new value", + ); + }); }); + describe("setLiquidationIncentive", () => { it("Should have AccessControl", async () => { - await expect(comptroller.connect(user)._setLiquidationIncentive(0)).to.be.revertedWith("access denied"); + await expect(comptroller.connect(user)._setLiquidationIncentive(1)).to.be.revertedWith("access denied"); expect(accessControl.isAllowedToCall).to.be.calledOnceWith(userAddress, "_setLiquidationIncentive(uint256)"); }); }); + describe("setMarketBorrowCaps", () => { it("Should have AccessControl", async () => { await expect(comptroller.connect(user)._setMarketBorrowCaps([], [])).to.be.revertedWith("access denied"); @@ -94,7 +114,7 @@ describe("Comptroller", () => { await expect(comptroller.connect(user)._setActionsPaused([], [], true)).to.be.revertedWith("access denied"); expect(accessControl.isAllowedToCall).to.be.calledOnceWith( userAddress, - "_setActionsPaused(address[],uint256[],bool)", + "_setActionsPaused(address[],uint8[],bool)", ); }); }); diff --git a/tests/hardhat/Comptroller/assetListTest.ts b/tests/hardhat/Comptroller/Diamond/assetListTest.ts similarity index 79% rename from tests/hardhat/Comptroller/assetListTest.ts rename to tests/hardhat/Comptroller/Diamond/assetListTest.ts index 6fbf2ec61..3d74e3043 100644 --- a/tests/hardhat/Comptroller/assetListTest.ts +++ b/tests/hardhat/Comptroller/Diamond/assetListTest.ts @@ -1,30 +1,32 @@ import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; import { loadFixture, setBalance } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import chai from "chai"; +import { Signer } from "ethers"; import { ethers } from "hardhat"; -import { convertToUnit } from "../../../helpers/utils"; +import { convertToUnit } from "../../../../helpers/utils"; import { - Comptroller, ComptrollerLens, ComptrollerLens__factory, - Comptroller__factory, + ComptrollerMock, IAccessControlManager, PriceOracle, + Unitroller, VBep20Immutable, -} from "../../../typechain"; -import { ComptrollerErrorReporter } from "../util/Errors"; +} from "../../../../typechain"; +import { ComptrollerErrorReporter } from "../../util/Errors"; +import { deployDiamond } from "./scripts/deploy"; const { expect } = chai; chai.use(smock.matchers); const { Error } = ComptrollerErrorReporter; -describe("assetListTest", () => { - let root: SignerWithAddress; // eslint-disable-line @typescript-eslint/no-unused-vars - let customer: SignerWithAddress; - let comptroller: MockContract; +describe("Comptroller: assetListTest", () => { + let root: Signer; // eslint-disable-line @typescript-eslint/no-unused-vars + let customer: Signer; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; let OMG: FakeContract; let ZRX: FakeContract; let BAT: FakeContract; @@ -32,7 +34,7 @@ describe("assetListTest", () => { let allTokens: FakeContract[]; type AssetListFixture = { - comptroller: MockContract; + unitroller: MockContract; comptrollerLens: MockContract; oracle: FakeContract; OMG: FakeContract; @@ -44,20 +46,26 @@ describe("assetListTest", () => { }; async function assetListFixture(): Promise { - const accessControl = await smock.fake("AccessControlManager"); - const ComptrollerFactory = await smock.mock("Comptroller"); + const accessControl = await smock.fake( + "contracts/Governance/IAccessControlManager.sol:IAccessControlManager", + ); + // const ComptrollerFactory = await smock.mock("ComptrollerMock"); const ComptrollerLensFactory = await smock.mock("ComptrollerLens"); - const comptroller = await ComptrollerFactory.deploy(); + const result = await deployDiamond(""); + unitroller = result.unitroller; const comptrollerLens = await ComptrollerLensFactory.deploy(); - const oracle = await smock.fake("PriceOracle"); + const oracle = await smock.fake("contracts/Oracle/PriceOracle.sol:PriceOracle"); accessControl.isAllowedToCall.returns(true); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); await comptroller._setAccessControl(accessControl.address); await comptroller._setComptrollerLens(comptrollerLens.address); await comptroller._setPriceOracle(oracle.address); const names = ["OMG", "ZRX", "BAT", "sketch"]; const [OMG, ZRX, BAT, SKT] = await Promise.all( names.map(async name => { - const vToken = await smock.fake("VBep20Immutable"); + const vToken = await smock.fake( + "contracts/Tokens/VTokens/VBep20Immutable.sol:VBep20Immutable", + ); if (name !== "sketch") { await comptroller._supportMarket(vToken.address); } @@ -65,7 +73,7 @@ describe("assetListTest", () => { }), ); const allTokens = [OMG, ZRX, BAT, SKT]; - return { comptroller, comptrollerLens, oracle, OMG, ZRX, BAT, SKT, allTokens, names }; + return { unitroller, comptrollerLens, oracle, OMG, ZRX, BAT, SKT, allTokens, names }; } function configure({ oracle, allTokens, names }: AssetListFixture) { @@ -82,14 +90,14 @@ describe("assetListTest", () => { [root, customer] = await ethers.getSigners(); const contracts = await loadFixture(assetListFixture); configure(contracts); - ({ comptroller, OMG, ZRX, BAT, SKT, allTokens } = contracts); + ({ unitroller, OMG, ZRX, BAT, SKT, allTokens } = contracts); }); async function checkMarkets(expectedTokens: FakeContract[]) { // eslint-disable-next-line prefer-const for (let token of allTokens) { const isExpected = expectedTokens.some(e => e == token); - expect(await comptroller.checkMembership(customer.address, token.address)).to.equal(isExpected); + expect(await comptroller.checkMembership(await customer.getAddress(), token.address)).to.equal(isExpected); } } @@ -100,13 +108,16 @@ describe("assetListTest", () => { ) { const reply = await comptroller.connect(customer).callStatic.enterMarkets(enterTokens.map(t => t.address)); const receipt = await comptroller.connect(customer).enterMarkets(enterTokens.map(t => t.address)); - const assetsIn = await comptroller.getAssetsIn(customer.address); + + const assetsIn = await comptroller.getAssetsIn(await customer.getAddress()); const expectedErrors_ = expectedErrors || enterTokens.map(_ => Error.NO_ERROR); reply.forEach((tokenReply, i) => { expect(tokenReply).to.equal(expectedErrors_[i]); }); + + expect(receipt).to.emit(unitroller, "MarketEntered"); expect(assetsIn).to.deep.equal(expectedTokens.map(t => t.address)); await checkMarkets(expectedTokens); @@ -127,9 +138,11 @@ describe("assetListTest", () => { ) { const reply = await comptroller.connect(customer).callStatic.exitMarket(exitToken.address); const receipt = await comptroller.connect(customer).exitMarket(exitToken.address); - const assetsIn = await comptroller.getAssetsIn(customer.address); + const assetsIn = await comptroller.getAssetsIn(await customer.getAddress()); + expect(reply).to.equal(expectedError); expect(assetsIn).to.deep.equal(expectedTokens.map(t => t.address)); + await checkMarkets(expectedTokens); return receipt; } @@ -138,9 +151,9 @@ describe("assetListTest", () => { it("properly emits events", async () => { const tx1 = await enterAndCheckMarkets([OMG], [OMG]); const tx2 = await enterAndCheckMarkets([OMG], [OMG]); - await expect(tx1).to.emit(comptroller, "MarketEntered").withArgs(OMG.address, customer.address); - console.log("D"); - expect((await tx2.wait()).events).to.be.empty; + expect(tx1).to.emit(unitroller, "MarketEntered").withArgs(OMG.address, customer); + const tx2Value = await tx2.wait(); + expect(tx2Value.events?.length).to.be.equals(0); }); it("adds to the asset list only once", async () => { @@ -229,9 +242,9 @@ describe("assetListTest", () => { it("enters when called by a vtoken", async () => { await setBalance(await BAT.wallet.getAddress(), 10n ** 18n); - await comptroller.connect(BAT.wallet).borrowAllowed(BAT.address, customer.address, 1); + await comptroller.connect(BAT.wallet).borrowAllowed(BAT.address, await customer.getAddress(), 1); - const assetsIn = await comptroller.getAssetsIn(customer.address); + const assetsIn = await comptroller.getAssetsIn(await customer.getAddress()); expect(assetsIn).to.deep.equal([BAT.address]); @@ -239,11 +252,11 @@ describe("assetListTest", () => { }); it("reverts when called by not a vtoken", async () => { - await expect(comptroller.connect(customer).borrowAllowed(BAT.address, customer.address, 1)).to.be.revertedWith( - "sender must be vToken", - ); + await expect( + comptroller.connect(customer).borrowAllowed(BAT.address, await customer.getAddress(), 1), + ).to.be.revertedWith("sender must be vToken"); - const assetsIn = await comptroller.getAssetsIn(customer.address); + const assetsIn = await comptroller.getAssetsIn(await customer.getAddress()); expect(assetsIn).to.deep.equal([]); @@ -252,12 +265,12 @@ describe("assetListTest", () => { it("adds to the asset list only once", async () => { await setBalance(await BAT.wallet.getAddress(), 10n ** 18n); - await comptroller.connect(BAT.wallet).borrowAllowed(BAT.address, customer.address, 1); + await comptroller.connect(BAT.wallet).borrowAllowed(BAT.address, await customer.getAddress(), 1); await enterAndCheckMarkets([BAT], [BAT]); - await comptroller.connect(BAT.wallet).borrowAllowed(BAT.address, customer.address, 1); - const assetsIn = await comptroller.getAssetsIn(customer.address); + await comptroller.connect(BAT.wallet).borrowAllowed(BAT.address, await customer.getAddress(), 1); + const assetsIn = await comptroller.getAssetsIn(await customer.getAddress()); expect(assetsIn).to.deep.equal([BAT.address]); }); }); diff --git a/tests/hardhat/Comptroller/comptrollerTest.ts b/tests/hardhat/Comptroller/Diamond/comptrollerTest.ts similarity index 64% rename from tests/hardhat/Comptroller/comptrollerTest.ts rename to tests/hardhat/Comptroller/Diamond/comptrollerTest.ts index 65fbcbda0..4215144c2 100644 --- a/tests/hardhat/Comptroller/comptrollerTest.ts +++ b/tests/hardhat/Comptroller/Diamond/comptrollerTest.ts @@ -5,19 +5,20 @@ import chai from "chai"; import { constants } from "ethers"; import { ethers } from "hardhat"; -import { convertToUnit } from "../../../helpers/utils"; +import { convertToUnit } from "../../../../helpers/utils"; import { - Comptroller, ComptrollerLens, ComptrollerLens__factory, - Comptroller__factory, + ComptrollerMock, EIP20Interface, IAccessControlManager, PriceOracle, + Unitroller, VAIController, VToken, -} from "../../../typechain"; -import { ComptrollerErrorReporter } from "../util/Errors"; +} from "../../../../typechain"; +import { ComptrollerErrorReporter } from "../../util/Errors"; +import { deployDiamond } from "./scripts/deploy"; const { expect } = chai; chai.use(smock.matchers); @@ -26,30 +27,37 @@ type SimpleComptrollerFixture = { oracle: FakeContract; accessControl: FakeContract; comptrollerLens: MockContract; - comptroller: MockContract; + unitroller: Unitroller; + comptroller: ComptrollerMock; }; async function deploySimpleComptroller(): Promise { - const oracle = await smock.fake("PriceOracle"); - const accessControl = await smock.fake("AccessControlManager"); + const oracle = await smock.fake("contracts/Oracle/PriceOracle.sol:PriceOracle"); + const accessControl = await smock.fake( + "contracts/Governance/IAccessControlManager.sol:IAccessControlManager", + ); accessControl.isAllowedToCall.returns(true); const ComptrollerLensFactory = await smock.mock("ComptrollerLens"); - const ComptrollerFactory = await smock.mock("Comptroller"); - const comptroller = await ComptrollerFactory.deploy(); + // const ComptrollerFactory = await smock.mock("ComptrollerMock"); + const result = await deployDiamond(""); + const unitroller = result.unitroller; + const comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); const comptrollerLens = await ComptrollerLensFactory.deploy(); await comptroller._setAccessControl(accessControl.address); await comptroller._setComptrollerLens(comptrollerLens.address); await comptroller._setPriceOracle(oracle.address); await comptroller._setLiquidationIncentive(convertToUnit("1", 18)); - return { oracle, comptroller, comptrollerLens, accessControl }; + return { oracle, comptroller, unitroller, comptrollerLens, accessControl }; } function configureOracle(oracle: FakeContract) { oracle.getUnderlyingPrice.returns(convertToUnit(1, 18)); } -function configureVToken(vToken: FakeContract, comptroller: MockContract) { - vToken.comptroller.returns(comptroller.address); +async function configureVToken(vToken: FakeContract, unitroller: MockContract) { + const result = await deployDiamond(""); + unitroller = result.unitroller; + vToken.comptroller.returns(unitroller.address); vToken.isVToken.returns(true); vToken.exchangeRateStored.returns(convertToUnit("2", 18)); vToken.totalSupply.returns(convertToUnit("1000000", 18)); @@ -59,11 +67,20 @@ function configureVToken(vToken: FakeContract, comptroller: MockContract describe("Comptroller", () => { let root: SignerWithAddress; let accounts: SignerWithAddress[]; + let comptroller: ComptrollerMock; before(async () => { [root, ...accounts] = await ethers.getSigners(); }); + type FuncNames = keyof ComptrollerMock["functions"]; + + function testZeroAddress(funcName: Func, args: Parameters) { + it(funcName, async () => { + await expect(comptroller[funcName](...args)).to.be.revertedWith("can't be zero address"); + }); + } + describe("constructor", () => { it("on success it sets admin to creator and pendingAdmin is unset", async () => { const { comptroller } = await loadFixture(deploySimpleComptroller); @@ -73,13 +90,15 @@ describe("Comptroller", () => { }); describe("_setLiquidationIncentive", () => { - let comptroller: MockContract; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; const initialIncentive = convertToUnit("1", 18); const validIncentive = convertToUnit("1.1", 18); const tooSmallIncentive = convertToUnit("0.99999", 18); beforeEach(async () => { - ({ comptroller } = await loadFixture(deploySimpleComptroller)); + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); }); it("fails if incentive is less than 1e18", async () => { @@ -90,39 +109,141 @@ describe("Comptroller", () => { expect(await comptroller.callStatic._setLiquidationIncentive(validIncentive)).to.equal( ComptrollerErrorReporter.Error.NO_ERROR, ); - await expect(await comptroller._setLiquidationIncentive(validIncentive)) - .to.emit(comptroller, "NewLiquidationIncentive") + expect(await comptroller._setLiquidationIncentive(validIncentive)) + .to.emit(unitroller, "NewLiquidationIncentive") .withArgs(initialIncentive, validIncentive); expect(await comptroller.liquidationIncentiveMantissa()).to.equal(validIncentive); }); + + it("should revert on same values", async () => { + await comptroller._setLiquidationIncentive(validIncentive); + await expect(comptroller._setLiquidationIncentive(validIncentive)).to.be.revertedWith( + "old value is same as new value", + ); + }); }); - describe("Non zero address check", () => { - let comptroller: MockContract; + describe("_setVenusVAIVaultRate", () => { + let unitroller: Unitroller; + let comptroller: ComptrollerMock; beforeEach(async () => { - ({ comptroller } = await loadFixture(deploySimpleComptroller)); + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + }); + + it("should revert on same values", async () => { + await expect(comptroller._setVenusVAIVaultRate(0)).to.be.revertedWith("old value is same as new value"); }); + }); - type FuncNames = keyof Comptroller["functions"]; + describe("_setVAIVaultInfo", () => { + let unitroller: Unitroller; + let comptroller: ComptrollerMock; - function testZeroAddress(funcName: Func, args: Parameters) { - it(funcName, async () => { - await expect(comptroller[funcName](...args)).to.be.revertedWith("can't be zero address"); - }); - } - testZeroAddress("_setPriceOracle", [constants.AddressZero]); - testZeroAddress("_setCollateralFactor", [constants.AddressZero, 0]); - testZeroAddress("_setPauseGuardian", [constants.AddressZero]); - testZeroAddress("_setVAIController", [constants.AddressZero]); - testZeroAddress("_setTreasuryData", [constants.AddressZero, constants.AddressZero, 0]); - testZeroAddress("_setComptrollerLens", [constants.AddressZero]); - testZeroAddress("_setVAIVaultInfo", [constants.AddressZero, 0, 0]); - testZeroAddress("_setVenusSpeeds", [[constants.AddressZero], [0], [0]]); + beforeEach(async () => { + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + }); + + it("should revert on same values", async () => { + await expect(comptroller._setVAIVaultInfo(constants.AddressZero, 0, 0)).to.be.revertedWith( + "old address is same as new address", + ); + await comptroller._setVAIVaultInfo(accounts[0].address, 0, 0); + testZeroAddress("_setVAIVaultInfo", [constants.AddressZero, 0, 0]); + }); + }); + + describe("_setVAIController", () => { + let unitroller: Unitroller; + let comptroller: ComptrollerMock; + + beforeEach(async () => { + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + }); + + it("should revert on same values", async () => { + await expect(comptroller._setVAIController(constants.AddressZero)).to.be.revertedWith( + "old address is same as new address", + ); + await comptroller._setVAIController(accounts[0].address); + testZeroAddress("_setVAIController", [constants.AddressZero]); + }); + }); + + describe("_setVAIMintRate", () => { + let unitroller: Unitroller; + let comptroller: ComptrollerMock; + + beforeEach(async () => { + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + }); + + it("should revert on same values", async () => { + await expect(comptroller._setVAIMintRate(0)).to.be.revertedWith("old value is same as new value"); + }); + }); + + describe("_setLiquidatorContract", () => { + let unitroller: Unitroller; + let comptroller: ComptrollerMock; + + beforeEach(async () => { + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + }); + + it("should revert on same values", async () => { + await expect(comptroller._setLiquidatorContract(constants.AddressZero)).to.be.revertedWith( + "old address is same as new address", + ); + }); + + it("should revert on zero address", async () => { + await comptroller._setLiquidatorContract(accounts[0].address); + await expect(comptroller._setLiquidatorContract(constants.AddressZero)).to.be.revertedWith( + "can't be zero address", + ); + }); + }); + + describe("_setPauseGuardian", () => { + let unitroller: Unitroller; + let comptroller: ComptrollerMock; + + beforeEach(async () => { + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + }); + + it("should revert on same values", async () => { + await expect(comptroller._setPauseGuardian(constants.AddressZero)).to.be.revertedWith( + "old address is same as new address", + ); + await comptroller._setPauseGuardian(accounts[0].address); + testZeroAddress("_setPauseGuardian", [constants.AddressZero]); + }); + }); + + describe("_setVenusSpeeds", () => { + let unitroller: Unitroller; + + beforeEach(async () => { + ({ unitroller } = await loadFixture(deploySimpleComptroller)); + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); + }); + + it("ensure non zero address for venus speeds", async () => { + testZeroAddress("_setVenusSpeeds", [[constants.AddressZero], [0], [0]]); + }); }); describe("_setPriceOracle", () => { - let comptroller: MockContract; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; let oracle: FakeContract; let newOracle: FakeContract; @@ -132,7 +253,8 @@ describe("Comptroller", () => { async function deploy(): Promise { const contracts = await deploySimpleComptroller(); - const newOracle = await smock.fake("PriceOracle"); + const newOracle = await smock.fake("contracts/Oracle/PriceOracle.sol:PriceOracle"); + // comptroller = await ethers.getContractAt("ComptrollerMock", contracts.unitroller); return { ...contracts, newOracle }; } @@ -141,35 +263,46 @@ describe("Comptroller", () => { }); it("fails if called by non-admin", async () => { - await expect(comptroller.connect(accounts[0])._setPriceOracle(oracle.address)).to.be.revertedWith( + await expect(comptroller.connect(accounts[0])._setPriceOracle(newOracle.address)).to.be.revertedWith( "only admin can", ); expect(await comptroller.oracle()).to.equal(oracle.address); }); it("accepts a valid price oracle and emits a NewPriceOracle event", async () => { - await expect(await comptroller._setPriceOracle(newOracle.address)) - .to.emit(comptroller, "NewPriceOracle") + expect(await comptroller._setPriceOracle(newOracle.address)) + .to.emit(unitroller, "NewPriceOracle") .withArgs(oracle.address, newOracle.address); expect(await comptroller.oracle()).to.equal(newOracle.address); }); + + it("Should revert on same values", async () => { + await expect(comptroller._setPriceOracle(oracle.address)).to.be.revertedWith( + "old address is same as new address", + ); + testZeroAddress("_setPriceOracle", [constants.AddressZero]); + }); }); describe("_setComptrollerLens", () => { - let comptroller: MockContract; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; let comptrollerLens: MockContract; type Contracts = { + unitroller: Unitroller; + comptroller: ComptrollerMock; comptrollerLens: MockContract; - comptroller: MockContract; }; async function deploy(): Promise { - const ComptrollerFactory = await smock.mock("Comptroller"); - const comptroller = await ComptrollerFactory.deploy(); + // const ComptrollerFactory = await smock.mock("ComptrollerMock"); + const result = await deployDiamond(""); + unitroller = result.unitroller; + const comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); const ComptrollerLensFactory = await smock.mock("ComptrollerLens"); const comptrollerLens = await ComptrollerLensFactory.deploy(); - return { comptroller, comptrollerLens }; + return { unitroller, comptroller, comptrollerLens }; } beforeEach(async () => { @@ -185,14 +318,24 @@ describe("Comptroller", () => { it("should fire an event", async () => { const { comptroller, comptrollerLens } = await loadFixture(deploy); const oldComptrollerLensAddress = await comptroller.comptrollerLens(); - await expect(await comptroller._setComptrollerLens(comptrollerLens.address)) - .to.emit(comptroller, "NewComptrollerLens") + expect(await comptroller._setComptrollerLens(comptrollerLens.address)) + .to.emit(unitroller, "NewComptrollerLens") .withArgs(oldComptrollerLensAddress, comptrollerLens.address); }); + + it("should revert on same value", async () => { + const { comptroller, comptrollerLens } = await loadFixture(deploy); + await comptroller._setComptrollerLens(comptrollerLens.address); + await expect(comptroller._setComptrollerLens(comptrollerLens.address)).to.be.revertedWith( + "old address is same as new address", + ); + testZeroAddress("_setComptrollerLens", [constants.AddressZero]); + }); }); describe("_setCloseFactor", () => { - let comptroller: MockContract; + let comptroller: ComptrollerMock; + let unitroller: Unitroller; beforeEach(async () => { ({ comptroller } = await loadFixture(deploySimpleComptroller)); @@ -201,11 +344,25 @@ describe("Comptroller", () => { it("fails if not called by admin", async () => { await expect(comptroller.connect(accounts[0])._setCloseFactor(1)).to.be.revertedWith("only admin can"); }); + + it("should revert on same values", async () => { + await expect(comptroller._setCloseFactor(0)).to.be.revertedWith("old value is same as new value"); + }); + + it("fails if factor is set out of range", async () => { + expect(await comptroller._setCloseFactor(convertToUnit(1, 18))) + .to.emit(unitroller, "Failure") + .withArgs( + ComptrollerErrorReporter.Error.INVALID_CLOSE_FACTOR, + ComptrollerErrorReporter.FailureInfo.SET_CLOSE_FACTOR_VALIDATION, + ); + }); }); describe("_setCollateralFactor", () => { const half = convertToUnit("0.5", 18); - let comptroller: MockContract; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; let vToken: FakeContract; let oracle: FakeContract; @@ -213,7 +370,7 @@ describe("Comptroller", () => { async function deploy(): Promise { const contracts = await deploySimpleComptroller(); - const vToken = await smock.fake("VToken"); + const vToken = await smock.fake("contracts/Tokens/VTokens/VToken.sol:VToken"); vToken.comptroller.returns(contracts.comptroller.address); vToken.isVToken.returns(true); return { vToken, ...contracts }; @@ -231,21 +388,28 @@ describe("Comptroller", () => { it("fails if factor is set without an underlying price", async () => { await comptroller._supportMarket(vToken.address); oracle.getUnderlyingPrice.returns(0); - await expect(await comptroller._setCollateralFactor(vToken.address, half)) - .to.emit(comptroller, "Failure") + expect(await comptroller._setCollateralFactor(vToken.address, half)) + .to.emit(unitroller, "Failure") .withArgs( ComptrollerErrorReporter.Error.PRICE_ERROR, ComptrollerErrorReporter.FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE, - 0, ); }); it("succeeds and sets market", async () => { await comptroller._supportMarket(vToken.address); - await expect(await comptroller._setCollateralFactor(vToken.address, half)) - .to.emit(comptroller, "NewCollateralFactor") + expect(await comptroller._setCollateralFactor(vToken.address, half)) + .to.emit(unitroller, "NewCollateralFactor") .withArgs(vToken.address, "0", half); }); + + it("should revert on same values", async () => { + await comptroller._supportMarket(vToken.address); + await comptroller._setCollateralFactor(vToken.address, half); + await expect(comptroller._setCollateralFactor(vToken.address, half)).to.be.revertedWith( + "old value is same as new value", + ); + }); }); describe("_setForcedLiquidation", async () => { @@ -306,11 +470,12 @@ describe("Comptroller", () => { }); describe("_supportMarket", () => { - let comptroller: MockContract; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; let oracle: FakeContract; let vToken1: FakeContract; let vToken2: FakeContract; - let token: FakeContract; + let token: FakeContract; //eslint-disable-line type Contracts = SimpleComptrollerFixture & { vToken1: FakeContract; @@ -320,17 +485,17 @@ describe("Comptroller", () => { async function deploy(): Promise { const contracts = await deploySimpleComptroller(); - const vToken1 = await smock.fake("VToken"); - const vToken2 = await smock.fake("VToken"); - const token = await smock.fake("EIP20Interface"); + const vToken1 = await smock.fake("contracts/Tokens/VTokens/VToken.sol:VToken"); + const vToken2 = await smock.fake("contracts/Tokens/VTokens/VToken.sol:VToken"); + const token = await smock.fake("contracts/Tokens/EIP20Interface.sol:EIP20Interface"); return { ...contracts, vToken1, vToken2, token }; } beforeEach(async () => { ({ comptroller, oracle, vToken1, vToken2, token } = await loadFixture(deploy)); configureOracle(oracle); - configureVToken(vToken1, comptroller); - configureVToken(vToken2, comptroller); + configureVToken(vToken1, unitroller); + configureVToken(vToken2, unitroller); }); it("fails if asset is not a VToken", async () => { @@ -338,54 +503,54 @@ describe("Comptroller", () => { }); it("succeeds and sets market", async () => { - await expect(await comptroller._supportMarket(vToken1.address)) - .to.emit(comptroller, "MarketListed") + expect(await comptroller._supportMarket(vToken1.address)) + .to.emit(unitroller, "MarketListed") .withArgs(vToken1.address); }); it("cannot list a market a second time", async () => { const tx1 = await comptroller._supportMarket(vToken1.address); const tx2 = await comptroller._supportMarket(vToken1.address); - await expect(tx1).to.emit(comptroller, "MarketListed").withArgs(vToken1.address); - await expect(tx2) - .to.emit(comptroller, "Failure") + expect(tx1).to.emit(comptroller, "MarketListed").withArgs(vToken1.address); + expect(tx2) + .to.emit(unitroller, "Failure") .withArgs( ComptrollerErrorReporter.Error.MARKET_ALREADY_LISTED, ComptrollerErrorReporter.FailureInfo.SUPPORT_MARKET_EXISTS, - 0, ); }); it("can list two different markets", async () => { const tx1 = await comptroller._supportMarket(vToken1.address); const tx2 = await comptroller._supportMarket(vToken2.address); - await expect(tx1).to.emit(comptroller, "MarketListed").withArgs(vToken1.address); - await expect(tx2).to.emit(comptroller, "MarketListed").withArgs(vToken2.address); + expect(tx1).to.emit(comptroller, "MarketListed").withArgs(vToken1.address); + expect(tx2).to.emit(unitroller, "MarketListed").withArgs(vToken2.address); }); }); describe("Hooks", () => { - let comptroller: MockContract; + let unitroller: Unitroller; + let comptroller: ComptrollerMock; let vToken: FakeContract; type Contracts = SimpleComptrollerFixture & { vToken: FakeContract }; async function deploy(): Promise { const contracts = await deploySimpleComptroller(); - const vToken = await smock.fake("VToken"); + const vToken = await smock.fake("contracts/Tokens/VTokens/VToken.sol:VToken"); await contracts.comptroller._supportMarket(vToken.address); return { ...contracts, vToken }; } beforeEach(async () => { ({ comptroller, vToken } = await loadFixture(deploy)); - configureVToken(vToken, comptroller); + configureVToken(vToken, unitroller); }); describe("mintAllowed", () => { beforeEach(async () => { ({ comptroller, vToken } = await loadFixture(deploy)); - configureVToken(vToken, comptroller); + configureVToken(vToken, unitroller); }); it("allows minting if cap is not reached", async () => { @@ -417,7 +582,7 @@ describe("Comptroller", () => { }); it("reverts if market is not listed", async () => { - const someVToken = await smock.fake("VToken"); + const someVToken = await smock.fake("contracts/Tokens/VTokens/VToken.sol:VToken"); await expect( comptroller.mintAllowed(someVToken.address, root.address, convertToUnit("1", 18)), ).to.be.revertedWith("market not listed"); diff --git a/tests/hardhat/Comptroller/Diamond/diamond.ts b/tests/hardhat/Comptroller/Diamond/diamond.ts new file mode 100644 index 000000000..29efb2e8f --- /dev/null +++ b/tests/hardhat/Comptroller/Diamond/diamond.ts @@ -0,0 +1,150 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { FacetCutAction, getSelectors } from "../../../../script/deploy/comptroller/diamond"; + +describe("Comptroller", async () => { + let diamond; + let unitroller; + let unitrollerAdmin; + let facetCutParams; + let diamondHarness; + let facet; + + before(async () => { + const UnitrollerFactory = await ethers.getContractFactory("Unitroller"); + unitroller = await UnitrollerFactory.deploy(); + const signer = await ethers.getSigners(); + unitrollerAdmin = signer[0]; + + const diamondFactory = await ethers.getContractFactory("DiamondHarness"); + diamond = await diamondFactory.deploy(); + + await unitroller.connect(unitrollerAdmin)._setPendingImplementation(diamond.address); + await diamond.connect(unitrollerAdmin)._become(unitroller.address); + + const Facet = await ethers.getContractFactory("MarketFacet"); + facet = await Facet.deploy(); + await facet.deployed(); + + const FacetInterface = await ethers.getContractAt("IMarketFacet", facet.address); + + diamondHarness = await ethers.getContractAt("DiamondHarnessInterface", unitroller.address); + facetCutParams = [ + { + facetAddress: facet.address, + action: FacetCutAction.Add, + functionSelectors: getSelectors(FacetInterface), + }, + ]; + }); + + it("Revert on check for the function selector", async () => { + await expect(diamondHarness.getFacetAddress("0xa76b3fda")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0x929fe9a1")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xc2998238")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xede4edd0")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xabfceffc")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0x007e3dd2")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xc488847b")).to.be.revertedWith("Diamond: Function does not exist"); + }); + + it("Add Facet and function selectors to proxy", async () => { + await diamondHarness.connect(unitrollerAdmin).diamondCut(facetCutParams); + + expect(await diamondHarness.getFacetAddress("0xa76b3fda")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0x929fe9a1")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xc2998238")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xede4edd0")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xabfceffc")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0x007e3dd2")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xc488847b")).to.equal(facet.address); + }); + + it("Get all facet function selectors by facet address", async () => { + const functionSelectors = await diamondHarness.facetFunctionSelectors(facetCutParams[0].facetAddress); + expect(functionSelectors).to.deep.equal(facetCutParams[0].functionSelectors); + }); + + it("Get facet position by facet address", async () => { + const facetPosition = await diamondHarness.facetPosition(facetCutParams[0].facetAddress); + expect(facetPosition).to.equal(0); + }); + + it("Get all facet addresses", async () => { + const facetsAddress = await diamondHarness.facetAddresses(); + expect(facetsAddress[0]).to.equal(facetCutParams[0].facetAddress); + }); + + it("Get all facets address and their selectors", async () => { + const facets = await diamondHarness.facets(); + expect(facets[0].facetAddress).to.equal(facetCutParams[0].facetAddress); + }); + + it("Get facet address and position by function selector", async () => { + const facetsAddressAndPosition = await diamondHarness.facetAddress(facetCutParams[0].functionSelectors[1]); + expect(facetsAddressAndPosition.facetAddress).to.equal(facetCutParams[0].facetAddress); + expect(facetsAddressAndPosition.functionSelectorPosition).to.equal(1); + }); + + it("Remove function selector from facet mapping", async () => { + facetCutParams = [ + { + facetAddress: ethers.constants.AddressZero, + action: FacetCutAction.Remove, + functionSelectors: ["0xa76b3fda", "0x929fe9a1"], + }, + ]; + await diamondHarness.connect(unitrollerAdmin).diamondCut(facetCutParams); + + await expect(diamondHarness.getFacetAddress("0xa76b3fda")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0x929fe9a1")).to.be.revertedWith("Diamond: Function does not exist"); + expect(await diamondHarness.getFacetAddress("0xc2998238")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xede4edd0")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xabfceffc")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0x007e3dd2")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xc488847b")).to.equal(facet.address); + }); + + it("Replace the function from facet mapping", async () => { + const Facet = await ethers.getContractFactory("PolicyFacet"); + const newFacet = await Facet.deploy(); + await newFacet.deployed(); + + facetCutParams = [ + { + facetAddress: newFacet.address, + action: FacetCutAction.Replace, + functionSelectors: ["0xc2998238", "0xede4edd0", "0xabfceffc"], + }, + ]; + await diamondHarness.connect(unitrollerAdmin).diamondCut(facetCutParams); + + await expect(diamondHarness.getFacetAddress("0xa76b3fda")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0x929fe9a1")).to.be.revertedWith("Diamond: Function does not exist"); + expect(await diamondHarness.getFacetAddress("0xc2998238")).to.equal(newFacet.address); + expect(await diamondHarness.getFacetAddress("0xede4edd0")).to.equal(newFacet.address); + expect(await diamondHarness.getFacetAddress("0xabfceffc")).to.equal(newFacet.address); + expect(await diamondHarness.getFacetAddress("0x007e3dd2")).to.equal(facet.address); + expect(await diamondHarness.getFacetAddress("0xc488847b")).to.equal(facet.address); + }); + + it("Remove all functions", async () => { + facetCutParams = [ + { + facetAddress: ethers.constants.AddressZero, + action: FacetCutAction.Remove, + functionSelectors: ["0xc2998238", "0xede4edd0", "0xabfceffc", "0x007e3dd2", "0xc488847b"], + }, + ]; + await diamondHarness.connect(unitrollerAdmin).diamondCut(facetCutParams); + + await expect(diamondHarness.getFacetAddress("0xa76b3fda")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0x929fe9a1")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xc2998238")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xede4edd0")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xabfceffc")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0x007e3dd2")).to.be.revertedWith("Diamond: Function does not exist"); + await expect(diamondHarness.getFacetAddress("0xc488847b")).to.be.revertedWith("Diamond: Function does not exist"); + }); +}); diff --git a/tests/hardhat/Comptroller/liquidateCalculateAmountSeizeTest.ts b/tests/hardhat/Comptroller/Diamond/liquidateCalculateAmoutSeizeTest.ts similarity index 82% rename from tests/hardhat/Comptroller/liquidateCalculateAmountSeizeTest.ts rename to tests/hardhat/Comptroller/Diamond/liquidateCalculateAmoutSeizeTest.ts index f71b9df68..30e0287cf 100644 --- a/tests/hardhat/Comptroller/liquidateCalculateAmountSeizeTest.ts +++ b/tests/hardhat/Comptroller/Diamond/liquidateCalculateAmoutSeizeTest.ts @@ -2,18 +2,19 @@ import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import chai from "chai"; import { BigNumberish, constants } from "ethers"; +import { ethers } from "hardhat"; -import { convertToUnit } from "../../../helpers/utils"; +import { convertToUnit } from "../../../../helpers/utils"; import { - Comptroller, ComptrollerLens, ComptrollerLens__factory, - Comptroller__factory, + ComptrollerMock, IAccessControlManager, PriceOracle, VBep20Immutable, -} from "../../../typechain"; -import { ComptrollerErrorReporter } from "../util/Errors"; +} from "../../../../typechain"; +import { ComptrollerErrorReporter } from "../../util/Errors"; +import { deployDiamond } from "./scripts/deploy"; const { expect } = chai; chai.use(smock.matchers); @@ -23,7 +24,7 @@ const collateralPrice = convertToUnit(1, 18); const repayAmount = convertToUnit(1, 18); async function calculateSeizeTokens( - comptroller: MockContract, + comptroller: ComptrollerMock, vTokenBorrowed: FakeContract, vTokenCollateral: FakeContract, repayAmount: BigNumberish, @@ -36,38 +37,43 @@ function rando(min: number, max: number): number { } describe("Comptroller", () => { - let comptroller: MockContract; + let comptroller: ComptrollerMock; let oracle: FakeContract; let vTokenBorrowed: FakeContract; let vTokenCollateral: FakeContract; type LiquidateFixture = { - comptroller: MockContract; + comptroller: ComptrollerMock; comptrollerLens: MockContract; oracle: FakeContract; vTokenBorrowed: FakeContract; vTokenCollateral: FakeContract; }; - function setOraclePrice(vToken: FakeContract, price: BigNumberish) { + async function setOraclePrice(vToken: FakeContract, price: BigNumberish) { oracle.getUnderlyingPrice.whenCalledWith(vToken.address).returns(price); } async function liquidateFixture(): Promise { - const accessControl = await smock.fake("AccessControlManager"); - const ComptrollerFactory = await smock.mock("Comptroller"); + const accessControl = await smock.fake("IAccessControlManager"); const ComptrollerLensFactory = await smock.mock("ComptrollerLens"); - const comptroller = await ComptrollerFactory.deploy(); + const result = await deployDiamond(""); + const unitroller = result.unitroller; + comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); const comptrollerLens = await ComptrollerLensFactory.deploy(); - const oracle = await smock.fake("PriceOracle"); + const oracle = await smock.fake("contracts/Oracle/PriceOracle.sol:PriceOracle"); accessControl.isAllowedToCall.returns(true); await comptroller._setAccessControl(accessControl.address); await comptroller._setComptrollerLens(comptrollerLens.address); await comptroller._setPriceOracle(oracle.address); await comptroller._setLiquidationIncentive(convertToUnit("1.1", 18)); - const vTokenBorrowed = await smock.fake("VBep20Immutable"); - const vTokenCollateral = await smock.fake("VBep20Immutable"); + const vTokenBorrowed = await smock.fake( + "contracts/Tokens/VTokens/VBep20Immutable.sol:VBep20Immutable", + ); + const vTokenCollateral = await smock.fake( + "contracts/Tokens/VTokens/VBep20Immutable.sol:VBep20Immutable", + ); return { comptroller, comptrollerLens, oracle, vTokenBorrowed, vTokenCollateral }; } @@ -106,20 +112,18 @@ describe("Comptroller", () => { }); it("fails if the repayAmount causes overflow ", async () => { - await expect( - calculateSeizeTokens(comptroller, vTokenBorrowed, vTokenCollateral, constants.MaxUint256), - ).to.be.revertedWith("multiplication overflow"); + await expect(calculateSeizeTokens(comptroller, vTokenBorrowed, vTokenCollateral, constants.MaxUint256)).to.be + .reverted; }); it("fails if the borrowed asset price causes overflow ", async () => { setOraclePrice(vTokenBorrowed, constants.MaxUint256); - await expect(calculateSeizeTokens(comptroller, vTokenBorrowed, vTokenCollateral, repayAmount)).to.be.revertedWith( - "multiplication overflow", - ); + await expect(calculateSeizeTokens(comptroller, vTokenBorrowed, vTokenCollateral, repayAmount)).to.be.reverted; }); it("reverts if it fails to calculate the exchange rate", async () => { vTokenCollateral.exchangeRateStored.reverts("exchangeRateStored: exchangeRateStoredInternal failed"); + ethers.provider.getBlockNumber(); /// TODO: Somehow the error message does not get propagated into the resulting tx. Smock bug? await expect( comptroller.liquidateCalculateSeizeTokens(vTokenBorrowed.address, vTokenCollateral.address, repayAmount), diff --git a/tests/hardhat/Comptroller/pauseTest.ts b/tests/hardhat/Comptroller/Diamond/pauseTest.ts similarity index 86% rename from tests/hardhat/Comptroller/pauseTest.ts rename to tests/hardhat/Comptroller/Diamond/pauseTest.ts index c62197227..a9d62cd7b 100644 --- a/tests/hardhat/Comptroller/pauseTest.ts +++ b/tests/hardhat/Comptroller/Diamond/pauseTest.ts @@ -1,21 +1,17 @@ import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import chai from "chai"; +import { ethers } from "hardhat"; -import { - Comptroller, - Comptroller__factory, - IAccessControlManager, - PriceOracle, - VBep20Immutable, -} from "../../../typechain"; +import { ComptrollerMock, IAccessControlManager, PriceOracle, VBep20Immutable } from "../../../../typechain"; +import { deployDiamond } from "./scripts/deploy"; const { expect } = chai; chai.use(smock.matchers); type PauseFixture = { accessControl: FakeContract; - comptroller: MockContract; + comptroller: ComptrollerMock; oracle: FakeContract; OMG: FakeContract; ZRX: FakeContract; @@ -26,18 +22,19 @@ type PauseFixture = { }; async function pauseFixture(): Promise { - const accessControl = await smock.fake("AccessControlManager"); - const ComptrollerFactory = await smock.mock("Comptroller"); - const comptroller = await ComptrollerFactory.deploy(); + const accessControl = await smock.fake("IAccessControlManager"); + const result = await deployDiamond(""); + const unitroller = result.unitroller; + const comptroller = await ethers.getContractAt("ComptrollerMock", unitroller.address); await comptroller._setAccessControl(accessControl.address); - const oracle = await smock.fake("PriceOracle"); + const oracle = await smock.fake("contracts/Oracle/PriceOracle.sol:PriceOracle"); accessControl.isAllowedToCall.returns(true); await comptroller._setPriceOracle(oracle.address); const names = ["OMG", "ZRX", "BAT", "sketch"]; const [OMG, ZRX, BAT, SKT] = await Promise.all( names.map(async name => { - const vToken = await smock.fake("VBep20Immutable"); + const vToken = await smock.fake("contracts/Tokens/VTokens/VBep20Immutable.sol:VBep20Immutable"); if (name !== "sketch") { await comptroller._supportMarket(vToken.address); } @@ -59,8 +56,8 @@ function configure({ accessControl, allTokens, names }: PauseFixture) { }); } -describe("Comptroller", () => { - let comptroller: MockContract; +describe("ComptrollerMock", () => { + let comptroller: MockContract; let OMG: FakeContract; let ZRX: FakeContract; let BAT: FakeContract; diff --git a/tests/hardhat/Comptroller/Diamond/scripts/deploy.ts b/tests/hardhat/Comptroller/Diamond/scripts/deploy.ts new file mode 100644 index 000000000..f1a7bcb4d --- /dev/null +++ b/tests/hardhat/Comptroller/Diamond/scripts/deploy.ts @@ -0,0 +1,83 @@ +import { impersonateAccount } from "@nomicfoundation/hardhat-network-helpers"; +import hre from "hardhat"; + +import { FacetCutAction, getSelectors } from "../../../../../script/deploy/comptroller/diamond"; +import { Unitroller__factory } from "../../../../../typechain"; + +require("dotenv").config(); + +const ethers = hre.ethers; + +const Owner = "0x939bd8d64c0a9583a7dcea9933f7b21697ab6396"; + +export async function deployFacets() { + // deploy Diamond + const Diamond = await ethers.getContractFactory("Diamond"); + const diamond = await Diamond.deploy(); + await diamond.deployed(); + + // deploy facets + const FacetNames = ["MarketFacet", "PolicyFacet", "RewardFacet", "SetterFacet"]; + const cut: any = []; + + for (const FacetName of FacetNames) { + const Facet = await ethers.getContractFactory(FacetName); + const facet = await Facet.deploy(); + await facet.deployed(); + + const FacetInterface = await ethers.getContractAt(`I${FacetName}`, facet.address); + + cut.push({ + facetAddress: facet.address, + action: FacetCutAction.Add, + functionSelectors: getSelectors(FacetInterface), + }); + } + + return { + diamond, + cut, + }; +} + +export async function deployDiamond(unitrollerAddress) { + let unitroller; + let unitrollerAdmin; + + if (unitrollerAddress != "") { + await impersonateAccount(Owner); + unitrollerAdmin = await ethers.getSigner(Owner); + unitroller = await Unitroller__factory.connect(unitrollerAddress, unitrollerAdmin); + } else { + const UnitrollerFactory = await ethers.getContractFactory("Unitroller"); + unitroller = await UnitrollerFactory.deploy(); + const signer = await ethers.getSigners(); + unitrollerAdmin = signer[0]; + } + + const { diamond, cut } = await deployFacets(); + await unitroller.connect(unitrollerAdmin)._setPendingImplementation(diamond.address); + await diamond.connect(unitrollerAdmin)._become(unitroller.address); + + // upgrade diamond with facets + const diamondCut = await ethers.getContractAt("IDiamondCut", unitroller.address); + + const tx = await diamondCut.connect(unitrollerAdmin).diamondCut(cut); + const receipt = await tx.wait(); + if (!receipt.status) { + throw Error(`Diamond upgrade failed: ${tx.hash}`); + } + + return { unitroller, diamond, cut }; +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +if (require.main === module) { + deployDiamond("") + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); +} diff --git a/tests/hardhat/Comptroller/verifyStorage.ts b/tests/hardhat/Comptroller/verifyStorage.ts deleted file mode 100644 index c69d59109..000000000 --- a/tests/hardhat/Comptroller/verifyStorage.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { Comptroller__factory } from "./../../../typechain"; - -const helpers = require("@nomicfoundation/hardhat-network-helpers"); - -const Owner = "0x939bd8d64c0a9583a7dcea9933f7b21697ab6396"; -const Unitroller = "0xfD36E2c2a6789Db23113685031d7F16329158384"; -const zeroAddr = "0x0000000000000000000000000000000000000000"; -const prevComp = "0xae8ba50ee0a0e55ec21bf4ffe2c48d2fdf52d3e6"; -const BUSD = "0x95c78222B3D6e262426483D42CfA53685A67Ab9D"; - -let owner, - comptrollerV1, - comptrollerV2, - unitroller, - maxAssets, - closeFactorMantissa, - liquidationIncentiveMantissa, - venusRate, - venusSupplyState, - venusBorrowState, - venusAccrued, - vaiMintRate, - supplyCaps, - venusSpeeds; - -describe("Verify Storage Collison", () => { - // These tests checks the storage collision of comptroller while updating it. - // Using mainnet comptroller fork to verify it. - if (process.env.FORK_MAINNET === "true") { - before("Get Deployed Contract", async () => { - /* - * Forking mainnet - * */ - await helpers.impersonateAccount(Owner); - owner = await ethers.getSigner(Owner); - - /** - * sending gas cost to owner - * */ - const [signer] = await ethers.getSigners(); - console.log("-- Sending gas cost to owner addr --"); - await signer.sendTransaction({ - to: owner.address, - value: ethers.BigNumber.from("10000000000000000000"), - data: undefined, - }); - - unitroller = await ethers.getContractAt("contracts/Comptroller/Unitroller.sol:Unitroller", Unitroller, owner); - }); - describe("should match old admin address", async () => { - it("Owner of unitroller deployed contract should match", async () => { - const unitrollerAdmin = await unitroller.admin(); - const pendingAdmin = await unitroller.pendingAdmin(); - - expect(unitrollerAdmin.toLowerCase()).to.equal(Owner); - expect(pendingAdmin.toLowerCase()).to.equal(zeroAddr); - }); - - it("should match old Comptroller Address", async () => { - const comptrollerImplementation = await unitroller.comptrollerImplementation(); - const pendingComptrollerImplementation = await unitroller.pendingComptrollerImplementation(); - - expect(comptrollerImplementation.toLowerCase()).to.equal(prevComp); - expect(pendingComptrollerImplementation.toLowerCase()).to.equal(zeroAddr); - }); - }); - - describe("save initial states of Comptroller Storage", async () => { - it("Save all version 1 state", async () => { - const [signer] = await ethers.getSigners(); - const compBySigner = Comptroller__factory.connect(unitroller.address, signer); - - maxAssets = await compBySigner.maxAssets(); - closeFactorMantissa = await compBySigner.closeFactorMantissa(); - liquidationIncentiveMantissa = await compBySigner.liquidationIncentiveMantissa(); - await compBySigner.allMarkets(0); - await compBySigner.markets(BUSD); - venusRate = await compBySigner.venusRate(); - venusSpeeds = await compBySigner.venusSpeeds(BUSD); - venusSupplyState = await compBySigner.venusSupplyState(BUSD); - venusBorrowState = await compBySigner.venusBorrowState(BUSD); - venusAccrued = await compBySigner.venusAccrued(BUSD); - vaiMintRate = await compBySigner.vaiMintRate(); - supplyCaps = await compBySigner.supplyCaps(BUSD); - }); - }); - describe("deploy updatedComprtroller and verify previous states", async () => { - it("deploy updatedComptroller", async () => { - const ComptrollerV1 = await ethers.getContractFactory( - "contracts/Comptroller/UpdatedComptroller.sol:UpdatedComptroller", - ); - comptrollerV1 = await ComptrollerV1.deploy(); - await comptrollerV1.deployed(); - await unitroller.connect(owner)._setPendingImplementation(comptrollerV1.address); - await comptrollerV1.connect(owner)._become(unitroller.address); - }); - - it("verify all version 1 state", async () => { - const [signer] = await ethers.getSigners(); - const compBySigner = Comptroller__factory.connect(unitroller.address, signer); - - const maxAssetsV1 = await compBySigner.maxAssets(); - const closeFactorMantissaV1 = await compBySigner.closeFactorMantissa(); - const liquidationIncentiveMantissaV1 = await compBySigner.liquidationIncentiveMantissa(); - const allMarketsV1 = await compBySigner.allMarkets(0); - const venusRateV1 = await compBySigner.venusRate(); - const venusSpeedsV1 = await compBySigner.venusSpeeds(BUSD); - const venusSupplyStateV1 = await compBySigner.venusSupplyState(BUSD); - const venusBorrowStateV1 = await compBySigner.venusBorrowState(BUSD); - const venusAccruedV1 = await compBySigner.venusAccrued(BUSD); - const vaiMintRateV1 = await compBySigner.vaiMintRate(); - const supplyCapsV1 = await compBySigner.supplyCaps(BUSD); - const venusSupplySpeedsV1 = await compBySigner.venusSupplySpeeds(BUSD); - - expect(maxAssets).to.equal(maxAssetsV1); - expect(liquidationIncentiveMantissa).to.equal(liquidationIncentiveMantissaV1); - expect(closeFactorMantissa).to.equal(closeFactorMantissaV1); - expect(allMarketsV1).to.equal(allMarketsV1); - expect(venusRate).to.equal(venusRateV1); - expect(venusSpeedsV1).to.equal(0); - expect(venusSupplyState.index.toString()).to.equal(venusSupplyStateV1.index.toString()); - expect(venusBorrowState.index.toString()).to.equal(venusBorrowStateV1.index.toString()); - expect(venusAccrued).to.equal(venusAccruedV1); - expect(vaiMintRate).to.equal(vaiMintRateV1); - expect(supplyCaps).to.equal(supplyCapsV1); - expect(venusSpeeds.toString()).to.equal(venusSupplySpeedsV1.toString()); - }); - }); - - describe("deploy Comptroller and verify previous states", async () => { - it("deploy updatedComptroller", async () => { - const ComptrollerV2 = await ethers.getContractFactory("contracts/Comptroller/Comptroller.sol:Comptroller"); - comptrollerV2 = await ComptrollerV2.deploy(); - await comptrollerV2.deployed(); - await unitroller.connect(owner)._setPendingImplementation(comptrollerV2.address); - await comptrollerV2.connect(owner)._become(unitroller.address); - }); - - it("verify all version 2 state", async () => { - const [signer] = await ethers.getSigners(); - const compBySigner = Comptroller__factory.connect(unitroller.address, signer); - - const maxAssetsV2 = await compBySigner.maxAssets(); - const closeFactorMantissaV2 = await compBySigner.closeFactorMantissa(); - const liquidationIncentiveMantissaV2 = await compBySigner.liquidationIncentiveMantissa(); - const allMarketsV2 = await compBySigner.allMarkets(0); - const venusRateV2 = await compBySigner.venusRate(); - const venusSpeedsV2 = await compBySigner.venusSpeeds(BUSD); - const venusSupplyStateV2 = await compBySigner.venusSupplyState(BUSD); - const venusBorrowStateV2 = await compBySigner.venusBorrowState(BUSD); - const venusAccruedV2 = await compBySigner.venusAccrued(BUSD); - const vaiMintRateV2 = await compBySigner.vaiMintRate(); - const supplyCapsV2 = await compBySigner.supplyCaps(BUSD); - const venusSupplySpeedsV2 = await compBySigner.venusSupplySpeeds(BUSD); - - expect(maxAssets).to.equal(maxAssetsV2); - expect(liquidationIncentiveMantissa).to.equal(liquidationIncentiveMantissaV2); - expect(closeFactorMantissa).to.equal(closeFactorMantissaV2); - expect(allMarketsV2).to.equal(allMarketsV2); - expect(venusRate).to.equal(venusRateV2); - expect(venusSpeedsV2).to.equal(0); - expect(venusSupplyState.index.toString()).to.equal(venusSupplyStateV2.index.toString()); - expect(venusBorrowState.index.toString()).to.equal(venusBorrowStateV2.index.toString()); - expect(venusAccrued).to.equal(venusAccruedV2); - expect(vaiMintRate).to.equal(vaiMintRateV2); - expect(supplyCaps).to.equal(supplyCapsV2); - expect(venusSpeeds.toString()).to.equal(venusSupplySpeedsV2.toString()); - }); - }); - } -}); diff --git a/tests/hardhat/DelegateBorrowers/SwapDebtDelegate.ts b/tests/hardhat/DelegateBorrowers/SwapDebtDelegate.ts index 165649953..c18963989 100644 --- a/tests/hardhat/DelegateBorrowers/SwapDebtDelegate.ts +++ b/tests/hardhat/DelegateBorrowers/SwapDebtDelegate.ts @@ -6,7 +6,7 @@ import { parseUnits } from "ethers/lib/utils"; import { ethers, upgrades } from "hardhat"; import { - Comptroller, + ComptrollerMock, IERC20Upgradeable, PriceOracle, SwapDebtDelegate, @@ -23,7 +23,7 @@ describe("assetListTest", () => { let owner: SignerWithAddress; let borrower: SignerWithAddress; let priceOracle: FakeContract; - let comptroller: FakeContract; + let comptroller: FakeContract; let foo: FakeContract; let bar: FakeContract; let vFoo: FakeContract; @@ -44,7 +44,7 @@ describe("assetListTest", () => { [owner, borrower] = await ethers.getSigners(); priceOracle = await smock.fake("PriceOracle"); - comptroller = await smock.fake("Comptroller"); + comptroller = await smock.fake("ComptrollerMock"); foo = await smock.fake("IERC20Upgradeable"); bar = await smock.fake("IERC20Upgradeable"); vFoo = await smock.fake("VBep20"); diff --git a/tests/hardhat/EvilXToken.ts b/tests/hardhat/EvilXToken.ts index a148b4563..4d94b71c0 100644 --- a/tests/hardhat/EvilXToken.ts +++ b/tests/hardhat/EvilXToken.ts @@ -13,7 +13,7 @@ describe("Evil Token test", async () => { beforeEach(async () => { const [root, account1] = await ethers.getSigners(); - const accessControlMock = await smock.fake("AccessControlManager"); + const accessControlMock = await smock.fake("IAccessControlManager"); accessControlMock.isAllowedToCall.returns(true); user = account1; @@ -155,8 +155,9 @@ describe("Evil Token test", async () => { await vDelegator3.deployed(); vToken3 = await ethers.getContractAt("EvilXToken", vDelegator3.address); - await unitroller._supportMarket(vToken3.address); + + await unitroller._setCollateralFactor(vToken3.address, convertToUnit(cf2, 18)); await unitroller._setCollateralFactor(vToken3.address, convertToUnit(cf3, 18)); await priceOracle.setUnderlyingPrice(vToken2.address, convertToUnit(up3, 18)); diff --git a/tests/hardhat/Fork/diamondTest.ts b/tests/hardhat/Fork/diamondTest.ts new file mode 100644 index 000000000..ae032c0f6 --- /dev/null +++ b/tests/hardhat/Fork/diamondTest.ts @@ -0,0 +1,420 @@ +import { smock } from "@defi-wonderland/smock"; +import { impersonateAccount } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import chai from "chai"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers, network } from "hardhat"; + +import { initMainnetUser } from "../../../script/hardhat/fork/vip-framework/utils"; +import { IAccessControlManagerV5__factory, VBep20 } from "../../../typechain"; +import { deployDiamond } from "../Comptroller/Diamond/scripts/deploy"; + +const { expect } = chai; +chai.use(smock.matchers); + +const Owner = "0x939bd8d64c0a9583a7dcea9933f7b21697ab6396"; +const UNITROLLER = "0xfD36E2c2a6789Db23113685031d7F16329158384"; +const zeroAddr = "0x0000000000000000000000000000000000000000"; +const VBUSD = "0x95c78222B3D6e262426483D42CfA53685A67Ab9D"; +const VUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; +const ACM = "0x4788629ABc6cFCA10F9f969efdEAa1cF70c23555"; + +let owner, + unitroller, + diamondProxy, + // layout variables + oracle, + maxAssets, + closeFactorMantissa, + liquidationIncentiveMantissa, + allMarkets, + venusSupplyState, + venusBorrowState, + venusAccrued, + vaiMintRate, + vaiController, + mintedVAIs, + mintVAIGuardianPaused, + repayVAIGuardianPaused, + protocolPaused, + venusVAIVaultRate, + vaiVaultAddress, + releaseStartBlock, + minReleaseAmount, + treasuryGuardian, + treasuryAddress, + treasuryPercent, + liquidatorContract, + comptrollerLens, + market, + venusSupplierIndex, + venusBorrowerIndex, + accessControlManager; + +export async function setForkBlock(blockNumber: number) { + await network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: process.env.BSC_ARCHIVE_NODE_URL, + blockNumber: blockNumber, + }, + }, + ], + }); +} + +const forking = (blockNumber: number, fn: () => void) => { + describe(`Diamond architecture check At block #${blockNumber}`, () => { + before(async () => { + await setForkBlock(blockNumber); + }); + fn(); + }); +}; + +forking(31873700, () => { + let USDT: ethers.contract; + let BUSD: ethers.contract; + let usdtHolder: ethers.Signer; + let busdHolder: ethers.Signer; + let vBUSD: ethers.contract; + let vUSDT: ethers.contract; + let ownerSigner: SignerWithAddress; //eslint-disable-line + let diamondUnitroller; + + if (process.env.FORK_MAINNET === "true") { + before(async () => { + /* + * Forking mainnet + * */ + + /** + * sending gas cost to owner + * */ + await impersonateAccount(Owner); + owner = await ethers.getSigner(Owner); + const [signer] = await ethers.getSigners(); + await signer.sendTransaction({ + to: owner.address, + value: ethers.BigNumber.from("10000000000000000000"), + data: undefined, + }); + + // unitroller without diamond + unitroller = await ethers.getContractAt("ComptrollerMock", UNITROLLER); + + // deploy Diamond + const result = await deployDiamond(UNITROLLER); + diamondUnitroller = result.unitroller; + diamondProxy = result.diamond; + + // unitroller with diamond + diamondUnitroller = await ethers.getContractAt("ComptrollerMock", diamondUnitroller.address); + + busdHolder = await initMainnetUser("0xf977814e90da44bfa03b6295a0616a897441acec", parseUnits("1000", 18)); + usdtHolder = await initMainnetUser("0x3e8734Ec146C981E3eD1f6b582D447DDE701d90c", parseUnits("1000", 18)); + ownerSigner = await initMainnetUser(Owner, parseUnits("1000", 18)); + accessControlManager = IAccessControlManagerV5__factory.connect(ACM, owner); + + [vBUSD, vUSDT] = await Promise.all( + [VBUSD, VUSDT].map((address: string) => { + return ethers.getContractAt("contracts/Tokens/VTokens/VBep20Delegate.sol:VBep20Delegate", address); + }), + ); + [BUSD, USDT] = await Promise.all( + [vBUSD, vUSDT].map(async (vToken: VBep20) => { + const underlying = await vToken.underlying(); + return ethers.getContractAt("IERC20Upgradeable", underlying); + }), + ); + }); + + describe("Verify Storage slots", () => { + // These tests checks the storage collision of comptroller while updating it via diamond. + describe("Diamond deployed successfully", async () => { + it("Owner of Diamond unitroller contract should match", async () => { + const diamondUnitrollerAdmin = await diamondUnitroller.admin(); + const pendingAdmin = await diamondUnitroller.pendingAdmin(); + expect(diamondUnitrollerAdmin.toLowerCase()).to.equal(Owner); + expect(pendingAdmin.toLowerCase()).to.equal(zeroAddr); + }); + + it("Diamond Unitroller Implementation (comptroller) should match the diamond Proxy Address", async () => { + const comptrollerImplementation = await diamondUnitroller.comptrollerImplementation(); + const pendingComptrollerImplementation = await diamondUnitroller.pendingComptrollerImplementation(); + expect(comptrollerImplementation.toLowerCase()).to.equal(diamondProxy.address.toLowerCase()); + expect(pendingComptrollerImplementation.toLowerCase()).to.equal(zeroAddr); + }); + }); + + describe("Verify storage layout", async () => { + it("verify all the state before and after upgrade", async () => { + oracle = await unitroller.oracle(); + const oracelUpgrade = await diamondUnitroller.oracle(); + expect(oracle).to.equal(oracelUpgrade); + + maxAssets = await unitroller.maxAssets(); + const maxAssetsAfterUpgrade = await diamondUnitroller.maxAssets(); + expect(maxAssets).to.equal(maxAssetsAfterUpgrade); + + closeFactorMantissa = await unitroller.closeFactorMantissa(); + const closeFactorMantissaAfterUpgrade = await diamondUnitroller.closeFactorMantissa(); + expect(closeFactorMantissa).to.equal(closeFactorMantissaAfterUpgrade); + + liquidationIncentiveMantissa = await unitroller.liquidationIncentiveMantissa(); + const liquidationIncentiveMantissaAfterUpgrade = await diamondUnitroller.liquidationIncentiveMantissa(); + expect(liquidationIncentiveMantissa).to.equal(liquidationIncentiveMantissaAfterUpgrade); + + allMarkets = await unitroller.allMarkets(0); + const allMarketsAfterUpgrade = await diamondUnitroller.allMarkets(0); + expect(allMarkets).to.equal(allMarketsAfterUpgrade); + + venusSupplyState = await unitroller.venusSupplyState(BUSD.address); + const venusSupplyStateAfterUpgrade = await diamondUnitroller.venusSupplyState(BUSD.address); + expect(venusSupplyState.index.toString()).to.equal(venusSupplyStateAfterUpgrade.index.toString()); + + venusBorrowState = await unitroller.venusBorrowState(BUSD.address); + const venusBorrowStateAfterUpgrade = await diamondUnitroller.venusBorrowState(BUSD.address); + expect(venusBorrowState.index.toString()).to.equal(venusBorrowStateAfterUpgrade.index.toString()); + + venusAccrued = await unitroller.venusAccrued(BUSD.address); + const venusAccruedAfterUpgrade = await diamondUnitroller.venusAccrued(BUSD.address); + expect(venusAccrued).to.equal(venusAccruedAfterUpgrade); + + vaiMintRate = await unitroller.vaiMintRate(); + const vaiMintRateAfterUpgrade = await diamondUnitroller.vaiMintRate(); + expect(vaiMintRate).to.equal(vaiMintRateAfterUpgrade); + + vaiController = await unitroller.vaiController(); + const vaiControllerUpgrade = await diamondUnitroller.vaiController(); + expect(vaiControllerUpgrade).to.equal(vaiController); + + mintedVAIs = await unitroller.mintedVAIs(busdHolder.address); + unitroller.minte; + const mintedVAIsUpgrade = await diamondUnitroller.mintedVAIs(busdHolder.address); + expect(mintedVAIsUpgrade).to.equal(mintedVAIs); + + mintVAIGuardianPaused = await unitroller.mintVAIGuardianPaused(); + const mintVAIGuardianPausedUpgrade = await diamondUnitroller.mintVAIGuardianPaused(); + expect(mintVAIGuardianPausedUpgrade).to.equal(mintVAIGuardianPaused); + + repayVAIGuardianPaused = await unitroller.repayVAIGuardianPaused(); + const repayVAIGuardianPausedUpgrade = await diamondUnitroller.repayVAIGuardianPaused(); + expect(repayVAIGuardianPausedUpgrade).to.equal(repayVAIGuardianPaused); + + protocolPaused = await unitroller.protocolPaused(); + const protocolPausedUpgrade = await diamondUnitroller.protocolPaused(); + expect(protocolPausedUpgrade).to.equal(protocolPaused); + + venusVAIVaultRate = await unitroller.venusVAIVaultRate(); + const venusVAIVaultRateUpgrade = await diamondUnitroller.venusVAIVaultRate(); + expect(venusVAIVaultRateUpgrade).to.equal(venusVAIVaultRate); + + vaiVaultAddress = await unitroller.vaiVaultAddress(); + const vaiVaultAddressUpgrade = await diamondUnitroller.vaiVaultAddress(); + expect(vaiVaultAddressUpgrade).to.equal(vaiVaultAddress); + + releaseStartBlock = await unitroller.releaseStartBlock(); + const releaseStartBlockUpgrade = await diamondUnitroller.releaseStartBlock(); + expect(releaseStartBlockUpgrade).to.equal(releaseStartBlock); + + minReleaseAmount = await unitroller.minReleaseAmount(); + const minReleaseAmountUpgrade = await diamondUnitroller.minReleaseAmount(); + expect(minReleaseAmountUpgrade).to.equal(minReleaseAmount); + + treasuryGuardian = await unitroller.treasuryGuardian(); + const treasuryGuardianUpgrade = await diamondUnitroller.treasuryGuardian(); + expect(treasuryGuardian).to.equal(treasuryGuardianUpgrade); + + treasuryAddress = await unitroller.treasuryAddress(); + const treasuryAddressUpgrade = await diamondUnitroller.treasuryAddress(); + expect(treasuryAddress).to.equal(treasuryAddressUpgrade); + + treasuryPercent = await unitroller.treasuryPercent(); + const treasuryPercentUpgrade = await diamondUnitroller.treasuryPercent(); + expect(treasuryPercent).to.equal(treasuryPercentUpgrade); + + liquidatorContract = await unitroller.liquidatorContract(); + const liquidatorContractUpgrade = await diamondUnitroller.liquidatorContract(); + expect(liquidatorContract).to.equal(liquidatorContractUpgrade); + + comptrollerLens = await unitroller.comptrollerLens(); + const comptrollerLensUpgrade = await diamondUnitroller.comptrollerLens(); + expect(comptrollerLens).to.equal(comptrollerLensUpgrade); + + // cheking all public mappings + market = await unitroller.markets(vBUSD.address); + const marketUpgrade = await diamondUnitroller.markets(vBUSD.address); + expect(market.collateralFactorMantissa).to.equal(marketUpgrade.collateralFactorMantissa); + expect(market.isListed).to.equal(marketUpgrade.isListed); + expect(market.isVenus).to.equal(marketUpgrade.isVenus); + + venusBorrowerIndex = await unitroller.venusBorrowerIndex(vBUSD.address, busdHolder.address); + const venusBorrowerIndexUpgrade = await diamondUnitroller.venusBorrowerIndex( + vBUSD.address, + busdHolder.address, + ); + expect(venusBorrowerIndex).to.equal(venusBorrowerIndexUpgrade); + + venusSupplierIndex = await unitroller.venusSupplierIndex(vBUSD.address, busdHolder.address); + const venusSupplierIndexUpgrade = await diamondUnitroller.venusSupplierIndex( + vBUSD.address, + busdHolder.address, + ); + expect(venusSupplierIndex).to.equal(venusSupplierIndexUpgrade); + + const venusBorrowSpeeds = await unitroller.venusBorrowSpeeds(vUSDT.address); + const venusBorrowSpeedsUpgrade = await diamondUnitroller.venusBorrowSpeeds(vUSDT.address); + const venusSupplySpeeds = await unitroller.venusSupplySpeeds(vUSDT.address); + const venusSupplySpeedsUpgrade = await diamondUnitroller.venusSupplySpeeds(vUSDT.address); + + expect(venusBorrowSpeeds).to.equal(venusBorrowSpeedsUpgrade); + expect(venusSupplySpeeds).to.equal(venusSupplySpeedsUpgrade); + }); + }); + }); + + describe("Verify states of diamond Contract", () => { + describe("Diamond setters", () => { + it("setting market supply cap", async () => { + const currentSupplyCap = (await diamondUnitroller.supplyCaps(vBUSD.address)).toString(); + await diamondUnitroller.connect(owner)._setMarketSupplyCaps([vBUSD.address], [parseUnits("100000", 18)]); + expect(await diamondUnitroller.supplyCaps(vBUSD.address)).to.equals(parseUnits("100000", 18)); + await diamondUnitroller + .connect(owner) + ._setMarketSupplyCaps([vBUSD.address], [parseUnits(currentSupplyCap, 0)]); + expect(await diamondUnitroller.supplyCaps(vBUSD.address)).to.equals(parseUnits(currentSupplyCap, 0)); + }); + + it("setting close factor", async () => { + const currentCloseFactor = (await diamondUnitroller.closeFactorMantissa()).toString(); + await diamondUnitroller.connect(owner)._setCloseFactor(parseUnits("8", 17)); + expect(await diamondUnitroller.closeFactorMantissa()).to.equals(parseUnits("8", 17)); + await diamondUnitroller.connect(owner)._setCloseFactor(parseUnits(currentCloseFactor, 0)); + expect(await diamondUnitroller.closeFactorMantissa()).to.equals(parseUnits(currentCloseFactor, 0)); + }); + + it("setting collateral factor", async () => { + await diamondUnitroller.connect(owner)._setCollateralFactor(vUSDT.address, 2); + market = await diamondUnitroller.markets(vUSDT.address); + expect(market.collateralFactorMantissa).to.equal(2); + + await diamondUnitroller.connect(owner)._setCollateralFactor(vUSDT.address, parseUnits("8", 17)); + market = await diamondUnitroller.markets(vUSDT.address); + expect(market.collateralFactorMantissa).to.equal(parseUnits("8", 17)); + }); + + it("setting setting Liquidation Incentive", async () => { + await diamondUnitroller.connect(owner)._setLiquidationIncentive(parseUnits("13", 17)); + expect(await diamondUnitroller.liquidationIncentiveMantissa()).to.equal(parseUnits("13", 17)); + + await diamondUnitroller.connect(owner)._setLiquidationIncentive(parseUnits("11", 17)); + expect(await diamondUnitroller.liquidationIncentiveMantissa()).to.equal(parseUnits("11", 17)); + }); + + it("setting Pause Guardian", async () => { + const currentPauseGuardia = (await diamondUnitroller.pauseGuardian()).toString(); + + await diamondUnitroller.connect(owner)._setPauseGuardian(owner.address); + expect(await diamondUnitroller.pauseGuardian()).to.equal(owner.address); + + await diamondUnitroller.connect(owner)._setPauseGuardian(currentPauseGuardia); + expect(await diamondUnitroller.pauseGuardian()).to.equal(currentPauseGuardia); + }); + + it("setting market borrow cap", async () => { + const currentBorrowCap = (await diamondUnitroller.borrowCaps(vUSDT.address)).toString(); + await diamondUnitroller.connect(owner)._setMarketBorrowCaps([vUSDT.address], [parseUnits("10000", 18)]); + expect(await diamondUnitroller.borrowCaps(vUSDT.address)).to.equal(parseUnits("10000", 18)); + + await diamondUnitroller.connect(owner)._setMarketBorrowCaps([vUSDT.address], [currentBorrowCap]); + expect(await diamondUnitroller.borrowCaps(vUSDT.address)).to.equal(currentBorrowCap); + }); + + it("pausing mint action in vUSDT", async () => { + const tx = await accessControlManager + .connect(owner) + .giveCallPermission(diamondUnitroller.address, "_setActionsPaused(address[],uint8[],bool)", Owner); + await tx.wait(); + + await expect(diamondUnitroller.connect(owner)._setActionsPaused([vUSDT.address], [0], true)).to.emit( + diamondUnitroller, + "ActionPausedMarket", + ); + + await expect(vUSDT.connect(usdtHolder).mint(1000)).to.be.revertedWith("action is paused"); + + await expect(diamondUnitroller.connect(owner)._setActionsPaused([vUSDT.address], [0], false)).to.emit( + diamondUnitroller, + "ActionPausedMarket", + ); + await expect(vUSDT.connect(busdHolder).mint(10)).to.be.emit(vUSDT, "Transfer"); + }); + + it("sets forced liquidation", async () => { + const tx = await accessControlManager + .connect(owner) + .giveCallPermission(diamondUnitroller.address, "_setForcedLiquidation(address,bool)", Owner); + await tx.wait(); + + await diamondUnitroller.connect(owner)._setForcedLiquidation(vUSDT.address, true); + expect(await diamondUnitroller.isForcedLiquidationEnabled(vUSDT.address)).to.be.true; + + await diamondUnitroller.connect(owner)._setForcedLiquidation(vUSDT.address, false); + expect(await diamondUnitroller.isForcedLiquidationEnabled(vUSDT.address)).to.be.false; + }); + }); + + describe("Diamond Hooks", () => { + it("mint vToken vUSDT", async () => { + const vBUSDBalance = await USDT.balanceOf(vUSDT.address); + const busdHolerBalance = await USDT.balanceOf(await usdtHolder.getAddress()); + + await USDT.connect(usdtHolder).approve(vUSDT.address, parseUnits("2", 18)); + await expect(await vUSDT.connect(usdtHolder).mint(parseUnits("2", 18))).to.emit(vUSDT, "Transfer"); + + const newvBUSDBalance = await USDT.balanceOf(vUSDT.address); + const newBusdHolerBalance = await USDT.balanceOf(await usdtHolder.getAddress()); + + expect(newvBUSDBalance).greaterThan(vBUSDBalance); + expect(newBusdHolerBalance).lessThan(busdHolerBalance); + }); + + it("redeem vToken", async () => { + await USDT.connect(usdtHolder).approve(vUSDT.address, 2000); + // await expect(vUSDT.connect(usdtHolder).mint(2000)).to.emit(vUSDT, "Mint"); + + const vUSDTUserBal = await vUSDT.connect(usdtHolder).balanceOf(await usdtHolder.getAddress()); + await expect(await vUSDT.connect(usdtHolder).redeem(2000)).to.emit(vUSDT, "Transfer"); + const newVUSDTUserBal = await vUSDT.connect(usdtHolder).balanceOf(await usdtHolder.getAddress()); + + expect(newVUSDTUserBal).to.equal(vUSDTUserBal.sub(2000)); + }); + + it("borrow vToken", async () => { + const busdUserBal = await USDT.balanceOf(await usdtHolder.getAddress()); + + await expect(vUSDT.connect(usdtHolder).borrow(1000)).to.emit(vUSDT, "Borrow"); + + expect((await USDT.balanceOf(await usdtHolder.getAddress())).toString()).to.equal(busdUserBal.add(1000)); + }); + + it("Repay vToken", async () => { + await USDT.connect(usdtHolder).approve(vUSDT.address, 2000); + + const busdUserBal = await USDT.balanceOf(await usdtHolder.getAddress()); + await vUSDT.connect(usdtHolder).borrow(1000); + + expect((await USDT.balanceOf(await usdtHolder.getAddress())).toString()).to.greaterThan(busdUserBal); + + await vUSDT.connect(usdtHolder).repayBorrow(1000); + + const balanceAfterRepay = await USDT.balanceOf(await usdtHolder.getAddress()); + expect(balanceAfterRepay).to.equal(busdUserBal); + }); + }); + }); + } +}); diff --git a/tests/hardhat/Fork/swapTest.ts b/tests/hardhat/Fork/swapTest.ts index aaec92377..61965093a 100644 --- a/tests/hardhat/Fork/swapTest.ts +++ b/tests/hardhat/Fork/swapTest.ts @@ -5,10 +5,10 @@ import chai from "chai"; import { parseUnits } from "ethers/lib/utils"; import { - Comptroller, ComptrollerLens, ComptrollerLens__factory, - Comptroller__factory, + ComptrollerMock, + ComptrollerMock__factory, FaucetToken, FaucetToken__factory, IAccessControlManager, @@ -46,7 +46,7 @@ let admin: SignerWithAddress; let oracle: FakeContract; let accessControl: FakeContract; let comptrollerLens: MockContract; -let comptroller: MockContract; +let comptroller: MockContract; const SWAP_AMOUNT = 100; const MIN_AMOUNT_OUT = 90; @@ -60,7 +60,7 @@ async function deploySimpleComptroller() { accessControl = await smock.fake("IAccessControlManager"); accessControl.isAllowedToCall.returns(true); const ComptrollerLensFactory = await smock.mock("ComptrollerLens"); - const ComptrollerFactory = await smock.mock("Comptroller"); + const ComptrollerFactory = await smock.mock("ComptrollerMock"); comptroller = await ComptrollerFactory.deploy(); comptrollerLens = await ComptrollerLensFactory.deploy(); await comptroller._setAccessControl(accessControl.address); diff --git a/tests/hardhat/Lens/Rewards.ts b/tests/hardhat/Lens/Rewards.ts index 77ab9ae9f..80d6cd9c4 100644 --- a/tests/hardhat/Lens/Rewards.ts +++ b/tests/hardhat/Lens/Rewards.ts @@ -1,16 +1,19 @@ import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; import { loadFixture, mineUpTo } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; +import chai from "chai"; import { BigNumber, Signer } from "ethers"; import { ethers } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; -import { Comptroller, MockToken, VToken, VenusLens, VenusLens__factory } from "../../../typechain"; +import { ComptrollerMock, FaucetToken, VToken, VenusLens, VenusLens__factory } from "../../../typechain"; -let comptroller: FakeContract; +const { expect } = chai; +chai.use(smock.matchers); + +let comptroller: FakeContract; let vBUSD: FakeContract; let vWBTC: FakeContract; -let XVS: FakeContract; +let XVS: FakeContract; let account: Signer; let venusLens: MockContract; let startBlock: number; @@ -18,10 +21,10 @@ let startBlock: number; const VENUS_ACCRUED = convertToUnit(10, 18); type RewardsFixtire = { - comptroller: FakeContract; + comptroller: FakeContract; vBUSD: FakeContract; vWBTC: FakeContract; - XVS: FakeContract; + XVS: FakeContract; venusLens: MockContract; startBlock: number; }; @@ -29,10 +32,10 @@ type RewardsFixtire = { const rewardsFixture = async (): Promise => { vBUSD = await smock.fake("VToken"); vWBTC = await smock.fake("VToken"); - XVS = await smock.fake("MockToken"); + XVS = await smock.fake("FaucetToken"); const venusLensFactory = await smock.mock("VenusLens"); venusLens = await venusLensFactory.deploy(); - comptroller = await smock.fake("Comptroller"); + comptroller = await smock.fake("ComptrollerMock"); const startBlock = await ethers.provider.getBlockNumber(); diff --git a/tests/hardhat/Liquidator/liquidatorHarnessTest.ts b/tests/hardhat/Liquidator/liquidatorHarnessTest.ts index fd8bd34e1..9623a0d6f 100644 --- a/tests/hardhat/Liquidator/liquidatorHarnessTest.ts +++ b/tests/hardhat/Liquidator/liquidatorHarnessTest.ts @@ -6,7 +6,7 @@ import { ethers, upgrades } from "hardhat"; import { convertToBigInt } from "../../../helpers/utils"; import { - Comptroller, + ComptrollerMock, LiquidatorHarness, LiquidatorHarness__factory, MockVBNB, @@ -21,7 +21,7 @@ const announcedIncentive = convertToBigInt("1.1", 18); const treasuryPercent = convertToBigInt("0.05", 18); type LiquidatorFixture = { - comptroller: FakeContract; + comptroller: FakeContract; vTokenCollateral: FakeContract; liquidator: MockContract; vBnb: FakeContract; @@ -30,7 +30,7 @@ type LiquidatorFixture = { async function deployLiquidator(): Promise { const [, treasury] = await ethers.getSigners(); - const comptroller = await smock.fake("Comptroller"); + const comptroller = await smock.fake("ComptrollerMock"); comptroller.liquidationIncentiveMantissa.returns(announcedIncentive); const vBnb = await smock.fake("MockVBNB"); const vTokenCollateral = await smock.fake("VBep20Immutable"); diff --git a/tests/hardhat/Liquidator/liquidatorTest.ts b/tests/hardhat/Liquidator/liquidatorTest.ts index c1b66a985..3816ebb00 100644 --- a/tests/hardhat/Liquidator/liquidatorTest.ts +++ b/tests/hardhat/Liquidator/liquidatorTest.ts @@ -7,7 +7,7 @@ import { ethers, upgrades } from "hardhat"; import { convertToBigInt } from "../../../helpers/utils"; import { - Comptroller, + ComptrollerMock, FaucetToken, FaucetToken__factory, Liquidator, @@ -29,7 +29,7 @@ const treasuryShare = 181n; // seizeTokens * treasuryPercent / announcedIncentiv const liquidatorShare = seizeTokens - treasuryShare; type LiquidatorFixture = { - comptroller: FakeContract; + comptroller: FakeContract; borrowedUnderlying: MockContract; vai: MockContract; vaiController: FakeContract; @@ -42,7 +42,7 @@ type LiquidatorFixture = { async function deployLiquidator(): Promise { const [, treasury] = await ethers.getSigners(); - const comptroller = await smock.fake("Comptroller"); + const comptroller = await smock.fake("ComptrollerMock"); const vBnb = await smock.fake("MockVBNB"); const FaucetToken = await smock.mock("FaucetToken"); const borrowedUnderlying = await FaucetToken.deploy(convertToBigInt("100", 18), "USD", 18, "USD"); diff --git a/tests/hardhat/Liquidator/restrictedLiquidations.ts b/tests/hardhat/Liquidator/restrictedLiquidations.ts index 9ceded1f8..3590f92c6 100644 --- a/tests/hardhat/Liquidator/restrictedLiquidations.ts +++ b/tests/hardhat/Liquidator/restrictedLiquidations.ts @@ -5,7 +5,7 @@ import chai from "chai"; import { ethers, upgrades } from "hardhat"; import { convertToBigInt } from "../../../helpers/utils"; -import { Comptroller, Liquidator, Liquidator__factory, MockVBNB, VBep20Immutable } from "../../../typechain"; +import { ComptrollerMock, Liquidator, Liquidator__factory, MockVBNB, VBep20Immutable } from "../../../typechain"; const { expect } = chai; chai.use(smock.matchers); @@ -13,7 +13,7 @@ chai.use(smock.matchers); type LiquidatorFixture = { vBep20: FakeContract; vBnb: FakeContract; - comptroller: FakeContract; + comptroller: FakeContract; liquidator: MockContract; }; @@ -21,7 +21,7 @@ async function deployLiquidator(): Promise { const [, treasury] = await ethers.getSigners(); const treasuryPercentMantissa = convertToBigInt("0.05", 18); - const comptroller = await smock.fake("Comptroller"); + const comptroller = await smock.fake("ComptrollerMock"); comptroller.liquidationIncentiveMantissa.returns(convertToBigInt("1.1", 18)); const vBnb = await smock.fake("MockVBNB"); const vBep20 = await smock.fake("VBep20Immutable"); diff --git a/tests/hardhat/Unitroller/unitrollerTest.ts b/tests/hardhat/Unitroller/unitrollerTest.ts index 371770542..fd58a5e62 100644 --- a/tests/hardhat/Unitroller/unitrollerTest.ts +++ b/tests/hardhat/Unitroller/unitrollerTest.ts @@ -6,8 +6,8 @@ import { ContractTransaction, constants } from "ethers"; import { ethers } from "hardhat"; import { - Comptroller, - Comptroller__factory, + ComptrollerMock, + ComptrollerMock__factory, EchoTypesComptroller, EchoTypesComptroller__factory, Unitroller, @@ -22,10 +22,10 @@ describe("Unitroller", () => { let root: SignerWithAddress; let accounts: SignerWithAddress[]; let unitroller: MockContract; - let brains: MockContract; + let brains: MockContract; async function unitrollerFixture() { - const ComptrollerFactory = await smock.mock("Comptroller"); + const ComptrollerFactory = await smock.mock("ComptrollerMock"); const UnitrollerFactory = await smock.mock("Unitroller"); brains = await ComptrollerFactory.deploy(); unitroller = await UnitrollerFactory.deploy(); diff --git a/tests/hardhat/VAI/VAIController.ts b/tests/hardhat/VAI/VAIController.ts index 410369627..07eb22c2a 100644 --- a/tests/hardhat/VAI/VAIController.ts +++ b/tests/hardhat/VAI/VAIController.ts @@ -5,9 +5,9 @@ import { BigNumber, Wallet, constants } from "ethers"; import { ethers } from "hardhat"; import { - Comptroller, ComptrollerLens__factory, - Comptroller__factory, + ComptrollerMock, + ComptrollerMock__factory, IAccessControlManager, VAIControllerHarness__factory, } from "../../../typechain"; @@ -31,7 +31,7 @@ const BLOCKS_PER_YEAR = 1000; interface ComptrollerFixture { usdt: BEP20Harness; accessControl: FakeContract; - comptroller: MockContract; + comptroller: MockContract; priceOracle: SimplePriceOracle; vai: VAIScenario; vaiController: MockContract; @@ -45,7 +45,7 @@ describe("VAIController", async () => { let treasuryGuardian: Wallet; let treasuryAddress: Wallet; let accessControl: FakeContract; - let comptroller: MockContract; + let comptroller: MockContract; let priceOracle: SimplePriceOracle; let vai: VAIScenario; let vaiController: MockContract; @@ -65,10 +65,12 @@ describe("VAIController", async () => { "BEP20 usdt", )) as BEP20Harness; - const accessControl = await smock.fake("AccessControlManager"); + const accessControl = await smock.fake( + "contracts/Governance/IAccessControlManager.sol:IAccessControlManager", + ); accessControl.isAllowedToCall.returns(true); - const ComptrollerFactory = await smock.mock("Comptroller"); + const ComptrollerFactory = await smock.mock("ComptrollerMock"); const comptroller = await ComptrollerFactory.deploy(); const priceOracleFactory = await ethers.getContractFactory("SimplePriceOracle"); @@ -88,7 +90,6 @@ describe("VAIController", async () => { const ComptrollerLensFactory = await smock.mock("ComptrollerLens"); const comptrollerLens = await ComptrollerLensFactory.deploy(); - await comptroller._setComptrollerLens(comptrollerLens.address); await comptroller._setAccessControl(accessControl.address); await comptroller._setVAIController(vaiController.address); diff --git a/yarn.lock b/yarn.lock index e518f561d..2bf5e1c89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3073,7 +3073,8 @@ __metadata: eslint-config-prettier: ^8.5.0 eslint-plugin-jest: ^27.1.2 ethers: ^5.6.9 - hardhat: ^2.16.1 + hardhat: ^2.10.1 + hardhat-contract-sizer: ^2.8.0 hardhat-deploy: ^0.11.14 hardhat-deploy-ethers: ^0.3.0-beta.13 hardhat-gas-reporter: ^1.0.8 @@ -4351,7 +4352,7 @@ __metadata: languageName: node linkType: hard -"cli-table3@npm:^0.6.2, cli-table3@npm:^0.6.3": +"cli-table3@npm:^0.6.0, cli-table3@npm:^0.6.2, cli-table3@npm:^0.6.3": version: 0.6.3 resolution: "cli-table3@npm:0.6.3" dependencies: @@ -6646,6 +6647,19 @@ __metadata: languageName: node linkType: hard +"hardhat-contract-sizer@npm:^2.8.0": + version: 2.10.0 + resolution: "hardhat-contract-sizer@npm:2.10.0" + dependencies: + chalk: ^4.0.0 + cli-table3: ^0.6.0 + strip-ansi: ^6.0.0 + peerDependencies: + hardhat: ^2.0.0 + checksum: 870e7cad5d96ad7288b64da0faec7962a9a18e1eaaa02ed474e4f9285cd4b1a0fc6f66326e6a7476f7063fdf99aee57f227084519b1fb3723700a2d65fc65cfa + languageName: node + linkType: hard + "hardhat-deploy-ethers@npm:^0.3.0-beta.13": version: 0.3.0-beta.13 resolution: "hardhat-deploy-ethers@npm:0.3.0-beta.13" @@ -6701,7 +6715,7 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.16.1": +"hardhat@npm:^2.10.1, hardhat@npm:^2.16.1": version: 2.17.1 resolution: "hardhat@npm:2.17.1" dependencies: