From 679b7c24b0582d135a7b9e9af672dc6ed5bae04b Mon Sep 17 00:00:00 2001 From: Defcon <104391977+defcon022@users.noreply.github.com> Date: Fri, 3 Feb 2023 10:04:18 +0530 Subject: [PATCH 01/23] Feature: stable rate borrowing (#132) * Added basic functionality for the stable rate. * Calculate Stable rate formulas. * Added Ownable functionality in the stable rate model * WIP: get supply rate method. * Added stable rate setter in the vToken. * Updated getSupplyRate and utilizationRate. * Removed supply rate from interest rate model. * CHnages after merging main branch. * Updated tests for the vToken. * Added tests for the stable rate borrowing. * Updated the stable rate model. * Updated the test for the add market. * Changes in the stable rate borrow tests. * Lint fix. * fix : Minor Fix * FIxed tests after merging main branch. * Added states for the stableborrowing. * Calculate stable rate functionality. * Added borow on stable rate functionality. * Fixed tests. * Removed unwanted check. * Added harness functions and helpers for vTOken. * Added tests for the stable rate borrows. * Refactored accrue interest calculations. * Added tests for the stable accureinterest. * Fix lint. * Refactored functionality to get intreset at stable rate. * Added repay funcitonality for the stable rate. * Refactored borrow for stable rate. * Fixed tests. * Fixed minor issue. * Tests: Repay-stable rate. * Added stable rate in the heal borrowing. * Refactored: get stable borrow amount and update borrow amount for user. * Update states for stable borrow for user. * Updated get getAccountSnapshot, borrowBalanceCurrent, borrowBalanceStored. * Took care of stable borrows. * Added funcionality to swap borrow rate. * Added event. * Tests: swap borrow rate mode. * Fixed tests. * Fixed PR comments. * Fixed PR comments. * Fixed naming conventions. * Created seperate borrow and repay methods for stable rate borrowing. * Changed StableRate file name to stableRateModel. * Added method to get stable borrow rate per block. * Fix: Stable rate formulas (1/2). * Refactored other contracts and tests for stable rate (2/2). * Added event and fixed PR comments. * Added tests fot the interest accrual at stable rate. * Fix: minor issue. * Fix: PR comments. --- contracts/BaseJumpRateModelV2.sol | 4 +- contracts/Comptroller.sol | 4 + contracts/ComptrollerInterface.sol | 2 + contracts/ErrorReporter.sol | 5 + contracts/InterestRate/StableRateModel.sol | 130 +++++ contracts/InterestRateModel.sol | 4 +- contracts/Lens/PoolLens.sol | 2 + contracts/Pool/PoolRegistry.sol | 4 + contracts/VToken.sol | 547 +++++++++++++++++++-- contracts/VTokenInterfaces.sol | 84 +++- contracts/test/UpgradedVToken.sol | 6 +- contracts/test/VTokenHarness.sol | 210 +++++++- tests/hardhat/Fork/RiskFundSwap.ts | 6 + tests/hardhat/PoolRegistry.ts | 39 ++ tests/hardhat/StableRateModel.ts | 98 ++++ tests/hardhat/Tokens/accrueInterestTest.ts | 39 +- tests/hardhat/Tokens/borrowAndRepayTest.ts | 417 +++++++++++++++- tests/hardhat/Tokens/liquidateTest.ts | 2 - tests/hardhat/Tokens/mintAndRedeemTest.ts | 2 - tests/hardhat/Tokens/swapBorrowRateMode.ts | 103 ++++ tests/hardhat/UpgradedVToken.ts | 3 + tests/hardhat/util/TokenTestHelpers.ts | 35 +- 22 files changed, 1656 insertions(+), 90 deletions(-) create mode 100644 contracts/InterestRate/StableRateModel.sol create mode 100644 tests/hardhat/StableRateModel.ts create mode 100644 tests/hardhat/Tokens/swapBorrowRateMode.ts diff --git a/contracts/BaseJumpRateModelV2.sol b/contracts/BaseJumpRateModelV2.sol index ce8693329..97c5adac8 100644 --- a/contracts/BaseJumpRateModelV2.sol +++ b/contracts/BaseJumpRateModelV2.sol @@ -93,8 +93,8 @@ abstract contract BaseJumpRateModelV2 is InterestRateModel { if (!isAllowedToCall) { revert Unauthorized(msg.sender, address(this), signature); } - - _updateJumpRateModel(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_); + + updateJumpRateModelInternal(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_); } /** diff --git a/contracts/Comptroller.sol b/contracts/Comptroller.sol index eb473cded..c040c0add 100644 --- a/contracts/Comptroller.sol +++ b/contracts/Comptroller.sol @@ -580,6 +580,10 @@ contract Comptroller is } } + function preSwapBorrowRateModeHook(address vToken) external view override { + _checkActionPauseState(vToken, Action.SWAP_RATE_MODE); + } + /*** Pool-level operations ***/ /** diff --git a/contracts/ComptrollerInterface.sol b/contracts/ComptrollerInterface.sol index d1879f90a..c8e0cc9fa 100644 --- a/contracts/ComptrollerInterface.sol +++ b/contracts/ComptrollerInterface.sol @@ -47,6 +47,8 @@ interface ComptrollerInterface { function isComptroller() external view returns (bool); + function preSwapBorrowRateModeHook(address vToken) external virtual; + /*** Liquidity/Liquidation Calculations ***/ function liquidateCalculateSeizeTokens( diff --git a/contracts/ErrorReporter.sol b/contracts/ErrorReporter.sol index 57858e109..399588569 100644 --- a/contracts/ErrorReporter.sol +++ b/contracts/ErrorReporter.sol @@ -45,4 +45,9 @@ contract TokenErrorReporter { error ReduceReservesCashValidation(); error SetInterestRateModelFreshCheck(); + + error SetStableInterestRateModelOwnerCheck(); + error SetStableInterestRateModelFreshCheck(); + + error SwapBorrowRateModeFreshnessCheck(); } diff --git a/contracts/InterestRate/StableRateModel.sol b/contracts/InterestRate/StableRateModel.sol new file mode 100644 index 000000000..9236565d2 --- /dev/null +++ b/contracts/InterestRate/StableRateModel.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.13; + +import "hardhat/console.sol"; + +/** + * @title Logic for Venus stable rate. + */ +contract StableRateModel { + event NewStableInterestParams(uint256 baseRatePerBlock, uint256 stableRatePremium, uint256 optimalStableLoanRatio); + + /// @notice Indicator that this is an InterestRateModel contract (for inspection) + bool public constant isInterestRateModel = true; + + uint256 private constant BASE = 1e18; + + /// @notice The approximate number of blocks per year that is assumed by the interest rate model + uint256 public constant blocksPerYear = 2102400; + + /// @notice The stable base interest rate which is the y-intercept when utilization rate is 0(also known by base_premium) + uint256 public baseRatePerBlock; + + /// @notice The premium rate applicable on optimal stable loan rate(also known by stable_rate_slope) + uint256 public stableRatePremium; + + /// @notice The factor to be applied to the stable rate premium before adding to the interest rate + uint256 public optimalStableLoanRatio; + + /// @notice The address of the owner, i.e. the Timelock contract, which can update parameters directly + address public owner; + + /** + * @param baseRatePerYear_ The approximate target base APR, as a mantissa (scaled by BASE) + * @param stableRatePremium_ The multiplierPerBlock after hitting a specified utilization point + * @param optimalStableLoanRatio_ Optimal stable loan rate percentage. + * @param owner_ Address of the owner for this model(Governance) + */ + constructor( + uint256 baseRatePerYear_, + uint256 stableRatePremium_, + uint256 optimalStableLoanRatio_, + address owner_ + ) { + owner = owner_; + + updateStableRateModelInternal(baseRatePerYear_, stableRatePremium_, optimalStableLoanRatio_); + } + + /** + * @notice Updates the parameters of the interest rate model (only callable by owner, i.e. Timelock) + * @param baseRatePerYear_ The approximate target base APR, as a mantissa (scaled by BASE) + * @param stableRatePremium_ The multiplierPerBlock after hitting a specified utilization point + * @param optimalStableLoanRatio_ Optimal stable loan rate percentage. + * @custom:events Emits NewStableInterestParams, after updating the parameters + * @custom:access Only governance + */ + function updateStableRateModel( + uint256 baseRatePerYear_, + uint256 stableRatePremium_, + uint256 optimalStableLoanRatio_ + ) external virtual { + require(msg.sender == owner, "StableRateModel: only owner may call this function."); + + updateStableRateModelInternal(baseRatePerYear_, stableRatePremium_, optimalStableLoanRatio_); + } + + /** + * @notice Calculates the ratio of the stable borrows to total borrows + * @param stableBorrows The amount of stable borrows in the market + * @param totalBorrows The amount of total borrows in the market + * @return The stable loan rate as a mantissa between [0, BASE] + */ + function stableLoanRatio(uint256 stableBorrows, uint256 totalBorrows) public pure returns (uint256) { + // Loan ratio is 0 when there are no stable borrows + if (totalBorrows == 0) { + return 0; + } + + return (stableBorrows * BASE) / totalBorrows; + } + + /** + * @notice Calculates the current borrow rate per block, with the error code expected by the market + * @param stableBorrows The amount of stable borrows in the market + * @param totalBorrows The amount of borrows in the market + * @param variableBorrowRate Current variable borrow rate per block of the protocol + * @return The borrow rate percentage per block as a mantissa (scaled by BASE) + */ + function getBorrowRate( + uint256 stableBorrows, + uint256 totalBorrows, + uint256 variableBorrowRate + ) external view returns (uint256) { + uint256 loanRatio = stableLoanRatio(stableBorrows, totalBorrows); + uint256 excessLoanRatio = calculateLoanRatioDiff(loanRatio); + + return (variableBorrowRate + baseRatePerBlock + ((stableRatePremium * excessLoanRatio) / BASE)); + } + + /** + * @notice Internal function to update the parameters of the interest rate model + * @param baseRatePerYear_ The approximate target base APR, as a mantissa (scaled by BASE) + * @param stableRatePremium_ The multiplierPerBlock after hitting a specified utilization point + * @param optimalStableLoanRatio_ Optimal stable loan rate percentage. + */ + function updateStableRateModelInternal( + uint256 baseRatePerYear_, + uint256 stableRatePremium_, + uint256 optimalStableLoanRatio_ + ) internal { + baseRatePerBlock = baseRatePerYear_ / blocksPerYear; + stableRatePremium = stableRatePremium_; + optimalStableLoanRatio = optimalStableLoanRatio_; + + emit NewStableInterestParams(baseRatePerBlock, stableRatePremium, optimalStableLoanRatio); + } + + /** + * @notice Calculates the difference of stableLoanRatio and the optimalStableLoanRatio + * @param loanRatio Stable loan ratio for stable borrows in the market + * @return The difference between the stableLoanRatio and optimal loan rate as a mantissa between [0, BASE] + */ + function calculateLoanRatioDiff(uint256 loanRatio) internal view returns (uint256) { + if (loanRatio == 0 || optimalStableLoanRatio > loanRatio) { + return 0; + } + + return (loanRatio - optimalStableLoanRatio); + } +} diff --git a/contracts/InterestRateModel.sol b/contracts/InterestRateModel.sol index 354b8f1e7..b596f017c 100644 --- a/contracts/InterestRateModel.sol +++ b/contracts/InterestRateModel.sol @@ -22,7 +22,7 @@ abstract contract InterestRateModel { ) external view virtual returns (uint256); /** - * @notice Calculates the current supply interest rate per block + * @notice Calculates the utilization rate for the market * @param cash The total amount of cash the market has * @param borrows The total amount of borrows the market has outstanding * @param reserves The total amount of reserves the market has @@ -30,7 +30,7 @@ abstract contract InterestRateModel { * @param badDebt The amount of badDebt in the market * @return The supply rate per block (as a percentage, and scaled by 1e18) */ - function getSupplyRate( + function utilizationRate( uint256 cash, uint256 borrows, uint256 reserves, diff --git a/contracts/Lens/PoolLens.sol b/contracts/Lens/PoolLens.sol index de29c8b31..859e0815f 100644 --- a/contracts/Lens/PoolLens.sol +++ b/contracts/Lens/PoolLens.sol @@ -53,6 +53,7 @@ contract PoolLens is ExponentialNoError { uint256 exchangeRateCurrent; uint256 supplyRatePerBlock; uint256 borrowRatePerBlock; + uint256 stableBorrowRatePerBlock; uint256 reserveFactorMantissa; uint256 supplyCaps; uint256 borrowCaps; @@ -386,6 +387,7 @@ contract PoolLens is ExponentialNoError { exchangeRateCurrent: exchangeRateCurrent, supplyRatePerBlock: vToken.supplyRatePerBlock(), borrowRatePerBlock: vToken.borrowRatePerBlock(), + stableBorrowRatePerBlock: vToken.stableBorrowRatePerBlock(), reserveFactorMantissa: vToken.reserveFactorMantissa(), supplyCaps: comptroller.supplyCaps(address(vToken)), borrowCaps: comptroller.borrowCaps(address(vToken)), diff --git a/contracts/Pool/PoolRegistry.sol b/contracts/Pool/PoolRegistry.sol index 4a5b0e16e..077067190 100644 --- a/contracts/Pool/PoolRegistry.sol +++ b/contracts/Pool/PoolRegistry.sol @@ -10,6 +10,7 @@ import { PoolRegistryInterface } from "./PoolRegistryInterface.sol"; import { Comptroller } from "../Comptroller.sol"; import { VToken } from "../VToken.sol"; import { ensureNonzeroAddress } from "../lib/validators.sol"; +import {StableRateModel} from "../InterestRate/StableRateModel.sol"; /** * @title PoolRegistry @@ -44,6 +45,9 @@ contract PoolRegistry is Ownable2StepUpgradeable, AccessControlledV8, PoolRegist address vTokenReceiver; uint256 supplyCap; uint256 borrowCap; + uint256 baseRatePerBlockForStable; + uint256 stableRatePremium; + uint256 optimalStableLoanRatio; } uint256 internal constant MAX_POOL_NAME_LENGTH = 100; diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 527f9e5fd..602d6c6fd 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -13,6 +13,7 @@ import { InterestRateModel } from "./InterestRateModel.sol"; import { ExponentialNoError } from "./ExponentialNoError.sol"; import { IProtocolShareReserve } from "./RiskFund/IProtocolShareReserve.sol"; import { ensureNonzeroAddress } from "./lib/validators.sol"; +import { StableRateModel } from "./InterestRate/StableRateModel.sol"; /** * @title VToken @@ -82,6 +83,7 @@ contract VToken is * @param admin_ Address of the administrator of this token * @param accessControlManager_ AccessControlManager contract address * @param riskManagement Addresses of risk & income related contracts + * @param stableRateModel_ Address of the stable interest rate model * @param reserveFactorMantissa_ Percentage of borrow interest that goes to reserves (from 0 to 1e18) * @custom:error ZeroAddressNotAllowed is thrown when admin address is zero * @custom:error ZeroAddressNotAllowed is thrown when shortfall contract address is zero @@ -98,7 +100,8 @@ contract VToken is address admin_, address accessControlManager_, RiskManagementInit memory riskManagement, - uint256 reserveFactorMantissa_ + uint256 reserveFactorMantissa_, + StableRateModel stableRateModel_ ) external initializer { ensureNonzeroAddress(admin_); @@ -114,7 +117,8 @@ contract VToken is admin_, accessControlManager_, riskManagement, - reserveFactorMantissa_ + reserveFactorMantissa_, + stableRateModel_ ); } @@ -277,6 +281,33 @@ contract VToken is return NO_ERROR; } + /** + * @notice Returns the current per-block borrow interest rate for this vToken + * @return rate The borrow interest rate per block, scaled by 1e18 + */ + function stableBorrowRatePerBlock() public view override returns (uint256) { + uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + return stableRateModel.getBorrowRate(stableBorrows, totalBorrows, variableBorrowRate); + } + + /** + * @notice Returns the current per-block supply interest rate for this v + * @return rate The supply interest rate per block, scaled by 1e18 + */ + function supplyRatePerBlock() external view override returns (uint256) { + if (totalBorrows == 0) { + return 0; + } + uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 variableBorrows = totalBorrows - stableBorrows; + uint256 averageMarketBorrowRate = ((variableBorrows * variableBorrowRate) + + (stableBorrows * averageStableBorrowRate)) / totalBorrows; + return + (averageMarketBorrowRate * utilizationRate * (mantissaOne - reserveFactorMantissa)) / + (mantissaOne * mantissaOne); + } + /** * @notice Sender redeems vTokens in exchange for the underlying asset * @dev Accrues interest whether or not the operation succeeds, unless reverted @@ -637,15 +668,20 @@ contract VToken is * @return borrowBalance Amount owed in terms of underlying * @return exchangeRate Stored exchange rate */ - function getAccountSnapshot( - address account - ) + function getAccountSnapshot(address account) external view override - returns (uint256 error, uint256 vTokenBalance, uint256 borrowBalance, uint256 exchangeRate) + returns ( + uint256 error, + uint256 vTokenBalance, + uint256 borrowBalance, + uint256 exchangeRate + ) { - return (NO_ERROR, accountTokens[account], _borrowBalanceStored(account), _exchangeRateStored()); + (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); + uint256 borrowAmount = _borrowBalanceStored(account) + stableBorrowAmount; + return (NO_ERROR, accountTokens[account], borrowAmount, _exchangeRateStored()); } /** @@ -682,10 +718,95 @@ contract VToken is /** * @notice Return the borrow balance of account based on stored data * @param account The address whose balance should be calculated - * @return borrowBalance The calculated balance + * @return borrowBalance the calculated balance */ - function borrowBalanceStored(address account) external view override returns (uint256) { - return _borrowBalanceStored(account); + function _stableBorrowBalanceStored(address account) + internal + view + returns ( + uint256, + uint256, + Exp memory + ) + { + /* Get borrowBalance and borrowIndex */ + StableBorrowSnapshot storage borrowSnapshot = accountStableBorrows[account]; + Exp memory simpleStableInterestFactor; + + /* If borrowBalance = 0 then borrowIndex is likely also 0. + * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. + */ + if (borrowSnapshot.principal == 0) { + return (0, borrowSnapshot.interestIndex, simpleStableInterestFactor); + } + + uint256 currentBlockNumber = _getBlockNumber(); + + /* Short-circuit accumulating 0 interest */ + if (borrowSnapshot.lastBlockAccrued == currentBlockNumber) { + return (borrowSnapshot.principal, borrowSnapshot.interestIndex, simpleStableInterestFactor); + } + + /* Calculate the number of blocks elapsed since the last accrual */ + uint256 blockDelta = currentBlockNumber - borrowSnapshot.lastBlockAccrued; + + simpleStableInterestFactor = mul_(Exp({ mantissa: borrowSnapshot.stableRateMantissa }), blockDelta); + + uint256 stableBorrowIndexNew = mul_ScalarTruncateAddUInt( + simpleStableInterestFactor, + borrowSnapshot.interestIndex, + borrowSnapshot.interestIndex + ); + uint256 principalUpdated = (borrowSnapshot.principal * stableBorrowIndexNew) / borrowSnapshot.interestIndex; + + return (principalUpdated, stableBorrowIndexNew, simpleStableInterestFactor); + } + + /** + * @notice Return the stable borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return Stable borrowBalance the calculated balance + * @custom:events UpdatedUserStableBorrowBalance event emitted after updating account's borrow + */ + function _updateUserStableBorrowBalance(address account) internal returns (uint256) { + StableBorrowSnapshot storage borrowSnapshot = accountStableBorrows[account]; + + Exp memory simpleStableInterestFactor; + uint256 principalUpdated; + uint256 stableBorrowIndexNew; + + (principalUpdated, stableBorrowIndexNew, simpleStableInterestFactor) = _stableBorrowBalanceStored(account); + uint256 stableBorrowsPrior = stableBorrows; + uint256 totalBorrowsPrior = totalBorrows; + uint256 totalReservesPrior = totalReserves; + + uint256 stableInterestAccumulated = mul_ScalarTruncate(simpleStableInterestFactor, borrowSnapshot.principal); + uint256 stableBorrowsUpdated = stableBorrowsPrior + stableInterestAccumulated; + uint256 totalBorrowsUpdated = totalBorrowsPrior + stableInterestAccumulated; + uint256 totalReservesUpdated = mul_ScalarTruncateAddUInt( + Exp({ mantissa: reserveFactorMantissa }), + stableInterestAccumulated, + totalReservesPrior + ); + + stableBorrows = stableBorrowsUpdated; + totalBorrows = totalBorrowsUpdated; + totalReserves = totalReservesUpdated; + borrowSnapshot.interestIndex = stableBorrowIndexNew; + borrowSnapshot.principal = principalUpdated; + borrowSnapshot.lastBlockAccrued = _getBlockNumber(); + + emit UpdatedUserStableBorrowBalance(account, principalUpdated); + return principalUpdated; + } + + /** + * @notice Accrue interest then return the up-to-date exchange rate + * @return exchangeRate Calculated exchange rate scaled by 1e18 + + function borrowBalanceStored(address account) public view override returns (uint256) { + (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); + return _borrowBalanceStored(account) + stableBorrowAmount; } /** @@ -726,7 +847,7 @@ contract VToken is /* Read the previous values out of storage */ uint256 cashPrior = _getCashPrior(); - uint256 borrowsPrior = totalBorrows; + uint256 borrowsPrior = totalBorrows - stableBorrows; uint256 reservesPrior = totalReserves; uint256 borrowIndexPrior = borrowIndex; @@ -740,15 +861,15 @@ contract VToken is /* * Calculate the interest accumulated into borrows and reserves and the new index: * simpleInterestFactor = borrowRate * blockDelta - * interestAccumulated = simpleInterestFactor * totalBorrows - * totalBorrowsNew = interestAccumulated + totalBorrows + * interestAccumulated = simpleInterestFactor * totalBorrows(for variable borrows only) + * totalBorrowsNew = interestAccumulated + totalBorrows(variable + stable) * totalReservesNew = interestAccumulated * reserveFactor + totalReserves * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex */ Exp memory simpleInterestFactor = mul_(Exp({ mantissa: borrowRateMantissa }), blockDelta); uint256 interestAccumulated = mul_ScalarTruncate(simpleInterestFactor, borrowsPrior); - uint256 totalBorrowsNew = interestAccumulated + borrowsPrior; + uint256 totalBorrowsNew = interestAccumulated + borrowsPrior + stableBorrows; uint256 totalReservesNew = mul_ScalarTruncateAddUInt( Exp({ mantissa: reserveFactorMantissa }), interestAccumulated, @@ -766,8 +887,47 @@ contract VToken is totalBorrows = totalBorrowsNew; totalReserves = totalReservesNew; + uint256 err = _accrueStableInterest(blockDelta); + + if (err != 0) { + return err; + } + /* We emit an AccrueInterest event */ - emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew); + emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew, stableBorrowIndex); + + return NO_ERROR; + } + + /** + * @notice Applies accrued stable interest to stable borrows and reserves + * @dev This calculates interest accrued from the last checkpointed block + * up to the current block and writes new checkpoint to storage. + * @param blockDelta Number of blocks between last accrual and current block + * @return Always NO_ERROR + * @custom:events Emits AccrueInterest event on success + * @custom:access Not restricted + */ + function _accrueStableInterest(uint256 blockDelta) internal returns (uint256) { + uint256 stableIndexPrior = stableBorrowIndex; + + uint256 stableBorrowRateMantissa = stableBorrowRatePerBlock(); + require(stableBorrowRateMantissa <= stableBorrowRateMaxMantissa, "vToken: stable borrow rate is absurdly high"); + + Exp memory simpleStableInterestFactor = mul_(Exp({ mantissa: stableBorrowRateMantissa }), blockDelta); + + uint256 stableBorrowIndexNew = mul_ScalarTruncateAddUInt( + simpleStableInterestFactor, + stableIndexPrior, + stableIndexPrior + ); + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We write the previously calculated values into storage */ + stableBorrowIndex = stableBorrowIndexNew; return NO_ERROR; } @@ -904,12 +1064,39 @@ contract VToken is emit Redeem(redeemer, redeemAmount, redeemTokens, balanceAfter); } + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits Borrow event; may emit AccrueInterest + * @custom:error BorrowCashNotAvailable is thrown when the protocol has insufficient cash + * @custom:access Not restricted + */ + function borrow(uint256 borrowAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // borrowFresh emits borrow-specific logs on errors, so we don't need to + _borrowFresh(payable(msg.sender), borrowAmount, InterestRateMode.VARIABLE); + return NO_ERROR; + } + + function borrowStable(uint256 borrowAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // borrowFresh emits borrow-specific logs on errors, so we don't need to + _borrowFresh(payable(msg.sender), borrowAmount, InterestRateMode.STABLE); + return NO_ERROR; + } + /** * @notice Users borrow assets from the protocol to their own address * @param borrower User who borrows the assets * @param borrowAmount The amount of the underlying asset to borrow + * @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable */ - function _borrowFresh(address borrower, uint256 borrowAmount) internal { + function _borrowFresh( + address payable borrower, + uint256 borrowAmount, + InterestRateMode interestRateMode + ) internal { /* Fail if borrow not allowed */ comptroller.preBorrowHook(address(this), borrower, borrowAmount); @@ -923,25 +1110,65 @@ contract VToken is revert BorrowCashNotAvailable(); } - /* - * We calculate the new borrower and total borrow balances, failing on overflow: - * accountBorrowNew = accountBorrow + borrowAmount - * totalBorrowsNew = totalBorrows + borrowAmount - */ - uint256 accountBorrowsPrev = _borrowBalanceStored(borrower); - uint256 accountBorrowsNew = accountBorrowsPrev + borrowAmount; - uint256 totalBorrowsNew = totalBorrows + borrowAmount; + uint256 totalBorrowsNew; + uint256 accountBorrowsNew; + if (InterestRateMode(interestRateMode) == InterestRateMode.STABLE) { + /* + * We calculate the new borrower and total borrow balances, failing on overflow: + * accountBorrowNew = accountStableBorrow + borrowAmount + * totalBorrowsNew = totalBorrows + borrowAmount + */ + uint256 accountBorrowsPrev = _updateUserStableBorrowBalance(borrower); + accountBorrowsNew = accountBorrowsPrev + borrowAmount; + totalBorrowsNew = totalBorrows + borrowAmount; - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) + /** + * Calculte the average stable borrow rate for the total stable borrows + */ + uint256 stableBorrowsNew = stableBorrows + borrowAmount; + uint256 stableBorrowRate = stableBorrowRatePerBlock(); + uint256 averageStableBorrowRateNew = ((stableBorrows * averageStableBorrowRate) + + (borrowAmount * stableBorrowRate)) / stableBorrowsNew; + + uint256 stableRateMantissaNew = ((accountBorrowsPrev * accountStableBorrows[borrower].stableRateMantissa) + + (borrowAmount * stableBorrowRate)) / accountBorrowsNew; + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We write the previously calculated values into storage. + * Note: Avoid token reentrancy attacks by writing increased borrow before external transfer. + */ + + accountStableBorrows[borrower].principal = accountBorrowsNew; + accountStableBorrows[borrower].interestIndex = stableBorrowIndex; + accountStableBorrows[borrower].stableRateMantissa = stableRateMantissaNew; + stableBorrows = stableBorrowsNew; + averageStableBorrowRate = averageStableBorrowRateNew; + } else { + /* + * We calculate the new borrower and total borrow balances, failing on overflow: + * accountBorrowNew = accountBorrow + borrowAmount + * totalBorrowsNew = totalBorrows + borrowAmount + */ + uint256 accountBorrowsPrev = _borrowBalanceStored(borrower); + accountBorrowsNew = accountBorrowsPrev + borrowAmount; + totalBorrowsNew = totalBorrows + borrowAmount; + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We write the previously calculated values into storage. + * Note: Avoid token reentrancy attacks by writing increased borrow before external transfer. + */ + accountBorrows[borrower].principal = accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + } - /* - * We write the previously calculated values into storage. - * Note: Avoid token reentrancy attacks by writing increased borrow before external transfer. - `*/ - accountBorrows[borrower].principal = accountBorrowsNew; - accountBorrows[borrower].interestIndex = borrowIndex; totalBorrows = totalBorrowsNew; /* @@ -955,14 +1182,83 @@ contract VToken is emit Borrow(borrower, borrowAmount, accountBorrowsNew, totalBorrowsNew); } + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrow(uint256 repayAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, msg.sender, repayAmount, InterestRateMode.VARIABLE); + return NO_ERROR; + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrowStable(uint256 repayAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, msg.sender, repayAmount, InterestRateMode.STABLE); + return NO_ERROR; + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrowBehalf(address borrower, uint256 repayAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, borrower, repayAmount, InterestRateMode.VARIABLE); + return NO_ERROR; + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrowStableBehalf(address borrower, uint256 repayAmount) + external + override + nonReentrant + returns (uint256) + { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, borrower, repayAmount, InterestRateMode.STABLE); + return NO_ERROR; + } + /** * @notice Borrows are repaid by another user (possibly the borrower). * @param payer the account paying off the borrow * @param borrower the account with the debt being payed off - * @param repayAmount the amount of underlying tokens being returned, or type(uint256).max for the full outstanding amount + * @param repayAmount the amount of underlying tokens being returned, or -1 for the full outstanding amount + * @param interestRateMode The interest rate mode of the debt the user wants to repay: 1 for Stable, 2 for Variable * @return (uint) the actual repayment amount. */ - function _repayBorrowFresh(address payer, address borrower, uint256 repayAmount) internal returns (uint256) { + function _repayBorrowFresh( + address payer, + address borrower, + uint256 repayAmount, + InterestRateMode interestRateMode + ) internal returns (uint256) { /* Fail if repayBorrow not allowed */ comptroller.preRepayHook(address(this), borrower); @@ -971,8 +1267,17 @@ contract VToken is revert RepayBorrowFreshnessCheck(); } - /* We fetch the amount the borrower owes, with accumulated interest */ - uint256 accountBorrowsPrev = _borrowBalanceStored(borrower); + uint256 accountBorrowsPrev; + if (InterestRateMode(interestRateMode) == InterestRateMode.STABLE) { + accountBorrowsPrev = _updateUserStableBorrowBalance(borrower); + } else { + /* We fetch the amount the borrower owes, with accumulated interest */ + accountBorrowsPrev = _borrowBalanceStored(borrower); + } + + if (accountBorrowsPrev == 0) { + return 0; + } uint256 repayAmountFinal = repayAmount >= accountBorrowsPrev ? accountBorrowsPrev : repayAmount; @@ -996,9 +1301,31 @@ contract VToken is uint256 accountBorrowsNew = accountBorrowsPrev - actualRepayAmount; uint256 totalBorrowsNew = totalBorrows - actualRepayAmount; - /* We write the previously calculated values into storage */ - accountBorrows[borrower].principal = accountBorrowsNew; - accountBorrows[borrower].interestIndex = borrowIndex; + if (InterestRateMode(interestRateMode) == InterestRateMode.STABLE) { + uint256 stableBorrowsNew = stableBorrows - actualRepayAmount; + + uint256 averageStableBorrowRateNew; + if (stableBorrowsNew == 0) { + averageStableBorrowRateNew = 0; + } else { + uint256 stableRateMantissa = accountStableBorrows[borrower].stableRateMantissa; + + unchecked { + averageStableBorrowRateNew = + ((stableBorrows * averageStableBorrowRate) - (actualRepayAmount * stableRateMantissa)) / + stableBorrowsNew; + } + } + + accountStableBorrows[borrower].principal = accountBorrowsNew; + accountStableBorrows[borrower].interestIndex = stableBorrowIndex; + + stableBorrows = stableBorrowsNew; + averageStableBorrowRate = averageStableBorrowRateNew; + } else { + accountBorrows[borrower].principal = accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + } totalBorrows = totalBorrowsNew; /* We emit a RepayBorrow event */ @@ -1007,6 +1334,84 @@ contract VToken is return actualRepayAmount; } + /** + * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa + * @param rateMode The rate mode that the user wants to swap to + **/ + function swapBorrowRateMode(uint256 rateMode) external { + /* Fail if swapBorrowRateMode not allowed */ + comptroller.preSwapBorrowRateModeHook(address(this)); + + accrueInterest(); + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != _getBlockNumber()) { + revert SwapBorrowRateModeFreshnessCheck(); + } + + address account = msg.sender; + uint256 variableDebt = _borrowBalanceStored(account); + uint256 stableDebt = _updateUserStableBorrowBalance(account); + uint256 accountBorrowsNew = variableDebt + stableDebt; + uint256 stableBorrowsNew; + uint256 averageStableBorrowRateNew; + + if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { + require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); + + stableBorrowsNew = stableBorrows + variableDebt; + uint256 stableBorrowRate = stableBorrowRatePerBlock(); + + averageStableBorrowRateNew = + ((stableBorrows * averageStableBorrowRate) + (variableDebt * stableBorrowRate)) / + stableBorrowsNew; + + uint256 stableRateMantissaNew = ((stableDebt * accountStableBorrows[account].stableRateMantissa) + + (variableDebt * stableBorrowRate)) / accountBorrowsNew; + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + accountStableBorrows[account].principal = stableDebt + variableDebt; + accountStableBorrows[account].interestIndex = stableBorrowIndex; + accountStableBorrows[account].stableRateMantissa = stableRateMantissaNew; + + accountBorrows[account].principal = 0; + accountBorrows[account].interestIndex = borrowIndex; + } else { + require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); + + stableBorrowsNew = stableBorrows - stableDebt; + + uint256 stableRateMantissa = accountStableBorrows[account].stableRateMantissa; + + if (stableBorrowsNew == 0) { + averageStableBorrowRateNew = 0; + } else { + unchecked { + averageStableBorrowRateNew = + ((stableBorrows * averageStableBorrowRate) - (stableDebt * stableRateMantissa)) / + stableBorrowsNew; + } + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + accountBorrows[account].principal = accountBorrowsNew; + accountBorrows[account].interestIndex = borrowIndex; + + accountStableBorrows[account].principal = 0; + accountStableBorrows[account].interestIndex = stableBorrowIndex; + } + + stableBorrows = stableBorrowsNew; + averageStableBorrowRate = averageStableBorrowRateNew; + + emit SwapBorrowRateMode(account, rateMode); + } + /** * @notice The sender liquidates the borrowers collateral. * The collateral seized is transferred to the liquidator. @@ -1088,7 +1493,9 @@ contract VToken is } /* Fail if repayBorrow fails */ - uint256 actualRepayAmount = _repayBorrowFresh(liquidator, borrower, repayAmount); + // Repay for both types of interest rate: stable and variable + uint256 actualRepayAmount = _repayBorrowFresh(liquidator, borrower, repayAmount, InterestRateMode.STABLE) + + _repayBorrowFresh(liquidator, borrower, repayAmount, InterestRateMode.VARIABLE); ///////////////////////// // EFFECTS & INTERACTIONS @@ -1294,6 +1701,58 @@ contract VToken is /*** Safe Token ***/ + /** + * @notice Accrues interest and updates the stable interest rate model using _setStableInterestRateModelFresh + * @dev Admin function to accrue interest and update the stable interest rate model + * @param newStableInterestRateModel The new interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + * @custom:events Emits NewMarketInterestRateModel event; may emit AccrueInterest + * @custom:events Emits NewMarketStableInterestRateModel, after setting the new stable rate model + * @custom:access Controlled by AccessControlManager + */ + function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public override returns (uint256) { + bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( + msg.sender, + "setStableInterestRateModel(address)" + ); + + // Check if caller has call permissions + if (!canCallFunction) { + revert SetStableInterestRateModelOwnerCheck(); + } + accrueInterest(); + // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. + return _setStableInterestRateModelFresh(newStableInterestRateModel); + } + + /** + * @notice Updates the stable interest rate model (requires fresh interest accrual) + * @dev Admin function to update the stable interest rate model + * @param newStableInterestRateModel The new stable interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setStableInterestRateModelFresh(StableRateModel newStableInterestRateModel) internal returns (uint256) { + // Used to store old model for use in the event that is emitted on success + StableRateModel oldStableInterestRateModel; + + // We fail gracefully unless market's block number equals current block number + if (accrualBlockNumber != _getBlockNumber()) { + revert SetStableInterestRateModelFreshCheck(); + } + + // Track the market's current stable interest rate model + oldStableInterestRateModel = stableRateModel; + + // Ensure invoke newInterestRateModel.isInterestRateModel() returns true + require(newStableInterestRateModel.isInterestRateModel(), "marker method returned false"); + + // Set the interest rate model to newStableInterestRateModel + stableRateModel = newStableInterestRateModel; + + // Emit NewMarketStableInterestRateModel(oldStableInterestRateModel, newStableInterestRateModel) + emit NewMarketStableInterestRateModel(oldStableInterestRateModel, newStableInterestRateModel); + } + /** * @dev Similar to ERC-20 transfer, but handles tokens that have transfer fees. * This function returns the actual amount received, @@ -1391,7 +1850,8 @@ contract VToken is address admin_, address accessControlManager_, RiskManagementInit memory riskManagement, - uint256 reserveFactorMantissa_ + uint256 reserveFactorMantissa_, + StableRateModel stableRateModel_ ) internal onlyInitializing { __Ownable2Step_init(); __AccessControlled_init_unchained(accessControlManager_); @@ -1410,6 +1870,9 @@ contract VToken is // Set the interest rate model (depends on block number / borrow index) _setInterestRateModelFresh(interestRateModel_); + // Set the interest rate model (depends on block number / borrow index) + _setStableInterestRateModelFresh(stableRateModel_); + _setReserveFactorFresh(reserveFactorMantissa_); name = name_; diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 7483e68b8..7da66c49f 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -2,10 +2,9 @@ pragma solidity 0.8.13; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol"; - import { ComptrollerInterface } from "./ComptrollerInterface.sol"; import { InterestRateModel } from "./InterestRateModel.sol"; +import { StableRateModel } from "./InterestRate/StableRateModel.sol"; /** * @title VTokenStorage @@ -60,6 +59,9 @@ contract VTokenStorage { // Maximum fraction of interest that can be set aside for reserves uint256 internal constant MAX_RESERVE_FACTOR_MANTISSA = 1e18; + // Maximum stable borrow rate that can ever be applied (.0005% / block) + uint256 internal constant stableBorrowRateMaxMantissa = 0.0005e16; + /** * @notice Contract which oversees inter-vToken operations */ @@ -128,11 +130,50 @@ contract VTokenStorage { address public shortfall; /** + * @notice Model which tells what the current stable interest rate should be + */ + StableRateModel public stableRateModel; + + /** + * @notice Total amount of outstanding stable borrows of the underlying in this market + */ + uint256 public stableBorrows; + + /** + * @notice Accumulator of the total earned stable interest rate since the opening of the market + */ + uint256 public stableBorrowIndex; + + /** + * @notice Average of all of the stable borrows + */ + uint256 public averageStableBorrowRate; + + struct StableBorrowSnapshot { + uint256 principal; + uint256 stableRateMantissa; + uint256 interestIndex; + uint256 lastBlockAccrued; + } + + // Mapping of account addresses to outstanding stable borrow balances + mapping(address => StableBorrowSnapshot) internal accountStableBorrows; + + /** + * @notice Types of the Interest rate model + */ + enum InterestRateMode { + NONE, + STABLE, + VARIABLE + } + + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[50] private __gap; + uint256[45] private __gap; } /** @@ -151,7 +192,13 @@ abstract contract VTokenInterface is VTokenStorage { /** * @notice Event emitted when interest is accrued */ - event AccrueInterest(uint256 cashPrior, uint256 interestAccumulated, uint256 borrowIndex, uint256 totalBorrows); + event AccrueInterest( + uint256 cashPrior, + uint256 interestAccumulated, + uint256 borrowIndex, + uint256 totalBorrows, + uint256 stableBorrowIndex + ); /** * @notice Event emitted when tokens are minted @@ -206,6 +253,11 @@ abstract contract VTokenInterface is VTokenStorage { uint256 seizeTokens ); + /** + * @notice Event emitted when a borrow rate mode is swapped for account + */ + event SwapBorrowRateMode(address account, uint256 swappedBorrowMode); + /*** Admin Events ***/ /** @@ -236,6 +288,11 @@ abstract contract VTokenInterface is VTokenStorage { */ event NewProtocolSeizeShare(uint256 oldProtocolSeizeShareMantissa, uint256 newProtocolSeizeShareMantissa); + /** + * @notice Event emitted when stableInterestRateModel is changed + */ + event NewMarketStableInterestRateModel(StableRateModel oldInterestRateModel, StableRateModel newInterestRateModel); + /** * @notice Event emitted when the reserve factor is changed */ @@ -270,6 +327,11 @@ abstract contract VTokenInterface is VTokenStorage { * @notice Event emitted when tokens are swept */ event SweepToken(address indexed token); + + /** + * @notice Event emitted after updating stable borrow balance for borrower + */ + event UpdatedUserStableBorrowBalance(address borrower, uint256 updatedPrincipal); /*** User Interface ***/ @@ -283,10 +345,16 @@ abstract contract VTokenInterface is VTokenStorage { function borrow(uint256 borrowAmount) external virtual returns (uint256); + function borrowStable(uint256 borrowAmount) external virtual returns (uint256); + function repayBorrow(uint256 repayAmount) external virtual returns (uint256); + function repayBorrowStable(uint256 repayAmount) external virtual returns (uint256); + function repayBorrowBehalf(address borrower, uint256 repayAmount) external virtual returns (uint256); + function repayBorrowStableBehalf(address borrower, uint256 repayAmount) external virtual returns (uint256); + function liquidateBorrow( address borrower, uint256 repayAmount, @@ -327,6 +395,10 @@ abstract contract VTokenInterface is VTokenStorage { function addReserves(uint256 addAmount) external virtual; + function stableBorrowRatePerBlock() public view virtual returns (uint256); + + function supplyRatePerBlock() external view virtual returns (uint256); + function totalBorrowsCurrent() external virtual returns (uint256); function balanceOfUnderlying(address owner) external virtual returns (uint256); @@ -360,4 +432,8 @@ abstract contract VTokenInterface is VTokenStorage { function isVToken() external pure virtual returns (bool) { return true; } + + function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public virtual returns (uint256); + + function addReserves(uint256 addAmount) external virtual; } diff --git a/contracts/test/UpgradedVToken.sol b/contracts/test/UpgradedVToken.sol index de86227ca..f9e7f3fd3 100644 --- a/contracts/test/UpgradedVToken.sol +++ b/contracts/test/UpgradedVToken.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.10; -import { AccessControlManager } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlManager.sol"; - import { VToken } from "../VToken.sol"; import { ComptrollerInterface } from "../ComptrollerInterface.sol"; import { InterestRateModel } from "../InterestRateModel.sol"; +import {StableRateModel} from "../InterestRate/StableRateModel.sol"; /** * @title Venus's VToken Contract @@ -24,6 +23,7 @@ contract UpgradedVToken is VToken { * @param decimals_ ERC-20 decimal precision of this token * @param admin_ Address of the administrator of this token * @param riskManagement Addresses of risk fund contracts + * @param stableRateModel_ The address of the stable interest rate model */ /// @notice We added this new function to test contract upgrade @@ -42,6 +42,7 @@ contract UpgradedVToken is VToken { address payable admin_, address accessControlManager_, RiskManagementInit memory riskManagement, + StableRateModel stableRateModel_, uint256 reserveFactorMantissa_ ) public reinitializer(2) { super._initialize( @@ -55,6 +56,7 @@ contract UpgradedVToken is VToken { admin_, accessControlManager_, riskManagement, + stableRateModel_, reserveFactorMantissa_ ); } diff --git a/contracts/test/VTokenHarness.sol b/contracts/test/VTokenHarness.sol index 3690c212e..dda900f56 100644 --- a/contracts/test/VTokenHarness.sol +++ b/contracts/test/VTokenHarness.sol @@ -5,6 +5,7 @@ import { AccessControlManager } from "@venusprotocol/governance-contracts/contra import { VToken } from "../VToken.sol"; import { InterestRateModel } from "../InterestRateModel.sol"; +import {StableRateModel} from "../InterestRate/StableRateModel.sol"; contract VTokenHarness is VToken { uint256 public blockNumber; @@ -13,6 +14,54 @@ contract VTokenHarness is VToken { mapping(address => bool) public failTransferToAddresses; + function initializeHarness( + address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint256 initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_, + address payable admin_, + AccessControlManager accessControlManager_, + RiskManagementInit memory riskManagement, + StableRateModel stableRateModel_ + ) external initializer { + blockNumber = 100000; + super._initialize( + underlying_, + comptroller_, + interestRateModel_, + initialExchangeRateMantissa_, + name_, + symbol_, + decimals_, + admin_, + accessControlManager_, + riskManagement, + stableRateModel_ + ); + } + + function _exchangeRateStored() internal view override returns (uint256) { + if (harnessExchangeRateStored) { + return harnessExchangeRate; + } + return super._exchangeRateStored(); + } + + function _getBlockNumber() internal view override returns (uint256) { + return blockNumber; + } + + function getBorrowRateMaxMantissa() external pure returns (uint256) { + return borrowRateMaxMantissa; + } + + function getStableBorrowRateMaxMantissa() external pure returns (uint256) { + return stableBorrowRateMaxMantissa; + } + function harnessSetAccrualBlockNumber(uint256 accrualBlockNumber_) external { accrualBlockNumber = accrualBlockNumber_; } @@ -37,6 +86,10 @@ contract VTokenHarness is VToken { totalBorrows = totalBorrows_; } + function harnessSetStableBorrows(uint256 stableBorrows_) external { + stableBorrows = stableBorrows_; + } + function harnessSetTotalReserves(uint256 totalReserves_) external { totalReserves = totalReserves_; } @@ -64,20 +117,77 @@ contract VTokenHarness is VToken { super._redeemFresh(account, vTokenAmount, underlyingAmount); } - function harnessSetAccountBorrows(address account, uint256 principal, uint256 interestIndex) external { + function harnessAccountBorrows(address account) external view returns (uint256 principal, uint256 interestIndex) { + BorrowSnapshot memory snapshot = accountBorrows[account]; + return (snapshot.principal, snapshot.interestIndex); + } + + function harnessAccountStableBorrows(address account) + external + view + returns ( + uint256 principal, + uint256 interestIndex, + uint256 lastBlockAccrued + ) + { + StableBorrowSnapshot memory snapshot = accountStableBorrows[account]; + return (snapshot.principal, snapshot.interestIndex, snapshot.lastBlockAccrued); + } + + function harnessSetAccountBorrows( + address account, + uint256 principal, + uint256 interestIndex + ) external { accountBorrows[account] = BorrowSnapshot({ principal: principal, interestIndex: interestIndex }); } + function harnessSetAccountStableBorrows( + address account, + uint256 principal, + uint256 interestIndex, + uint256 stableRateMantissa, + uint256 lastBlock + ) external { + accountStableBorrows[account] = StableBorrowSnapshot({ + principal: principal, + interestIndex: interestIndex, + stableRateMantissa: stableRateMantissa, + lastBlockAccrued: lastBlock + }); + } + function harnessSetBorrowIndex(uint256 borrowIndex_) external { borrowIndex = borrowIndex_; } + function harnessSetStableBorrowIndex(uint256 stableBorrowIndex_) external { + stableBorrowIndex = stableBorrowIndex_; + } + function harnessBorrowFresh(address payable account, uint256 borrowAmount) external { - _borrowFresh(account, borrowAmount); + _borrowFresh(account, borrowAmount, InterestRateMode.VARIABLE); + } + + function harnessBorrowStableFresh(address payable account, uint256 borrowAmount) external { + _borrowFresh(account, borrowAmount, InterestRateMode.STABLE); } - function harnessRepayBorrowFresh(address payer, address account, uint256 repayAmount) external { - _repayBorrowFresh(payer, account, repayAmount); + function harnessRepayBorrowFresh( + address payer, + address account, + uint256 repayAmount + ) external { + _repayBorrowFresh(payer, account, repayAmount, InterestRateMode.VARIABLE); + } + + function harnessRepayBorrowStableFresh( + address payer, + address account, + uint256 repayAmount + ) external { + _repayBorrowFresh(payer, account, repayAmount, InterestRateMode.STABLE); } function harnessLiquidateBorrowFresh( @@ -124,6 +234,56 @@ contract VTokenHarness is VToken { return super._doTransferOut(to, amount); } + function harnessSetAvgStableBorrowRate(uint256 averageStableBorrowRate_) public { + averageStableBorrowRate = averageStableBorrowRate_; + } + + function harnessStableBorrows(uint256 stableBorrows_) public { + stableBorrows = stableBorrows_; + } + + function accrueStableInterest(uint256 blockDelta) public returns (uint256) { + return _accrueStableInterest(blockDelta); + } + + function harnessUpdateUserStableBorrowBalance(address account) public returns (uint256) { + return _updateUserStableBorrowBalance(account); + } +} + +contract VTokenScenario is VToken { + constructor( + address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint256 initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_, + address payable admin_, + AccessControlManager accessControlManager_, + VTokenInterface.RiskManagementInit memory riskManagement, + StableRateModel stableRateModel_ + ) { + initialize( + underlying_, + comptroller_, + interestRateModel_, + initialExchangeRateMantissa_, + name_, + symbol_, + decimals_, + admin_, + accessControlManager_, + riskManagement, + stableRateModel_ + ); + } + + function setTotalBorrows(uint256 totalBorrows_) public { + totalBorrows = totalBorrows_; + } + function _exchangeRateStored() internal view override returns (uint256) { if (harnessExchangeRateStored) { return harnessExchangeRate; @@ -132,6 +292,46 @@ contract VTokenHarness is VToken { } function _getBlockNumber() internal view override returns (uint256) { - return blockNumber; + ComptrollerScenario comptrollerScenario = ComptrollerScenario(address(comptroller)); + return comptrollerScenario.blockNumber(); + } +} + +contract VEvil is VTokenScenario { + constructor( + address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint256 initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_, + address payable admin_, + AccessControlManager accessControlManager_, + VTokenInterface.RiskManagementInit memory riskManagement, + StableRateModel stableRateModel_ + ) + VTokenScenario( + underlying_, + comptroller_, + interestRateModel_, + initialExchangeRateMantissa_, + name_, + symbol_, + decimals_, + admin_, + accessControlManager_, + riskManagement, + stableRateModel_ + ) + {} + + function evilSeize( + VToken treasure, + address liquidator, + address borrower, + uint256 seizeTokens + ) public { + treasure.seize(liquidator, borrower, seizeTokens); } } diff --git a/tests/hardhat/Fork/RiskFundSwap.ts b/tests/hardhat/Fork/RiskFundSwap.ts index a3a883220..216b24fa8 100644 --- a/tests/hardhat/Fork/RiskFundSwap.ts +++ b/tests/hardhat/Fork/RiskFundSwap.ts @@ -201,6 +201,9 @@ const riskFundFixture = async (): Promise => { vTokenReceiver: admin.address, supplyCap: initialSupply, borrowCap: initialSupply, + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }); await BUSD.faucet(initialSupply); @@ -213,6 +216,9 @@ const riskFundFixture = async (): Promise => { vTokenReceiver: admin.address, supplyCap: initialSupply, borrowCap: initialSupply, + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }); await riskFund.setPoolRegistry(poolRegistry.address); diff --git a/tests/hardhat/PoolRegistry.ts b/tests/hardhat/PoolRegistry.ts index 37f1bceae..0a611cbfe 100644 --- a/tests/hardhat/PoolRegistry.ts +++ b/tests/hardhat/PoolRegistry.ts @@ -65,6 +65,9 @@ describe("PoolRegistry: Tests", function () { vTokenReceiver: owner.address, supplyCap: INITIAL_SUPPLY, borrowCap: INITIAL_SUPPLY, + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }; return { ...defaults, ...overwrites }; }; @@ -157,6 +160,9 @@ describe("PoolRegistry: Tests", function () { vTokenReceiver: owner.address, supplyCap: INITIAL_SUPPLY, borrowCap: INITIAL_SUPPLY, + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }); vDAI = await makeVToken({ @@ -179,6 +185,9 @@ describe("PoolRegistry: Tests", function () { vTokenReceiver: owner.address, supplyCap: INITIAL_SUPPLY, borrowCap: INITIAL_SUPPLY, + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }); vMockToken = await makeVToken({ @@ -525,5 +534,35 @@ describe("PoolRegistry: Tests", function () { ), ).to.be.revertedWithCustomError(poolRegistry, "ZeroAddressNotAllowed"); }); + + it("Revert on addMarket by non owner user", async () => { + const [, user, proxyAdmin] = await ethers.getSigners(); + + await expect( + poolRegistry.connect(user).addMarket({ + comptroller: comptroller2Proxy.address, + asset: mockWBTC.address, + decimals: 8, + name: "Compound WBTC", + symbol: "vWBTC", + rateModel: 0, + baseRatePerYear: 0, + multiplierPerYear: "40000000000000000", + jumpMultiplierPerYear: 0, + kink_: 0, + collateralFactor: convertToUnit(0.7, 18), + liquidationThreshold: convertToUnit(0.7, 18), + accessControlManager: fakeAccessControlManager.address, + vTokenProxyAdmin: proxyAdmin.address, + beaconAddress: vTokenBeacon.address, + initialSupply: INITIAL_SUPPLY, + supplyCap: INITIAL_SUPPLY, + borrowCap: INITIAL_SUPPLY, + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), + }), + ).to.be.rejectedWith("Ownable: caller is not the owner"); + }); }); }); diff --git a/tests/hardhat/StableRateModel.ts b/tests/hardhat/StableRateModel.ts new file mode 100644 index 000000000..8faea6f86 --- /dev/null +++ b/tests/hardhat/StableRateModel.ts @@ -0,0 +1,98 @@ +import { FakeContract, MockContract, MockContractFactory, smock } from "@defi-wonderland/smock"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { convertToUnit } from "../../helpers/utils"; +import { InterestRateModel, StableRateModel, StableRateModel__factory, VTokenHarness } from "../../typechain"; +import { vTokenTestFixture } from "./util/TokenTestHelpers"; + +let stableRateModelFactory: MockContractFactory; +let stableRateModel: MockContract; +let vToken: MockContract; +let interestRateModel: FakeContract; + +const fixture = async (): Promise => { + const [owner] = await ethers.getSigners(); + stableRateModelFactory = await smock.mock("StableRateModel"); + stableRateModel = await stableRateModelFactory.deploy( + convertToUnit(1, 10), + convertToUnit(1, 12), + convertToUnit(4, 17), + owner.address, + ); + await stableRateModel.deployed(); + + const contracts = await loadFixture(vTokenTestFixture); + ({ vToken, interestRateModel } = contracts); +}; + +describe("StableRateModel: Tests", function () { + /** + * Deploying required contracts along with the poolRegistry. + */ + + before(async function () { + await loadFixture(fixture); + }); + + it("Update the stable rate model", async function () { + let baseRate = await stableRateModel.baseRatePerBlock(); + let premiumRate = await stableRateModel.stableRatePremium(); + let optimalRate = await stableRateModel.optimalStableLoanRatio(); + + expect(baseRate).equals(4756); + expect(premiumRate).equals(convertToUnit(1, 12)); + expect(optimalRate).equals(convertToUnit(4, 17)); + + await stableRateModel.updateStableRateModel(convertToUnit(1, 12), convertToUnit(1, 12), convertToUnit(4, 17)); + + baseRate = await stableRateModel.baseRatePerBlock(); + premiumRate = await stableRateModel.stableRatePremium(); + optimalRate = await stableRateModel.optimalStableLoanRatio(); + + expect(baseRate).equals(475646); + expect(premiumRate).equals(convertToUnit(1, 12)); + expect(optimalRate).equals(convertToUnit(4, 17)); + }); + + it("Return 0 as stableLoanRatio for borrows equal to zero", async function () { + const loanRatio = await stableRateModel.stableLoanRatio(0, 0); + expect(loanRatio).equals(0); + }); + + it("Stable loan Rate", async function () { + const loanRatio = await stableRateModel.stableLoanRatio(convertToUnit(8, 20), convertToUnit(80, 20)); + expect(loanRatio).equals(convertToUnit(10, 16)); + }); + + it("Calculate stable borrow rate when stable loan ratio below optimal ratio", async function () { + const rate = await stableRateModel.getBorrowRate(convertToUnit(1, 20), convertToUnit(4, 20), convertToUnit(5, 12)); + expect(rate).to.be.closeTo(Number(convertToUnit(5, 12)), Number(convertToUnit(1, 10))); + }); + + it("Calculate stable borrow rate when stable loan ratio above optimal ratio", async function () { + const rate = await stableRateModel.getBorrowRate( + convertToUnit(2.5, 20), + convertToUnit(4, 20), + convertToUnit(5, 12), + ); + expect(rate).to.be.closeTo(Number(convertToUnit(52, 11)), Number(convertToUnit(2, 11))); + }); + + it("Return 0 as TotalBorrows for supply equal to zero", async function () { + const sr = await vToken.supplyRatePerBlock(); + expect(sr).equals(0); + }); + + it("Calculate Supply rate of the market", async function () { + interestRateModel.utilizationRate.returns(convertToUnit(5, 17)); + interestRateModel.getBorrowRate.returns(convertToUnit(3, 17)); + await vToken.harnessSetTotalBorrows(convertToUnit(2, 20)); + await vToken.harnessSetReserveFactorFresh(convertToUnit(1, 17)); + await vToken.harnessSetAvgStableBorrowRate(convertToUnit(4, 17)); + await vToken.harnessStableBorrows(convertToUnit(2, 18)); + + expect((await vToken.supplyRatePerBlock()).toString()).to.equal("135450000000000000"); + }); +}); diff --git a/tests/hardhat/Tokens/accrueInterestTest.ts b/tests/hardhat/Tokens/accrueInterestTest.ts index 1e1e7023c..fa28950e2 100644 --- a/tests/hardhat/Tokens/accrueInterestTest.ts +++ b/tests/hardhat/Tokens/accrueInterestTest.ts @@ -5,8 +5,9 @@ import chai from "chai"; import { BigNumber, BigNumberish, constants } from "ethers"; import { parseUnits } from "ethers/lib/utils"; -import { InterestRateModel, VTokenHarness } from "../../../typechain"; +import { InterestRateModel, StableRateModel, VTokenHarness } from "../../../typechain"; import { vTokenTestFixture } from "../util/TokenTestHelpers"; +import { convertToUnit } from "../../../helpers/utils"; const { expect } = chai; chai.use(smock.matchers); @@ -23,40 +24,52 @@ async function pretendBlock( await vToken.harnessSetAccrualBlockNumber(accrualBlock); await vToken.harnessSetBlockNumber(BigNumber.from(accrualBlock).add(deltaBlocks)); await vToken.harnessSetBorrowIndex(borrowIndex); + await vToken.harnessSetStableBorrowIndex(borrowIndex); } async function preAccrue({ vToken, interestRateModel, + stableInterestRateModel, }: { vToken: VTokenHarness; interestRateModel: FakeContract; + stableInterestRateModel: FakeContract; }) { interestRateModel.getBorrowRate.reset(); interestRateModel.getBorrowRate.returns(borrowRate); - interestRateModel.getSupplyRate.reset(); + stableInterestRateModel.getBorrowRate.reset(); + stableInterestRateModel.getBorrowRate.returns(borrowRate); await vToken.harnessExchangeRateDetails(0, 0, 0); } describe("VToken", () => { let vToken: VTokenHarness; let interestRateModel: FakeContract; + let stableInterestRateModel: FakeContract; beforeEach(async () => { const contracts = await loadFixture(vTokenTestFixture); - ({ vToken, interestRateModel } = contracts); + ({ vToken, interestRateModel, stableInterestRateModel } = contracts); }); beforeEach(async () => { - await preAccrue({ vToken, interestRateModel }); + await preAccrue({ vToken, interestRateModel, stableInterestRateModel }); }); describe("accrueInterest", () => { it("reverts if the interest rate is absurdly high", async () => { await pretendBlock(vToken, blockNumber, 1); - expect(await vToken.getBorrowRateMaxMantissa()).to.equal(parseUnits("0.000005", 18)); // 0.0005% per block - interestRateModel.getBorrowRate.returns(parseUnits("0.00001", 18)); // 0.0010% per block - await expect(vToken.accrueInterest()).to.be.revertedWith("borrow rate is absurdly high"); + expect(await vToken.getBorrowRateMaxMantissa()).to.equal(convertToUnit("0.000005", 18)); // 0.0005% per block + interestRateModel.getBorrowRate.returns(convertToUnit("0.00001", 18)); // 0.0010% per block + await expect(vToken.accrueInterest()).to.be.revertedWith("vToken: borrow rate is absurdly high"); + }); + + it("reverts if the stable interest rate is absurdly high", async () => { + await pretendBlock(vToken, blockNumber, 1); + expect(await vToken.getStableBorrowRateMaxMantissa()).to.equal(convertToUnit("0.000005", 18)); // 0.0005% per block + stableInterestRateModel.getBorrowRate.returns(convertToUnit("0.00001", 18)); // 0.0010% per block + await expect(vToken.accrueInterest()).to.be.revertedWith("vToken: stable borrow rate is absurdly high"); }); it("fails if new borrow rate calculation fails", async () => { @@ -65,6 +78,12 @@ describe("VToken", () => { await expect(vToken.accrueInterest()).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); }); + it("fails if new borrow rate calculation fails", async () => { + await pretendBlock(vToken, blockNumber, 1); + stableInterestRateModel.getBorrowRate.reverts("Oups"); + await expect(vToken.accrueInterest()).to.be.reverted; //With("INTEREST_RATE_MODEL_ERROR"); + }); + it("fails if simple interest factor calculation fails", async () => { await pretendBlock(vToken, blockNumber, parseUnits("5", 70)); await expect(vToken.accrueInterest()).to.be.revertedWithPanic(PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW); @@ -81,6 +100,12 @@ describe("VToken", () => { await expect(vToken.accrueInterest()).to.be.revertedWithPanic(PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW); }); + it("fails if new borrow interest index calculation fails", async () => { + await pretendBlock(vToken); + await vToken.harnessSetStableBorrowIndex(constants.MaxUint256); + await expect(vToken.accrueInterest()).to.be.revertedWithPanic(PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW); + }); + it("fails if interest accumulated calculation fails", async () => { await vToken.harnessExchangeRateDetails(0, constants.MaxUint256, 0); await pretendBlock(vToken); diff --git a/tests/hardhat/Tokens/borrowAndRepayTest.ts b/tests/hardhat/Tokens/borrowAndRepayTest.ts index 36b5bca44..2d2b33a5d 100644 --- a/tests/hardhat/Tokens/borrowAndRepayTest.ts +++ b/tests/hardhat/Tokens/borrowAndRepayTest.ts @@ -8,8 +8,14 @@ import { ethers } from "hardhat"; import { SignerWithAddress } from "hardhat-deploy-ethers/signers"; import { convertToUnit } from "../../../helpers/utils"; -import { Comptroller, ERC20Harness, InterestRateModel, VTokenHarness } from "../../../typechain"; -import { VTokenTestFixture, preApprove, pretendBorrow, vTokenTestFixture } from "../util/TokenTestHelpers"; +import { Comptroller, ERC20Harness, InterestRateModel, StableRateModel, VTokenHarness } from "../../../typechain"; +import { + VTokenTestFixture, + preApprove, + pretendBorrow, + pretendStableBorrow, + vTokenTestFixture, +} from "../util/TokenTestHelpers"; const { expect } = chai; chai.use(smock.matchers); @@ -17,12 +23,17 @@ chai.use(smock.matchers); const repayAmount = parseUnits("100", 18).toBigInt(); const borrowAmount = parseUnits("1000", 18).toBigInt(); -async function preBorrow(contracts: VTokenTestFixture, borrower: SignerWithAddress, borrowAmount: BigNumberish) { - const { comptroller, interestRateModel, underlying, vToken } = contracts; +async function preBorrow( + contracts: VTokenTestFixture, + borrower: Signer, + borrowAmount: BigNumberish, + stableRateMantissa: BigNumberish = 0, +) { + const { comptroller, interestRateModel, underlying, vToken, stableInterestRateModel } = contracts; comptroller.preBorrowHook.reset(); interestRateModel.getBorrowRate.reset(); - interestRateModel.getSupplyRate.reset(); + stableInterestRateModel.getBorrowRate.returns(stableRateMantissa); await underlying.harnessSetBalance(vToken.address, borrowAmount); await vToken.harnessSetFailTransferToAddress(borrower.address, false); @@ -34,28 +45,44 @@ async function borrowFresh(vToken: VTokenHarness, borrower: SignerWithAddress, b return vToken.harnessBorrowFresh(borrower.address, borrowAmount); } -async function borrow(vToken: VTokenHarness, borrower: SignerWithAddress, borrowAmount: BigNumberish) { +async function borrowStableFresh(vToken: MockContract, borrower: Signer, borrowAmount: BigNumberish) { + return vToken.harnessBorrowStableFresh(await borrower.getAddress(), borrowAmount); +} + +async function borrow(vToken: MockContract, borrower: Signer, borrowAmount: BigNumberish) { // make sure to have a block delta so we accrue interest await vToken.harnessFastForward(1); return vToken.connect(borrower).borrow(borrowAmount); } +async function borrowStable(vToken: MockContract, borrower: Signer, borrowAmount: BigNumberish) { + // make sure to have a block delta so we accrue interest + await vToken.harnessFastForward(1); + // interestRateModel --> 1 for stabel rate model and 2 for varaible rate model + return vToken.connect(borrower).borrowStable(borrowAmount); +} + async function preRepay( contracts: VTokenTestFixture, - benefactor: SignerWithAddress, - borrower: SignerWithAddress, + benefactor: Signer, + borrower: Signer, repayAmount: BigNumberish, + interestRateMode: number, + stableRateMantissa: BigNumberish = 0, ) { const { comptroller, interestRateModel, underlying, vToken } = contracts; // setup either benefactor OR borrower for success in repaying comptroller.preRepayHook.reset(); interestRateModel.getBorrowRate.reset(); - interestRateModel.getSupplyRate.reset(); - await underlying.harnessSetFailTransferFromAddress(benefactor.address, false); - await underlying.harnessSetFailTransferFromAddress(borrower.address, false); - await pretendBorrow(vToken, borrower, parseUnits("1", 18), parseUnits("1", 18), repayAmount); + await underlying.harnessSetFailTransferFromAddress(await benefactor.getAddress(), false); + await underlying.harnessSetFailTransferFromAddress(await borrower.getAddress(), false); + if (interestRateMode == 1) { + await pretendStableBorrow(vToken, borrower, 1, 1, repayAmount, stableRateMantissa); + } else { + await pretendBorrow(vToken, borrower, 1, 1, repayAmount); + } await preApprove(underlying, vToken, benefactor, repayAmount, { faucet: true }); await preApprove(underlying, vToken, borrower, repayAmount, { faucet: true }); } @@ -70,12 +97,29 @@ async function repayBorrowFresh( return vToken.connect(payer).harnessRepayBorrowFresh(payer.address, borrowerAddress, repayAmount); } -async function repayBorrow(vToken: VTokenHarness, borrower: SignerWithAddress, repayAmount: BigNumberish) { +async function repayBorrowStableFresh( + vToken: MockContract, + payer: Signer, + borrower: Signer, + repayAmount: BigNumberish, +) { + const payerAddress = await payer.getAddress(); + const borrowerAddress = await borrower.getAddress(); + return vToken.connect(payer).harnessRepayBorrowStableFresh(payerAddress, borrowerAddress, repayAmount); +} + +async function repayBorrow(vToken: MockContract, borrower: Signer, repayAmount: BigNumberish) { // make sure to have a block delta so we accrue interest await vToken.harnessFastForward(1); return vToken.connect(borrower).repayBorrow(repayAmount); } +async function repayBorrowStable(vToken: MockContract, borrower: Signer, repayAmount: BigNumberish) { + // make sure to have a block delta so we accrue interest + await vToken.harnessFastForward(1); + return vToken.connect(borrower).repayBorrowStable(repayAmount); +} + async function repayBorrowBehalf( vToken: VTokenHarness, payer: SignerWithAddress, @@ -87,20 +131,33 @@ async function repayBorrowBehalf( return vToken.connect(payer).repayBorrowBehalf(borrower.address, repayAmount); } +async function repayBorrowStableBehalf( + vToken: MockContract, + payer: Signer, + borrower: Signer, + repayAmount: BigNumberish, +) { + // make sure to have a block delta so we accrue interest + await vToken.harnessFastForward(1); + return vToken.connect(payer).repayBorrowStableBehalf(await borrower.getAddress(), repayAmount); +} + describe("VToken", function () { let contracts: VTokenTestFixture; let comptroller: FakeContract; let vToken: VTokenHarness; let underlying: MockContract; let interestRateModel: FakeContract; - let _root: SignerWithAddress; - let borrower: SignerWithAddress; - let benefactor: SignerWithAddress; + let stableInterestRateModel: FakeContract; + let _root: Signer; + let borrower: Signer; + let benefactor: Signer; + let borrowerAddress: string; beforeEach(async () => { [_root, borrower, benefactor] = await ethers.getSigners(); contracts = await loadFixture(vTokenTestFixture); - ({ comptroller, vToken, underlying, interestRateModel } = contracts); + ({ comptroller, vToken, underlying, interestRateModel, stableInterestRateModel } = contracts); }); describe("borrowFresh", () => { @@ -191,6 +248,51 @@ describe("VToken", function () { expect(borrowSnap.interestIndex).to.equal(convertToUnit("3", 18)); expect(await vToken.totalBorrows()).to.equal(beforeProtocolBorrows.add(borrowAmount)); }); + + it("borrow fresh with stable rate", async () => { + const beforeProtocolCash = await underlying.balanceOf(vToken.address); + const beforeProtocolBorrows = await vToken.totalBorrows(); + const beforeStableBorrows = await vToken.stableBorrows(); + const beforeAccountCash = await underlying.balanceOf(borrowerAddress); + await stableInterestRateModel.getBorrowRate.returns(convertToUnit(25, 12)); + + const result = await borrowStableFresh(vToken, borrower, borrowAmount); + + expect(await underlying.balanceOf(borrowerAddress)).to.equal(beforeAccountCash.add(borrowAmount)); + expect(await underlying.balanceOf(vToken.address)).to.equal(beforeProtocolCash.sub(borrowAmount)); + expect(await vToken.totalBorrows()).to.equal(beforeProtocolBorrows.add(borrowAmount)); + expect(await vToken.stableBorrows()).to.equal(beforeStableBorrows.add(borrowAmount)); + + await expect(result).to.emit(underlying, "Transfer").withArgs(vToken.address, borrowerAddress, borrowAmount); + + await expect(result) + .to.emit(vToken, "Borrow") + .withArgs(borrowerAddress, borrowAmount, borrowAmount, beforeProtocolBorrows.add(borrowAmount).toString()); + }); + + it("stores new borrow principal and interest index for stable rate borrowing", async () => { + let beforeProtocolBorrows = await vToken.totalBorrows(); + let borrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(borrowSnap.principal).to.equal(0); + expect(borrowSnap.interestIndex).to.equal(0); + expect(await vToken.totalBorrows()).to.equal(0); + + await pretendStableBorrow(vToken, borrower, 1, 1, borrowAmount, convertToUnit(5, 8)); + beforeProtocolBorrows = await vToken.totalBorrows(); + borrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + + expect(borrowSnap.principal).to.equal(convertToUnit("1000", 18)); + expect(borrowSnap.interestIndex).to.equal(convertToUnit("1", 18)); + expect(beforeProtocolBorrows).to.equal(convertToUnit("1000", 18)); + + await borrowStableFresh(vToken, borrower, borrowAmount); + beforeProtocolBorrows = await vToken.totalBorrows(); + borrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + + expect(borrowSnap.principal).to.equal(convertToUnit("2000", 18)); + expect(borrowSnap.interestIndex).to.equal(convertToUnit("1", 18)); + expect(beforeProtocolBorrows).to.equal(convertToUnit("2000", 18)); + }); }); describe("borrow", () => { @@ -217,16 +319,24 @@ describe("VToken", function () { // ).toSucceed(); expect(await underlying.balanceOf(borrower.address)).to.equal(beforeAccountCash.add(borrowAmount)); }); + + it("returns success from borrowFresh and transfers the correct amount for stable rate borrowing", async () => { + const beforeAccountCash = await underlying.balanceOf(borrowerAddress); + await vToken.harnessFastForward(5); + await borrowStable(vToken, borrower, borrowAmount); + expect(await underlying.balanceOf(borrowerAddress)).to.equal(beforeAccountCash.add(borrowAmount)); + }); }); - describe("repayBorrowFresh", () => { + describe("repayBorrowFresh for variable rate borrowing", () => { [true, false].forEach(benefactorIsPayer => { let payer: SignerWithAddress; const label = benefactorIsPayer ? "benefactor paying" : "borrower paying"; describe(label, () => { beforeEach(async () => { payer = benefactorIsPayer ? benefactor : borrower; - await preRepay(contracts, payer, borrower, repayAmount); + payerAddress = await payer.getAddress(); + await preRepay(contracts, payer, borrower, repayAmount, 2); }); it("fails if repay is not allowed", async () => { @@ -295,9 +405,94 @@ describe("VToken", function () { }); }); + describe("repayBorrowFresh for stable rate borrowing", () => { + [true, false].forEach(benefactorIsPayer => { + let payer: Signer; + let payerAddress: string; + const label = benefactorIsPayer ? "benefactor paying" : "borrower paying"; + describe(label, () => { + beforeEach(async () => { + payer = benefactorIsPayer ? benefactor : borrower; + payerAddress = await payer.getAddress(); + await preRepay(contracts, payer, borrower, repayAmount, 1); + }); + + it("fails if repay is not allowed", async () => { + comptroller.preRepayHook.reverts(); + await expect(repayBorrowStableFresh(vToken, payer, borrower, repayAmount)).to.be.reverted; + }); + + it("fails if block number ≠ current block number", async () => { + await vToken.harnessFastForward(5); + await expect(repayBorrowStableFresh(vToken, payer, borrower, repayAmount)).to.be.revertedWithCustomError( + vToken, + "RepayBorrowFreshnessCheck", + ); + }); + + it("fails if insufficient approval", async () => { + await preApprove(underlying, vToken, payer, 1); + await expect(repayBorrowStableFresh(vToken, payer, borrower, repayAmount)).to.be.revertedWith( + "Insufficient allowance", + ); + }); + + it("fails if insufficient balance", async () => { + await underlying.harnessSetBalance(await payer.getAddress(), 1); + await expect(repayBorrowStableFresh(vToken, payer, borrower, repayAmount)).to.be.revertedWith( + "Insufficient balance", + ); + }); + + it("returns an error if calculating account new account borrow balance fails", async () => { + await pretendBorrow(vToken, borrower, 1, 1, 1); + await expect(repayBorrowStableFresh(vToken, payer, borrower, repayAmount)).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW, + ); + }); + + it("returns an error if calculation of new total borrow balance fails", async () => { + await vToken.harnessSetTotalBorrows(1); + await expect(repayBorrowStableFresh(vToken, payer, borrower, repayAmount)).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW, + ); + }); + + it("reverts if doTransferIn fails", async () => { + await underlying.harnessSetFailTransferFromAddress(payerAddress, true); + await expect(repayBorrowStableFresh(vToken, payer, borrower, repayAmount)).to.be.revertedWith( + "SafeERC20: ERC20 operation did not succeed", + ); + }); + + it("transfers the underlying cash, and emits Transfer, RepayBorrow events", async () => { + const beforeProtocolCash = await underlying.balanceOf(vToken.address); + const result = await repayBorrowStableFresh(vToken, payer, borrower, repayAmount); + expect(await underlying.balanceOf(vToken.address)).to.equal(beforeProtocolCash.add(repayAmount)); + await expect(result).to.emit(underlying, "Transfer").withArgs(payerAddress, vToken.address, repayAmount); + await expect(result) + .to.emit(vToken, "RepayBorrow") + .withArgs(payerAddress, borrowerAddress, repayAmount, "0", "0"); + }); + + it("stores new borrow principal and interest index", async () => { + const beforeProtocolBorrows = await vToken.totalBorrows(); + const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + //expect( + await repayBorrowStableFresh(vToken, payer, borrower, repayAmount); + //).toSucceed(); + const afterAccountBorrows = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(afterAccountBorrows.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); + expect(afterAccountBorrows.interestIndex).to.equal(convertToUnit("1", 18)); + expect(await vToken.totalBorrows()).to.equal(beforeProtocolBorrows.sub(repayAmount)); + }); + }); + }); + }); + describe("repayBorrow", () => { beforeEach(async () => { - await preRepay(contracts, borrower, borrower, repayAmount); + await preRepay(contracts, borrower, borrower, repayAmount, 2); }); it("emits a repay borrow failure if interest accrual fails", async () => { @@ -336,12 +531,159 @@ describe("VToken", function () { }); }); + describe("repayBorrow for stable rate", () => { + beforeEach(async () => { + await preRepay(contracts, borrower, borrower, repayAmount, 1); + }); + + it("emits a repay borrow failure if interest accrual fails", async () => { + interestRateModel.getBorrowRate.reverts("Oups"); + await expect(repayBorrowStable(vToken, borrower, repayAmount)).to.be.reverted; //With("INTEREST_RATE_MODEL_ERROR"); + }); + + it("returns error from repayBorrowFresh without emitting any extra logs", async () => { + await underlying.harnessSetBalance(borrowerAddress, 1); + await expect(repayBorrowStable(vToken, borrower, repayAmount)).to.be.revertedWith("Insufficient balance"); + }); + + it("returns success from repayBorrowFresh and repays the right amount", async () => { + await vToken.harnessFastForward(5); + const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + //expect( + await repayBorrowStable(vToken, borrower, repayAmount); + //).toSucceed(); + const afterAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(afterAccountBorrowSnap.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); + }); + + it("repays the full amount owed if payer has enough", async () => { + await vToken.harnessFastForward(5); + //expect( + await repayBorrowStable(vToken, borrower, constants.MaxUint256); + //).toSucceed(); + const afterAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(afterAccountBorrowSnap.principal).to.equal(0); + }); + + it("fails gracefully if payer does not have enough", async () => { + await underlying.harnessSetBalance(borrowerAddress, 3); + await vToken.harnessFastForward(5); + await expect(repayBorrowStable(vToken, borrower, constants.MaxUint256)).to.be.revertedWith( + "Insufficient balance", + ); + }); + }); + + describe("Stable interest rate accural", () => { + it("Stable rate accrual after few blocks", async () => { + await preRepay(contracts, borrower, borrower, repayAmount, 1, convertToUnit(1, 8)); + await vToken.harnessFastForward(18); + + let borrowSnap = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + expect(borrowSnap.principal).to.equal(convertToUnit(1, 20)); + expect(borrowSnap.lastBlockAccrued).to.equal(convertToUnit(2, 7)); + + await vToken.harnessUpdateUserStableBorrowBalance(borrower.getAddress()); + + borrowSnap = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + expect(borrowSnap.principal).to.equal(convertToUnit(10000000018, 10)); + expect(borrowSnap.lastBlockAccrued).to.equal(20000018); + + const borrows = await vToken.stableBorrows(); + expect(borrows).to.equal(convertToUnit(10000000018, 10)); + }); + + it("Stable rate accrual for two accounts after few blocks", async () => { + const [, , borrower2] = await ethers.getSigners(); + + await preBorrow(contracts, borrower, borrowAmount, convertToUnit(1, 6)); + await borrowStable(vToken, borrower, borrowAmount); + await vToken.harnessFastForward(10); + let borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + expect(borrowSnap1.principal).to.equal(convertToUnit(1, 21)); + expect(borrowSnap1.lastBlockAccrued).to.equal(100001); + + await preBorrow(contracts, borrower2, borrowAmount, convertToUnit(1, 8)); + await vToken.harnessSetTotalBorrows(borrowAmount); + await borrowStable(vToken, borrower2, borrowAmount); + await vToken.harnessFastForward(10); + + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + let borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + expect(borrowSnap1.principal).to.equal(convertToUnit(1, 21)); + expect(borrowSnap2.principal).to.equal(convertToUnit(1, 21)); + expect(borrowSnap1.lastBlockAccrued).to.equal(100001); + expect(borrowSnap2.lastBlockAccrued).to.equal(100012); + + await vToken.harnessFastForward(10); + await vToken.harnessUpdateUserStableBorrowBalance(borrower.getAddress()); + + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + expect(borrowSnap1.principal).to.equal("1000000000030999999999"); + expect(borrowSnap1.lastBlockAccrued).to.equal(100032); + expect(borrowSnap2.lastBlockAccrued).to.equal(100012); + + await vToken.harnessFastForward(10); + await vToken.harnessUpdateUserStableBorrowBalance(borrower2.getAddress()); + + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + expect(borrowSnap1.principal).to.equal("1000000000030999999999"); + expect(borrowSnap2.principal).to.equal("1000000002999999999697"); + expect(borrowSnap1.lastBlockAccrued).to.equal(100032); + expect(borrowSnap2.lastBlockAccrued).to.equal(100042); + + await vToken.harnessFastForward(10); + await vToken.harnessUpdateUserStableBorrowBalance(borrower.getAddress()); + await vToken.harnessUpdateUserStableBorrowBalance(borrower2.getAddress()); + + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + expect(borrowSnap1.principal).to.equal("1000000000050999999998"); + expect(borrowSnap2.principal).to.equal("1000000004000000002595"); + expect(borrowSnap1.lastBlockAccrued).to.equal(100052); + expect(borrowSnap2.lastBlockAccrued).to.equal(100052); + }); + + it("Average stable borrow rate, stable borrows after borrow and repay", async () => { + const [, , borrower2] = await ethers.getSigners(); + + await preBorrow(contracts, borrower, borrowAmount, convertToUnit(1, 6)); + await borrowStable(vToken, borrower, borrowAmount); + let averageBorrowRate = await vToken.averageStableBorrowRate(); + let stabelBorrows = await vToken.stableBorrows(); + expect(averageBorrowRate).to.equal(1000000); + expect(stabelBorrows).to.equal(borrowAmount); + + await preBorrow(contracts, borrower2, borrowAmount, convertToUnit(1, 8)); + await vToken.harnessSetTotalBorrows(borrowAmount); + await borrowStable(vToken, borrower2, borrowAmount); + await vToken.harnessFastForward(10); + + stabelBorrows = await vToken.stableBorrows(); + expect(stabelBorrows).to.equal(convertToUnit(2000, 18)); + averageBorrowRate = await vToken.averageStableBorrowRate(); + expect(averageBorrowRate).to.equal(50500000); + + await underlying.harnessSetFailTransferFromAddress(await borrower.getAddress(), false); + await preApprove(underlying, vToken, borrower, borrowAmount, { faucet: true }); + await vToken.connect(borrower).repayBorrowStable(borrowAmount); + + averageBorrowRate = await vToken.averageStableBorrowRate(); + expect(averageBorrowRate).to.equal(99999999); + stabelBorrows = await vToken.stableBorrows(); + expect(stabelBorrows).to.equal("1000000000011000000000"); + }); + }); + describe("repayBorrowBehalf", () => { let payer: SignerWithAddress; beforeEach(async () => { payer = benefactor; - await preRepay(contracts, payer, borrower, repayAmount); + payerAddress = await payer.getAddress(); + await preRepay(contracts, payer, borrower, repayAmount, 2); }); it("emits a repay borrow failure if interest accrual fails", async () => { @@ -364,4 +706,37 @@ describe("VToken", function () { expect(afterAccountBorrowSnap.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); }); }); + + describe("repayBorrowBehalf for stable rate", () => { + let payer: Signer; + let payerAddress: string; + + beforeEach(async () => { + payer = benefactor; + payerAddress = await payer.getAddress(); + await preRepay(contracts, payer, borrower, repayAmount, 1); + }); + + it("emits a repay borrow failure if interest accrual fails", async () => { + interestRateModel.getBorrowRate.reverts("Oups"); + await expect(repayBorrowStableBehalf(vToken, payer, borrower, repayAmount)).to.be.reverted; //With("INTEREST_RATE_MODEL_ERROR"); + }); + + it("returns error from repayBorrowFresh without emitting any extra logs", async () => { + await underlying.harnessSetBalance(payerAddress, 1); + await expect(repayBorrowStableBehalf(vToken, payer, borrower, repayAmount)).to.be.revertedWith( + "Insufficient balance", + ); + }); + + it("returns success from repayBorrowFresh and repays the right amount", async () => { + await vToken.harnessFastForward(5); + const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + //expect( + await repayBorrowStableBehalf(vToken, payer, borrower, repayAmount); + //).toSucceed(); + const afterAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(afterAccountBorrowSnap.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); + }); + }); }); diff --git a/tests/hardhat/Tokens/liquidateTest.ts b/tests/hardhat/Tokens/liquidateTest.ts index 4b0e9b7ff..2bc34e61c 100644 --- a/tests/hardhat/Tokens/liquidateTest.ts +++ b/tests/hardhat/Tokens/liquidateTest.ts @@ -118,8 +118,6 @@ function configure({ for (const model of [borrowedRateModel, collateralRateModel]) { model.getBorrowRate.reset(); model.getBorrowRate.returns(0); - model.getSupplyRate.reset(); - model.getSupplyRate.returns(0); } } diff --git a/tests/hardhat/Tokens/mintAndRedeemTest.ts b/tests/hardhat/Tokens/mintAndRedeemTest.ts index 19b473378..b429ad469 100644 --- a/tests/hardhat/Tokens/mintAndRedeemTest.ts +++ b/tests/hardhat/Tokens/mintAndRedeemTest.ts @@ -37,7 +37,6 @@ async function preMint( comptroller.preMintHook.reset(); interestRateModel.getBorrowRate.reset(); - interestRateModel.getSupplyRate.reset(); const minterAddress = minter.address; await underlying.harnessSetFailTransferFromAddress(minterAddress, false); @@ -74,7 +73,6 @@ async function preRedeem( comptroller.preRedeemHook.reset(); interestRateModel.getBorrowRate.reset(); - interestRateModel.getSupplyRate.reset(); await underlying.harnessSetBalance(vToken.address, redeemAmount); await underlying.harnessSetBalance(redeemer.address, 0); await underlying.harnessSetFailTransferToAddress(redeemer.address, false); diff --git a/tests/hardhat/Tokens/swapBorrowRateMode.ts b/tests/hardhat/Tokens/swapBorrowRateMode.ts new file mode 100644 index 000000000..a4c0eded0 --- /dev/null +++ b/tests/hardhat/Tokens/swapBorrowRateMode.ts @@ -0,0 +1,103 @@ +import { MockContract, smock } from "@defi-wonderland/smock"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import chai from "chai"; +import { BigNumberish, Signer } from "ethers"; +import { ethers } from "hardhat"; + +import { convertToUnit } from "../../../helpers/utils"; +import { VTokenHarness } from "../../../typechain"; +import { VTokenTestFixture, vTokenTestFixture } from "../util/TokenTestHelpers"; + +const { expect } = chai; +chai.use(smock.matchers); + +const borrowAmount = convertToUnit("1000", 18); + +async function preBorrow(contracts: VTokenTestFixture, borrower: Signer, borrowAmount: BigNumberish) { + const { comptroller, interestRateModel, underlying, vToken, stableInterestRateModel } = contracts; + comptroller.preBorrowHook.reset(); + + interestRateModel.getBorrowRate.reset(); + stableInterestRateModel.getBorrowRate.reset(); + + const borrowerAddress = await borrower.getAddress(); + await underlying.harnessSetBalance(vToken.address, borrowAmount); + await vToken.harnessSetFailTransferToAddress(borrowerAddress, false); + await vToken.harnessSetAccountBorrows(borrowerAddress, 0, 0); + await vToken.harnessSetTotalBorrows(0); +} + +async function borrow(vToken: MockContract, borrower: Signer, borrowAmount: BigNumberish) { + // make sure to have a block delta so we accrue interest + await vToken.harnessFastForward(1); + return vToken.connect(borrower).borrow(borrowAmount); +} + +async function borrowStable(vToken: MockContract, borrower: Signer, borrowAmount: BigNumberish) { + // make sure to have a block delta so we accrue interest + await vToken.harnessFastForward(1); + return vToken.connect(borrower).borrowStable(borrowAmount); +} + +describe("VToken", function () { + let contracts: VTokenTestFixture; + let vToken: MockContract; + let _root: Signer; + let borrower: Signer; + let borrowerAddress: string; + + beforeEach(async () => { + [_root, borrower] = await ethers.getSigners(); + borrowerAddress = await borrower.getAddress(); + contracts = await loadFixture(vTokenTestFixture); + ({ vToken } = contracts); + }); + + describe("swapBorrowRateMode: tests", () => { + it("fails if variable debt is 0", async () => { + await expect(vToken.swapBorrowRateMode(1)).to.be.revertedWith("vToken: swapBorrowRateMode variable debt is 0"); + }); + + it("fails if stable debt is 0", async () => { + await expect(vToken.swapBorrowRateMode(2)).to.be.revertedWith("vToken: swapBorrowRateMode stable debt is 0"); + }); + + it("Swapping borrow rate mode from variable to stable", async () => { + await preBorrow(contracts, borrower, borrowAmount); + await borrow(vToken, borrower, borrowAmount); + let variableBorrow, stableBorrow; + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(borrowAmount); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(0); + + await vToken.connect(borrower).swapBorrowRateMode(1); + + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(0); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(borrowAmount); + }); + + it("Swapping borrow rate mode from variable to stable", async () => { + await preBorrow(contracts, borrower, borrowAmount); + await borrowStable(vToken, borrower, borrowAmount); + let variableBorrow, stableBorrow; + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(0); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(borrowAmount); + + await vToken.connect(borrower).swapBorrowRateMode(2); + + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(borrowAmount); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(0); + }); + }); +}); diff --git a/tests/hardhat/UpgradedVToken.ts b/tests/hardhat/UpgradedVToken.ts index 613f8c7d2..c2873d6de 100644 --- a/tests/hardhat/UpgradedVToken.ts +++ b/tests/hardhat/UpgradedVToken.ts @@ -94,6 +94,9 @@ describe("UpgradedVToken: Tests", function () { vTokenReceiver: root.address, supplyCap: initialSupply, borrowCap: initialSupply, + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }); }); diff --git a/tests/hardhat/util/TokenTestHelpers.ts b/tests/hardhat/util/TokenTestHelpers.ts index 2f789f287..9b6554b54 100644 --- a/tests/hardhat/util/TokenTestHelpers.ts +++ b/tests/hardhat/util/TokenTestHelpers.ts @@ -11,12 +11,14 @@ import { ERC20Harness__factory, InterestRateModel, UpgradeableBeacon, + StableRateModel, VTokenHarness, VTokenHarness__factory, VToken__factory, } from "../../../typechain"; import { AddressOrContract, getAddress } from "./AddressOrContract"; import { DeployedContract } from "./types"; +import { convertToUnit } from "../../../helpers/utils"; chai.use(smock.matchers); @@ -41,6 +43,13 @@ const getNameAndSymbol = async (underlying: AddressOrContract): Promise<[string, const name = await underlying_.name(); const symbol = await underlying_.symbol(); return [name, symbol]; +} + +export type VTokenContracts = { + vToken: MockContract; + underlying: MockContract; + interestRateModel: FakeContract; + stableInterestRateModel: FakeContract; }; export const fakeComptroller = async (): Promise> => { @@ -135,6 +144,7 @@ export type VTokenTestFixture = { vToken: VTokenHarness; underlying: MockContract; interestRateModel: FakeContract; + stableInterestRateModel: FakeContract; }; export async function vTokenTestFixture(): Promise { @@ -156,7 +166,7 @@ export async function vTokenTestFixture(): Promise { { kind: "VTokenHarness" }, ); - return { accessControlManager, comptroller, vToken, interestRateModel, underlying }; + return { accessControlManager, comptroller, vToken, interestRateModel, underlying, stableInterestRateModel }; } type BalancesSnapshot = { @@ -248,3 +258,26 @@ export async function pretendBorrow( await vToken.harnessSetAccrualBlockNumber(blockNumber); await vToken.harnessSetBlockNumber(blockNumber); } + +export async function pretendStableBorrow( + vToken: MockContract, + borrower: Signer, + accountIndex: number, + marketIndex: number, + principalRaw: BigNumberish, + stableRateMantissa: BigNumberish, + blockNumber: number = 2e7, +) { + await vToken.harnessSetTotalBorrows(principalRaw); + await vToken.harnessSetStableBorrows(principalRaw); + await vToken.harnessSetAccountStableBorrows( + await borrower.getAddress(), + principalRaw, + convertToUnit(accountIndex, 18), + stableRateMantissa, + blockNumber, + ); + await vToken.harnessSetStableBorrowIndex(convertToUnit(marketIndex, 18)); + await vToken.harnessSetAccrualBlockNumber(blockNumber); + await vToken.harnessSetBlockNumber(blockNumber); +} From d24258c7c0d486debc512159c55b80cb5a7aea87 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Fri, 3 Feb 2023 17:06:31 +0530 Subject: [PATCH 02/23] Added rebalancing conditions for the stable rate. --- contracts/VToken.sol | 176 +++++++++++++++++++++++++++++++-- contracts/VTokenInterfaces.sol | 15 ++- 2 files changed, 181 insertions(+), 10 deletions(-) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 602d6c6fd..8e009a9a2 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -705,14 +705,75 @@ contract VToken is * @return rate The supply interest rate per block, scaled by 1e18 */ function supplyRatePerBlock() external view override returns (uint256) { + if (totalBorrows == 0) { + return 0; + } + uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 averageMarketBorrowRate = _averageMarketBorrowRate(); return - interestRateModel.getSupplyRate( - _getCashPrior(), - totalBorrows, - totalReserves, - reserveFactorMantissa, - badDebt - ); + (averageMarketBorrowRate * utilizationRate * (mantissaOne - reserveFactorMantissa)) / + (mantissaOne * mantissaOne); + } + + /// @notice Calculate the average market borrow rate with respect to variable and stable borrows + function _averageMarketBorrowRate() internal view returns (uint256) { + uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 variableBorrows = totalBorrows - stableBorrows; + return ((variableBorrows * variableBorrowRate) + (stableBorrows * averageStableBorrowRate)) / totalBorrows; + } + + /** + * @notice Returns the current total borrows plus accrued interest + * @return totalBorrows The total borrows with interest + */ + function totalBorrowsCurrent() external override nonReentrant returns (uint256) { + accrueInterest(); + return totalBorrows; + } + + /** + * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + * @param account The address whose balance should be calculated after updating borrowIndex + * @return borrowBalance The calculated balance + */ + function borrowBalanceCurrent(address account) external override nonReentrant returns (uint256) { + accrueInterest(); + (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); + return _borrowBalanceStored(account) + stableBorrowAmount; + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return borrowBalance The calculated balance + */ + function borrowBalanceStored(address account) public view override returns (uint256) { + (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); + return _borrowBalanceStored(account) + stableBorrowAmount; + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return borrowBalance the calculated balance + */ + function _borrowBalanceStored(address account) internal view returns (uint256) { + /* Get borrowBalance and borrowIndex */ + BorrowSnapshot storage borrowSnapshot = accountBorrows[account]; + + /* If borrowBalance = 0 then borrowIndex is likely also 0. + * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. + */ + if (borrowSnapshot.principal == 0) { + return 0; + } + + /* Calculate new borrow balance using the interest index: + * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex + */ + uint256 principalTimesIndex = borrowSnapshot.principal * borrowIndex; + + return principalTimesIndex / borrowSnapshot.interestIndex; } /** @@ -1263,7 +1324,7 @@ contract VToken is comptroller.preRepayHook(address(this), borrower); /* Verify market's block number equals current block number */ - if (accrualBlockNumber != _getBlockNumber()) { + if (accrualBlockNumber != _getBlockNumber()) { revert RepayBorrowFreshnessCheck(); } @@ -1571,6 +1632,105 @@ contract VToken is emit ReservesAdded(address(this), protocolSeizeAmount, totalReservesNew); } + /** + * @notice Rebalances the stable interest rate of a user to the current stable borrow rate. + * - Users can be rebalanced if the following conditions are satisfied: + * 1. Utilization rate is above rebalanceUtilizationRateThreshold. + * 2. Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. + * @param account The address of the account to be rebalanced + * @custom:events RebalancedStableBorrowRate - Emits after rebalancing the stable borrow rate for the user. + **/ + function rebalanceStableBorrowRate(address account) external { + accrueInterest(); + + validateRebalanceStableBorrowRate(); + _updateUserStableBorrowBalance(account); + + uint256 stableBorrowRate = stableRateModel.getBorrowRate( + _getCashPrior(), + stableBorrows, + totalBorrows, + totalReserves + ); + + accountStableBorrows[account].stableRateMantissa = stableBorrowRate; + + emit RebalancedStableBorrowRate(account, stableBorrowRate); + } + + /// Validate the conditions to rebalance the stable borrow rate. + function validateRebalanceStableBorrowRate() public view { + uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + + /// Utilization rate is above rebalanceUtilizationRateThreshold. + /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. + require(utilizationRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); + require( + _averageMarketBorrowRate() < (variableBorrowRate * rebalanceRateFractionThreshold), + "vToken: average borrow rate higher than variable rate threshold." + ); + } + + /*** Admin Functions ***/ + + /** + * @notice Sets a new comptroller for the market + * @dev Admin function to set a new comptroller + * @custom:events Emits NewComptroller event + * @custom:error SetComptrollerOwnerCheck is thrown when the call is not from owner + * @custom:access Only Governance + */ + function setComptroller(ComptrollerInterface newComptroller) public override { + // Check caller is admin + if (msg.sender != owner()) { + revert SetComptrollerOwnerCheck(); + } + + _setComptroller(newComptroller); + } + + function _setComptroller(ComptrollerInterface newComptroller) internal { + ComptrollerInterface oldComptroller = comptroller; + // Ensure invoke comptroller.isComptroller() returns true + require(newComptroller.isComptroller(), "marker method returned false"); + + // Set market's comptroller to newComptroller + comptroller = newComptroller; + + // Emit NewComptroller(oldComptroller, newComptroller) + emit NewComptroller(oldComptroller, newComptroller); + } + + /** + * @notice sets protocol share accumulated from liquidations + * @dev must be less than liquidation incentive - 1 + * @param newProtocolSeizeShareMantissa_ new protocol share mantissa + * @custom:events Emits NewProtocolSeizeShare event on success + * @custom:error SetProtocolSeizeShareUnauthorized is thrown when the call is not authorized by AccessControlManager + * @custom:error ProtocolSeizeShareTooBig is thrown when the new seize share is too high + * @custom:access Controlled by AccessControlManager + */ + function setProtocolSeizeShare(uint256 newProtocolSeizeShareMantissa_) external { + bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( + msg.sender, + "setProtocolSeizeShare(uint256)" + ); + // Check caller is allowed to call this function + if (!canCallFunction) { + revert SetProtocolSeizeShareUnauthorized(); + } + + uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).liquidationIncentiveMantissa(); + if (newProtocolSeizeShareMantissa_ + 1e18 > liquidationIncentive) { + revert ProtocolSeizeShareTooBig(); + } + + uint256 oldProtocolSeizeShareMantissa = protocolSeizeShareMantissa; + protocolSeizeShareMantissa = newProtocolSeizeShareMantissa_; + emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa_); + } + function _setComptroller(ComptrollerInterface newComptroller) internal { ComptrollerInterface oldComptroller = comptroller; // Ensure invoke comptroller.isComptroller() returns true diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 7da66c49f..65f5123fe 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -168,12 +168,18 @@ contract VTokenStorage { VARIABLE } - /** + /// @notice Utilization rate threshold where rebalancing condition get satisfied for stable rate borrowing. + uint256 internal rebalanceUtilizationRateThreshold; + + /// @notice Rate fraction for variable rate borrwing where rebalancing condition get satisfied for stable rate borrowing. + uint256 internal rebalanceRateFractionThreshold; + + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[45] private __gap; + uint256[43] private __gap; } /** @@ -333,6 +339,11 @@ abstract contract VTokenInterface is VTokenStorage { */ event UpdatedUserStableBorrowBalance(address borrower, uint256 updatedPrincipal); + /** + * @notice Event emitted on stable rate rebalacing + */ + event RebalancedStableBorrowRate(address account, uint256 stableRateMantissa); + /*** User Interface ***/ function mint(uint256 mintAmount) external virtual returns (uint256); From 72f2aafad82f3f4fc11fa7dcf6e7ec12f8f3249d Mon Sep 17 00:00:00 2001 From: defcon022 Date: Fri, 3 Feb 2023 19:26:01 +0530 Subject: [PATCH 03/23] Borrow at both rates. --- contracts/VToken.sol | 9 ++------- contracts/VTokenInterfaces.sol | 2 +- tests/hardhat/Tokens/borrowAndRepayTest.ts | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 8e009a9a2..09546423b 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1324,7 +1324,7 @@ contract VToken is comptroller.preRepayHook(address(this), borrower); /* Verify market's block number equals current block number */ - if (accrualBlockNumber != _getBlockNumber()) { + if (accrualBlockNumber != _getBlockNumber()) { revert RepayBorrowFreshnessCheck(); } @@ -1646,12 +1646,7 @@ contract VToken is validateRebalanceStableBorrowRate(); _updateUserStableBorrowBalance(account); - uint256 stableBorrowRate = stableRateModel.getBorrowRate( - _getCashPrior(), - stableBorrows, - totalBorrows, - totalReserves - ); + uint256 stableBorrowRate = stableBorrowRatePerBlock(); accountStableBorrows[account].stableRateMantissa = stableBorrowRate; diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 65f5123fe..574a66ad6 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -340,7 +340,7 @@ abstract contract VTokenInterface is VTokenStorage { event UpdatedUserStableBorrowBalance(address borrower, uint256 updatedPrincipal); /** - * @notice Event emitted on stable rate rebalacing + * @notice Event emitted on stable rate rebalacing */ event RebalancedStableBorrowRate(address account, uint256 stableRateMantissa); diff --git a/tests/hardhat/Tokens/borrowAndRepayTest.ts b/tests/hardhat/Tokens/borrowAndRepayTest.ts index 2d2b33a5d..d33d3129a 100644 --- a/tests/hardhat/Tokens/borrowAndRepayTest.ts +++ b/tests/hardhat/Tokens/borrowAndRepayTest.ts @@ -326,6 +326,27 @@ describe("VToken", function () { await borrowStable(vToken, borrower, borrowAmount); expect(await underlying.balanceOf(borrowerAddress)).to.equal(beforeAccountCash.add(borrowAmount)); }); + + it("borrow at both rates", async () => { + const beforeAccountCash = await underlying.balanceOf(borrowerAddress); + await vToken.harnessFastForward(5); + + await borrowStable(vToken, borrower, convertToUnit(500, 18)); + expect(await underlying.balanceOf(borrowerAddress)).to.equal(beforeAccountCash.add(convertToUnit(500, 18))); + + const stabelBorrows = await vToken.stableBorrows(); + expect(stabelBorrows).to.equal(convertToUnit(500, 18)); + + const borrowSnapStable = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + expect(borrowSnapStable.principal).to.equal(convertToUnit(500, 18)); + + await borrow(vToken, borrower, convertToUnit(500, 18)); + + const borrowSnapVariable = await vToken.harnessAccountBorrows(borrower.getAddress()); + expect(borrowSnapVariable.principal).to.equal(convertToUnit(500, 18)); + + expect(await underlying.balanceOf(borrowerAddress)).to.equal(beforeAccountCash.add(borrowAmount)); + }); }); describe("repayBorrowFresh for variable rate borrowing", () => { From 81a198df363f0c9f1df88d33992e31b547a90dcc Mon Sep 17 00:00:00 2001 From: defcon022 Date: Fri, 3 Feb 2023 19:47:18 +0530 Subject: [PATCH 04/23] Setters for the thresholds. --- contracts/VToken.sol | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 09546423b..0283ee5c3 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1655,6 +1655,9 @@ contract VToken is /// Validate the conditions to rebalance the stable borrow rate. function validateRebalanceStableBorrowRate() public view { + require(rebalanceUtilizationRateThreshold > 0, "rebalanceUtilizationRateThreshold is not set."); + require(rebalanceRateFractionThreshold > 0, "rebalanceRateFractionThreshold is not set."); + uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); @@ -1935,6 +1938,28 @@ contract VToken is token.safeTransfer(to, amount); } + /** + * @notice Sets the utilization threshold for stable rate rebalancing + * @param utilizationRateThreshold The utilization rate threshold + */ + function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { + require(msg.sender == owner(), "only admin can set ACL address"); + require(utilizationRateThreshold > 0, "vToken: utilization rate should be greater than zero."); + rebalanceUtilizationRateThreshold = utilizationRateThreshold; + } + + /** + * @notice Sets the fraction threshold for stable rate rebalancing + * @param fractionThreshold The fraction threshold for the validation of the stable rate rebalancing + */ + function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { + require(msg.sender == owner(), "only admin can set ACL address"); + require(fractionThreshold > 0, "vToken: fraction threshold should be greater than zero."); + rebalanceRateFractionThreshold = fractionThreshold; + } + + /*** Reentrancy Guard ***/ + /** * @notice Transfer `tokens` tokens from `src` to `dst` by `spender` * @dev Called by both `transfer` and `transferFrom` internally From 4c318f8df0087c57e0338b559c42ac78f88c14d3 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Tue, 7 Feb 2023 01:07:51 +0530 Subject: [PATCH 05/23] Rebalance stable rate tests. --- contracts/VToken.sol | 11 +- contracts/test/VTokenHarness.sol | 5 +- tests/hardhat/Tokens/stableRateRebalancing.ts | 116 ++++++++++++++++++ 3 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 tests/hardhat/Tokens/stableRateRebalancing.ts diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 0283ee5c3..a6d770e13 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1655,12 +1655,11 @@ contract VToken is /// Validate the conditions to rebalance the stable borrow rate. function validateRebalanceStableBorrowRate() public view { - require(rebalanceUtilizationRateThreshold > 0, "rebalanceUtilizationRateThreshold is not set."); - require(rebalanceRateFractionThreshold > 0, "rebalanceRateFractionThreshold is not set."); + require(rebalanceUtilizationRateThreshold > 0, "vToken: rebalanceUtilizationRateThreshold is not set."); + require(rebalanceRateFractionThreshold > 0, "vToken: rebalanceRateFractionThreshold is not set."); uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); - /// Utilization rate is above rebalanceUtilizationRateThreshold. /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. require(utilizationRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); @@ -1942,17 +1941,17 @@ contract VToken is * @notice Sets the utilization threshold for stable rate rebalancing * @param utilizationRateThreshold The utilization rate threshold */ - function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { + function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { require(msg.sender == owner(), "only admin can set ACL address"); require(utilizationRateThreshold > 0, "vToken: utilization rate should be greater than zero."); rebalanceUtilizationRateThreshold = utilizationRateThreshold; } - /** + /** * @notice Sets the fraction threshold for stable rate rebalancing * @param fractionThreshold The fraction threshold for the validation of the stable rate rebalancing */ - function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { + function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { require(msg.sender == owner(), "only admin can set ACL address"); require(fractionThreshold > 0, "vToken: fraction threshold should be greater than zero."); rebalanceRateFractionThreshold = fractionThreshold; diff --git a/contracts/test/VTokenHarness.sol b/contracts/test/VTokenHarness.sol index dda900f56..edee9fb68 100644 --- a/contracts/test/VTokenHarness.sol +++ b/contracts/test/VTokenHarness.sol @@ -128,11 +128,12 @@ contract VTokenHarness is VToken { returns ( uint256 principal, uint256 interestIndex, - uint256 lastBlockAccrued + uint256 lastBlockAccrued, + uint256 stableRateMantissa ) { StableBorrowSnapshot memory snapshot = accountStableBorrows[account]; - return (snapshot.principal, snapshot.interestIndex, snapshot.lastBlockAccrued); + return (snapshot.principal, snapshot.interestIndex, snapshot.lastBlockAccrued, snapshot.stableRateMantissa); } function harnessSetAccountBorrows( diff --git a/tests/hardhat/Tokens/stableRateRebalancing.ts b/tests/hardhat/Tokens/stableRateRebalancing.ts new file mode 100644 index 000000000..a469332e4 --- /dev/null +++ b/tests/hardhat/Tokens/stableRateRebalancing.ts @@ -0,0 +1,116 @@ +import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import chai from "chai"; +import { BigNumberish, Signer } from "ethers"; +import { ethers } from "hardhat"; + +import { convertToUnit } from "../../../helpers/utils"; +import { InterestRateModel, StableRateModel, VTokenHarness } from "../../../typechain"; +import { VTokenTestFixture, vTokenTestFixture } from "../util/TokenTestHelpers"; + +const { expect } = chai; +chai.use(smock.matchers); + +const borrowAmount = convertToUnit("1000", 18); + +async function preBorrow( + contracts: VTokenTestFixture, + borrower: Signer, + borrowAmount: BigNumberish, + stableRateMantissa: BigNumberish = 0, +) { + const { comptroller, interestRateModel, underlying, vToken, stableInterestRateModel } = contracts; + comptroller.preBorrowHook.reset(); + + interestRateModel.getBorrowRate.reset(); + stableInterestRateModel.getBorrowRate.returns(stableRateMantissa); + + const borrowerAddress = await borrower.getAddress(); + await underlying.harnessSetBalance(vToken.address, borrowAmount); + await vToken.harnessSetFailTransferToAddress(borrowerAddress, false); + await vToken.harnessSetAccountBorrows(borrowerAddress, 0, 0); + await vToken.harnessSetTotalBorrows(0); +} + +async function borrowStable(vToken: MockContract, borrower: Signer, borrowAmount: BigNumberish) { + // make sure to have a block delta so we accrue interest + await vToken.harnessFastForward(1); + return vToken.connect(borrower).borrowStable(borrowAmount); +} + +describe("VToken", function () { + let contracts: VTokenTestFixture; + let vToken: MockContract; + let interestRateModel: FakeContract; + let stableInterestRateModel: FakeContract; + let _root: Signer; + let borrower: Signer; + + beforeEach(async () => { + [_root, borrower] = await ethers.getSigners(); + contracts = await loadFixture(vTokenTestFixture); + ({ vToken, interestRateModel, stableInterestRateModel } = contracts); + }); + + describe("Rebalance Stable rate", () => { + it("Revert on rebalanceUtilizationRateThreshold not set", async () => { + await expect(vToken.validateRebalanceStableBorrowRate()).to.be.revertedWith( + "vToken: rebalanceUtilizationRateThreshold is not set.", + ); + }); + + it("Revert on rebalanceRateFractionThreshold not set", async () => { + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 18)); + await expect(vToken.validateRebalanceStableBorrowRate()).to.be.revertedWith( + "vToken: rebalanceRateFractionThreshold is not set.", + ); + }); + + it("Revert on low utilization rate", async () => { + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); + await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); + await expect(vToken.validateRebalanceStableBorrowRate()).to.be.revertedWith( + "vToken: low utilization rate for rebalacing.", + ); + }); + + it("Revert on average borrow rate higher than variable rate threshold", async () => { + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); + await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); + await preBorrow(contracts, borrower, borrowAmount); + await borrowStable(vToken, borrower, convertToUnit(900, 18)); + interestRateModel.utilizationRate.returns(convertToUnit(90, 17)); + await expect(vToken.validateRebalanceStableBorrowRate()).to.be.revertedWith( + "vToken: average borrow rate higher than variable rate threshold.", + ); + }); + + it("Satisfy both conditions", async () => { + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); + await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); + await preBorrow(contracts, borrower, borrowAmount); + await borrowStable(vToken, borrower, convertToUnit(900, 18)); + interestRateModel.utilizationRate.returns(convertToUnit(90, 17)); + interestRateModel.getBorrowRate.returns(convertToUnit(5, 8)); + await vToken.validateRebalanceStableBorrowRate(); + }); + + it("Rebalacing the stable rate for a user", async () => { + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); + await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); + await preBorrow(contracts, borrower, borrowAmount, convertToUnit(5, 6)); + await borrowStable(vToken, borrower, convertToUnit(900, 18)); + + let accountBorrows = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(5, 6)); + + interestRateModel.utilizationRate.returns(convertToUnit(90, 17)); + interestRateModel.getBorrowRate.returns(convertToUnit(5, 8)); + stableInterestRateModel.getBorrowRate.returns(convertToUnit(8, 8)); + await vToken.rebalanceStableBorrowRate(borrower.getAddress()); + + accountBorrows = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(8, 8)); + }); + }); +}); From 6609c29d663ba590621df8af4fb7372264b9fcf3 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Wed, 8 Feb 2023 15:39:15 +0530 Subject: [PATCH 06/23] Swap borrow rate mode with amount. --- contracts/VToken.sol | 177 ++++++++++++++++++++++++--------- contracts/VTokenInterfaces.sol | 5 + 2 files changed, 137 insertions(+), 45 deletions(-) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index a6d770e13..d21b46e55 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1395,11 +1395,7 @@ contract VToken is return actualRepayAmount; } - /** - * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa - * @param rateMode The rate mode that the user wants to swap to - **/ - function swapBorrowRateMode(uint256 rateMode) external { + function swapBorrowRateModePreCalculation(address account) internal returns (uint256, uint256) { /* Fail if swapBorrowRateMode not allowed */ comptroller.preSwapBorrowRateModeHook(address(this)); @@ -1410,61 +1406,109 @@ contract VToken is revert SwapBorrowRateModeFreshnessCheck(); } - address account = msg.sender; uint256 variableDebt = _borrowBalanceStored(account); uint256 stableDebt = _updateUserStableBorrowBalance(account); - uint256 accountBorrowsNew = variableDebt + stableDebt; - uint256 stableBorrowsNew; - uint256 averageStableBorrowRateNew; - if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { - require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); + return (variableDebt, stableDebt); + } - stableBorrowsNew = stableBorrows + variableDebt; - uint256 stableBorrowRate = stableBorrowRatePerBlock(); + function _updateStatesForStableRateSwap( + uint256 swappedAmount, + uint256 stableDebt, + uint256 variableDebt, + address account, + uint256 accountBorrowsNew + ) internal returns (uint256, uint256) { + uint256 stableBorrowsNew = stableBorrows + swappedAmount; + uint256 stableBorrowRate = stableBorrowRatePerBlock(); - averageStableBorrowRateNew = - ((stableBorrows * averageStableBorrowRate) + (variableDebt * stableBorrowRate)) / - stableBorrowsNew; + uint256 averageStableBorrowRateNew = ((stableBorrows * averageStableBorrowRate) + + (swappedAmount * stableBorrowRate)) / stableBorrowsNew; - uint256 stableRateMantissaNew = ((stableDebt * accountStableBorrows[account].stableRateMantissa) + - (variableDebt * stableBorrowRate)) / accountBorrowsNew; + uint256 stableRateMantissaNew = ((stableDebt * accountStableBorrows[account].stableRateMantissa) + + (swappedAmount * stableBorrowRate)) / accountBorrowsNew; - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) - accountStableBorrows[account].principal = stableDebt + variableDebt; - accountStableBorrows[account].interestIndex = stableBorrowIndex; - accountStableBorrows[account].stableRateMantissa = stableRateMantissaNew; + accountStableBorrows[account].principal = accountBorrowsNew; + accountStableBorrows[account].interestIndex = stableBorrowIndex; + accountStableBorrows[account].stableRateMantissa = stableRateMantissaNew; - accountBorrows[account].principal = 0; - accountBorrows[account].interestIndex = borrowIndex; - } else { - require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); + accountBorrows[account].principal = variableDebt - swappedAmount; + accountBorrows[account].interestIndex = borrowIndex; - stableBorrowsNew = stableBorrows - stableDebt; + return (stableBorrowsNew, averageStableBorrowRateNew); + } - uint256 stableRateMantissa = accountStableBorrows[account].stableRateMantissa; + function _updateStatesForVariableRateSwap( + uint256 swappedAmount, + uint256 stableDebt, + uint256 variableDebt, + address account + ) internal returns (uint256, uint256) { + uint256 newStableDebt = stableDebt - swappedAmount; + uint256 stableBorrowsNew = stableBorrows - swappedAmount; - if (stableBorrowsNew == 0) { - averageStableBorrowRateNew = 0; - } else { - unchecked { - averageStableBorrowRateNew = - ((stableBorrows * averageStableBorrowRate) - (stableDebt * stableRateMantissa)) / - stableBorrowsNew; - } + uint256 stableRateMantissa = accountStableBorrows[account].stableRateMantissa; + uint256 averageStableBorrowRateNew; + if (stableBorrowsNew == 0) { + averageStableBorrowRateNew = 0; + } else { + unchecked { + averageStableBorrowRateNew = + ((stableBorrows * averageStableBorrowRate) - (swappedAmount * stableRateMantissa)) / + stableBorrowsNew; } + } - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - accountBorrows[account].principal = accountBorrowsNew; - accountBorrows[account].interestIndex = borrowIndex; + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + accountBorrows[account].principal = variableDebt + swappedAmount; + accountBorrows[account].interestIndex = borrowIndex; + + accountStableBorrows[account].principal = newStableDebt; + accountStableBorrows[account].interestIndex = stableBorrowIndex; + + return (stableBorrowsNew, averageStableBorrowRateNew); + } - accountStableBorrows[account].principal = 0; - accountStableBorrows[account].interestIndex = stableBorrowIndex; + /** + * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa + * @param rateMode The rate mode that the user wants to swap to + **/ + function swapBorrowRateMode(uint256 rateMode) external { + address account = msg.sender; + (uint256 variableDebt, uint256 stableDebt) = swapBorrowRateModePreCalculation(account); + + uint256 accountBorrowsNew = stableDebt + variableDebt; + uint256 stableBorrowsNew; + uint256 averageStableBorrowRateNew; + uint256 amount; + + if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { + require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); + amount = variableDebt; + + (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForStableRateSwap( + amount, + stableDebt, + variableDebt, + account, + accountBorrowsNew + ); + } else { + require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); + amount = stableDebt; + + (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForVariableRateSwap( + amount, + stableDebt, + variableDebt, + account + ); } stableBorrows = stableBorrowsNew; @@ -1473,6 +1517,49 @@ contract VToken is emit SwapBorrowRateMode(account, rateMode); } + /** + * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa with specific amount + * @param rateMode The rate mode that the user wants to swap to + * @param amount The amount that the user wants to convert form stable to variable mode or vice versa. + * @custom:access Not restricted + **/ + function swapBorrowRateModeWithAmount(uint256 rateMode, uint256 amount) external { + address account = msg.sender; + (uint256 variableDebt, uint256 stableDebt) = swapBorrowRateModePreCalculation(account); + + uint256 accountBorrowsNew = stableDebt + amount; + uint256 stableBorrowsNew; + uint256 averageStableBorrowRateNew; + + if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { + require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); + require(variableDebt >= amount, "Insufficient amount"); + + (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForStableRateSwap( + amount, + stableDebt, + variableDebt, + account, + accountBorrowsNew + ); + } else { + require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); + require(stableDebt >= amount, "Insufficient amount"); + + (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForVariableRateSwap( + amount, + stableDebt, + variableDebt, + account + ); + } + + stableBorrows = stableBorrowsNew; + averageStableBorrowRate = averageStableBorrowRateNew; + + emit SwapBorrowRateModeWithAmount(account, rateMode, amount); + } + /** * @notice The sender liquidates the borrowers collateral. * The collateral seized is transferred to the liquidator. diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 574a66ad6..22d161700 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -264,6 +264,11 @@ abstract contract VTokenInterface is VTokenStorage { */ event SwapBorrowRateMode(address account, uint256 swappedBorrowMode); + /** + * @notice Event emitted when a borrow rate mode is swapped for account with amount + */ + event SwapBorrowRateModeWithAmount(address account, uint256 swappedBorrowMode, uint256 amount); + /*** Admin Events ***/ /** From 5a3cb093fce429dbbd43ff5e6360beb7d27ac765 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Wed, 8 Feb 2023 15:39:51 +0530 Subject: [PATCH 07/23] Tests: swap borrow rate amount. --- tests/hardhat/Tokens/swapBorrowRateMode.ts | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/hardhat/Tokens/swapBorrowRateMode.ts b/tests/hardhat/Tokens/swapBorrowRateMode.ts index a4c0eded0..e94ccb6b5 100644 --- a/tests/hardhat/Tokens/swapBorrowRateMode.ts +++ b/tests/hardhat/Tokens/swapBorrowRateMode.ts @@ -100,4 +100,56 @@ describe("VToken", function () { expect(stableBorrow.principal).equal(0); }); }); + + describe("swapBorrowRateModeWithAmount: tests", () => { + it("fails if variable debt is 0", async () => { + await expect(vToken.swapBorrowRateModeWithAmount(1, borrowAmount)).to.be.revertedWith( + "vToken: swapBorrowRateMode variable debt is 0", + ); + }); + + it("fails if stable debt is 0", async () => { + await expect(vToken.swapBorrowRateModeWithAmount(2, borrowAmount)).to.be.revertedWith( + "vToken: swapBorrowRateMode stable debt is 0", + ); + }); + + it("Swapping borrow rate mode from variable to stable with amount", async () => { + await preBorrow(contracts, borrower, borrowAmount); + await borrow(vToken, borrower, borrowAmount); + let variableBorrow, stableBorrow; + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(borrowAmount); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(0); + + await vToken.connect(borrower).swapBorrowRateModeWithAmount(1, convertToUnit("400", 18)); + + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(convertToUnit("600", 18)); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(convertToUnit("400", 18)); + }); + + it("Swapping borrow rate mode from variable to stable with amount", async () => { + await preBorrow(contracts, borrower, borrowAmount); + await borrowStable(vToken, borrower, borrowAmount); + let variableBorrow, stableBorrow; + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(convertToUnit("0", 18)); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(convertToUnit("1000", 18)); + + await vToken.connect(borrower).swapBorrowRateModeWithAmount(2, convertToUnit("200", 18)); + + variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); + expect(variableBorrow.principal).equal(convertToUnit("200", 18)); + + stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); + expect(stableBorrow.principal).equal(convertToUnit("800", 18)); + }); + }); }); From 8dbf6c79a393c06f47002aecaa0dbaf22ff06b86 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Mon, 13 Feb 2023 13:56:10 +0530 Subject: [PATCH 08/23] Minor changes. --- contracts/VToken.sol | 8 +++++--- contracts/VTokenInterfaces.sol | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index d21b46e55..e0f12ad3b 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1954,7 +1954,7 @@ contract VToken is * @custom:events Emits NewMarketStableInterestRateModel, after setting the new stable rate model * @custom:access Controlled by AccessControlManager */ - function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public override returns (uint256) { + function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public override { bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( msg.sender, "setStableInterestRateModel(address)" @@ -1966,7 +1966,7 @@ contract VToken is } accrueInterest(); // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. - return _setStableInterestRateModelFresh(newStableInterestRateModel); + _setStableInterestRateModelFresh(newStableInterestRateModel); } /** @@ -1975,7 +1975,7 @@ contract VToken is * @param newStableInterestRateModel The new stable interest rate model to use * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) */ - function _setStableInterestRateModelFresh(StableRateModel newStableInterestRateModel) internal returns (uint256) { + function _setStableInterestRateModelFresh(StableRateModel newStableInterestRateModel) internal { // Used to store old model for use in the event that is emitted on success StableRateModel oldStableInterestRateModel; @@ -2027,6 +2027,7 @@ contract VToken is /** * @notice Sets the utilization threshold for stable rate rebalancing * @param utilizationRateThreshold The utilization rate threshold + * @custom:access Only Governance */ function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { require(msg.sender == owner(), "only admin can set ACL address"); @@ -2037,6 +2038,7 @@ contract VToken is /** * @notice Sets the fraction threshold for stable rate rebalancing * @param fractionThreshold The fraction threshold for the validation of the stable rate rebalancing + * @custom:access Only Governance */ function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { require(msg.sender == owner(), "only admin can set ACL address"); diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 22d161700..0d7888f16 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -449,7 +449,7 @@ abstract contract VTokenInterface is VTokenStorage { return true; } - function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public virtual returns (uint256); + function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public virtual; function addReserves(uint256 addAmount) external virtual; } From 532d3d732d5468c7acf7c05b2f8b8bd9c6fcfee4 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Mon, 20 Feb 2023 20:09:25 +0530 Subject: [PATCH 09/23] Fix: PR comments. --- contracts/ErrorReporter.sol | 3 + contracts/VToken.sol | 69 +++++++++++++++---- contracts/VTokenInterfaces.sol | 10 +++ tests/hardhat/Tokens/stableRateRebalancing.ts | 12 ++++ 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/contracts/ErrorReporter.sol b/contracts/ErrorReporter.sol index 399588569..b81dc3fec 100644 --- a/contracts/ErrorReporter.sol +++ b/contracts/ErrorReporter.sol @@ -50,4 +50,7 @@ contract TokenErrorReporter { error SetStableInterestRateModelFreshCheck(); error SwapBorrowRateModeFreshnessCheck(); + + error SetRebalanceUtilizationRateThresholdAdminCheck(); + error SetRebalanceRateFractionThresholdAdminCheck(); } diff --git a/contracts/VToken.sol b/contracts/VToken.sol index e0f12ad3b..e44807e16 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1395,7 +1395,12 @@ contract VToken is return actualRepayAmount; } - function swapBorrowRateModePreCalculation(address account) internal returns (uint256, uint256) { + /** + * @notice Checks before swapping borrow rate mode + * @param account Address of the borrow holder + * @return (uint256, uint256) returns the variableDebt and stableDebt for the account + */ + function _swapBorrowRateModePreCalculation(address account) internal returns (uint256, uint256) { /* Fail if swapBorrowRateMode not allowed */ comptroller.preSwapBorrowRateModeHook(address(this)); @@ -1412,6 +1417,15 @@ contract VToken is return (variableDebt, stableDebt); } + /** + * @notice Update states for the stable borrow while swapping + * @param swappedAmount Amount need to be swapped + * @param stableDebt Stable debt for the account + * @param variableDebt Variable debt for the account + * @param account Address of the account + * @param accountBorrowsNew New stable borrow for the account + * @return (uint256, uint256) returns updated stable borrow for the account and updated average stable borrow rate + */ function _updateStatesForStableRateSwap( uint256 swappedAmount, uint256 stableDebt, @@ -1442,6 +1456,14 @@ contract VToken is return (stableBorrowsNew, averageStableBorrowRateNew); } + /** + * @notice Update states for the variable borrow during the swap + * @param swappedAmount Amount need to be swapped + * @param stableDebt Stable debt for the account + * @param variableDebt Variable debt for the account + * @param account Address of the account + * @return (uint256, uint256) returns updated stable borrow for the account and updated average stable borrow rate + */ function _updateStatesForVariableRateSwap( uint256 swappedAmount, uint256 stableDebt, @@ -1481,7 +1503,7 @@ contract VToken is **/ function swapBorrowRateMode(uint256 rateMode) external { address account = msg.sender; - (uint256 variableDebt, uint256 stableDebt) = swapBorrowRateModePreCalculation(account); + (uint256 variableDebt, uint256 stableDebt) = _swapBorrowRateModePreCalculation(account); uint256 accountBorrowsNew = stableDebt + variableDebt; uint256 stableBorrowsNew; @@ -1525,7 +1547,7 @@ contract VToken is **/ function swapBorrowRateModeWithAmount(uint256 rateMode, uint256 amount) external { address account = msg.sender; - (uint256 variableDebt, uint256 stableDebt) = swapBorrowRateModePreCalculation(account); + (uint256 variableDebt, uint256 stableDebt) = _swapBorrowRateModePreCalculation(account); uint256 accountBorrowsNew = stableDebt + amount; uint256 stableBorrowsNew; @@ -1746,10 +1768,13 @@ contract VToken is require(rebalanceRateFractionThreshold > 0, "vToken: rebalanceRateFractionThreshold is not set."); uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); - uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + /// Utilization rate is above rebalanceUtilizationRateThreshold. - /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. require(utilizationRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); + + uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + + /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. require( _averageMarketBorrowRate() < (variableBorrowRate * rebalanceRateFractionThreshold), "vToken: average borrow rate higher than variable rate threshold." @@ -1949,7 +1974,6 @@ contract VToken is * @notice Accrues interest and updates the stable interest rate model using _setStableInterestRateModelFresh * @dev Admin function to accrue interest and update the stable interest rate model * @param newStableInterestRateModel The new interest rate model to use - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) * @custom:events Emits NewMarketInterestRateModel event; may emit AccrueInterest * @custom:events Emits NewMarketStableInterestRateModel, after setting the new stable rate model * @custom:access Controlled by AccessControlManager @@ -1973,7 +1997,6 @@ contract VToken is * @notice Updates the stable interest rate model (requires fresh interest accrual) * @dev Admin function to update the stable interest rate model * @param newStableInterestRateModel The new stable interest rate model to use - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) */ function _setStableInterestRateModelFresh(StableRateModel newStableInterestRateModel) internal { // Used to store old model for use in the event that is emitted on success @@ -2027,23 +2050,43 @@ contract VToken is /** * @notice Sets the utilization threshold for stable rate rebalancing * @param utilizationRateThreshold The utilization rate threshold - * @custom:access Only Governance + * @custom:access Controlled by AccessControlManager */ function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { - require(msg.sender == owner(), "only admin can set ACL address"); - require(utilizationRateThreshold > 0, "vToken: utilization rate should be greater than zero."); + bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( + msg.sender, + "setRebalanceUtilizationRateThreshold(uint256)" + ); + // Check caller is allowed to call this function + if (!canCallFunction) { + revert SetRebalanceUtilizationRateThresholdAdminCheck(); + } + + uint256 oldThreshold = rebalanceUtilizationRateThreshold; rebalanceUtilizationRateThreshold = utilizationRateThreshold; + + emit RebalanceUtilizationRateThresholdUpdated(oldThreshold, rebalanceUtilizationRateThreshold); } /** * @notice Sets the fraction threshold for stable rate rebalancing * @param fractionThreshold The fraction threshold for the validation of the stable rate rebalancing - * @custom:access Only Governance + * @custom:access Controlled by AccessControlManager */ function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { - require(msg.sender == owner(), "only admin can set ACL address"); - require(fractionThreshold > 0, "vToken: fraction threshold should be greater than zero."); + bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( + msg.sender, + "setRebalanceRateFractionThreshold(uint256)" + ); + // Check caller is allowed to call this function + if (!canCallFunction) { + revert SetRebalanceRateFractionThresholdAdminCheck(); + } + + uint256 oldThreshold = rebalanceRateFractionThreshold; rebalanceRateFractionThreshold = fractionThreshold; + + emit RebalanceRateFractionThresholdUpdated(oldThreshold, rebalanceRateFractionThreshold); } /*** Reentrancy Guard ***/ diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 0d7888f16..ccd5266e3 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -349,6 +349,16 @@ abstract contract VTokenInterface is VTokenStorage { */ event RebalancedStableBorrowRate(address account, uint256 stableRateMantissa); + /** + * @notice Event emitted when rebalanceUtilizationRateThreshold is updated + */ + event RebalanceUtilizationRateThresholdUpdated(uint256 oldThreshold, uint256 newThreshold); + + /** + * @notice Event emitted when rebalanceRateFractionThreshold is updated + */ + event RebalanceRateFractionThresholdUpdated(uint256 oldThreshold, uint256 newThreshold); + /*** User Interface ***/ function mint(uint256 mintAmount) external virtual returns (uint256); diff --git a/tests/hardhat/Tokens/stableRateRebalancing.ts b/tests/hardhat/Tokens/stableRateRebalancing.ts index a469332e4..c701615d4 100644 --- a/tests/hardhat/Tokens/stableRateRebalancing.ts +++ b/tests/hardhat/Tokens/stableRateRebalancing.ts @@ -66,6 +66,18 @@ describe("VToken", function () { ); }); + it("Emit event RebalanceUtilizationRateThresholdUpdated", async () => { + await expect(vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 18))) + .emit(vToken, "RebalanceUtilizationRateThresholdUpdated") + .withArgs(0, convertToUnit(70, 18)); + }); + + it("Emit event RebalanceRateFractionThresholdUpdated", async () => { + await expect(vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17))) + .emit(vToken, "RebalanceUtilizationRateThresholdUpdated") + .withArgs(0, convertToUnit(70, 17)); + }); + it("Revert on low utilization rate", async () => { await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); From 86ccc84efad1f83e3741b6dcc70f84043315141a Mon Sep 17 00:00:00 2001 From: defcon022 Date: Tue, 21 Feb 2023 13:18:53 +0530 Subject: [PATCH 10/23] Refactor: rebalancing condition for the stable rate. --- contracts/InterestRateModel.sol | 19 ++----- contracts/JumpRateModelV2.sol | 14 ++--- contracts/VToken.sol | 54 +++++++++++++++---- contracts/VTokenInterfaces.sol | 8 +++ contracts/WhitePaperInterestRateModel.sol | 41 ++------------ tests/hardhat/StableRateModel.ts | 3 +- tests/hardhat/Tokens/stableRateRebalancing.ts | 11 ++-- 7 files changed, 68 insertions(+), 82 deletions(-) diff --git a/contracts/InterestRateModel.sol b/contracts/InterestRateModel.sol index b596f017c..e37f4d741 100644 --- a/contracts/InterestRateModel.sol +++ b/contracts/InterestRateModel.sol @@ -6,21 +6,6 @@ pragma solidity 0.8.13; * @author Compound */ abstract contract InterestRateModel { - /** - * @notice Calculates the current borrow interest rate per block - * @param cash The total amount of cash the market has - * @param borrows The total amount of borrows the market has outstanding - * @param reserves The total amount of reserves the market has - * @param badDebt The amount of badDebt in the market - * @return The borrow rate per block (as a percentage, and scaled by 1e18) - */ - function getBorrowRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 badDebt - ) external view virtual returns (uint256); - /** * @notice Calculates the utilization rate for the market * @param cash The total amount of cash the market has @@ -45,4 +30,8 @@ abstract contract InterestRateModel { function isInterestRateModel() external pure virtual returns (bool) { return true; } + * @param utilizationRate The utilization rate as per total borrows and cash available + * @return The borrow rate per block (as a percentage, and scaled by 1e18) + */ + function getBorrowRate(uint256 utilizationRate) external view virtual returns (uint256); } diff --git a/contracts/JumpRateModelV2.sol b/contracts/JumpRateModelV2.sol index 0d322145f..435783fd3 100644 --- a/contracts/JumpRateModelV2.sol +++ b/contracts/JumpRateModelV2.sol @@ -26,18 +26,10 @@ contract JumpRateModelV2 is BaseJumpRateModelV2 { /** * @notice Calculates the current borrow rate per block - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market - * @param badDebt The amount of badDebt in the market + * @param utilizationRate The utilization rate as per total borrows and cash available * @return The borrow rate percentage per block as a mantissa (scaled by 1e18) */ - function getBorrowRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 badDebt - ) external view override returns (uint256) { - return _getBorrowRate(cash, borrows, reserves, badDebt); + function getBorrowRate(uint256 utilizationRate) external view override returns (uint256) { + return getBorrowRateInternal(utilizationRate); } } diff --git a/contracts/VToken.sol b/contracts/VToken.sol index e44807e16..cb31388f6 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -697,7 +697,18 @@ contract VToken is * @return rate The borrow interest rate per block, scaled by 1e18 */ function borrowRatePerBlock() external view override returns (uint256) { - return interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves, badDebt); + return interestRateModel.getBorrowRate(utilizationRate(_getCashPrior(), totalBorrows, totalReserves)); + } + + /** + * @notice Returns the current per-block borrow interest rate for this vToken + * @return rate The borrow interest rate per block, scaled by 1e18 + */ + function stableBorrowRatePerBlock() public view override returns (uint256) { + uint256 variableBorrowRate = interestRateModel.getBorrowRate( + utilizationRate(_getCashPrior(), totalBorrows, totalReserves) + ); + return stableRateModel.getBorrowRate(stableBorrows, totalBorrows, variableBorrowRate); } /** @@ -708,16 +719,17 @@ contract VToken is if (totalBorrows == 0) { return 0; } - uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves); uint256 averageMarketBorrowRate = _averageMarketBorrowRate(); return - (averageMarketBorrowRate * utilizationRate * (mantissaOne - reserveFactorMantissa)) / - (mantissaOne * mantissaOne); + (averageMarketBorrowRate * utilRate * (mantissaOne - reserveFactorMantissa)) / (mantissaOne * mantissaOne); } /// @notice Calculate the average market borrow rate with respect to variable and stable borrows function _averageMarketBorrowRate() internal view returns (uint256) { - uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 variableBorrowRate = interestRateModel.getBorrowRate( + utilizationRate(_getCashPrior(), totalBorrows, totalReserves) + ); uint256 variableBorrows = totalBorrows - stableBorrows; return ((variableBorrows * variableBorrowRate) + (stableBorrows * averageStableBorrowRate)) / totalBorrows; } @@ -913,8 +925,10 @@ contract VToken is uint256 borrowIndexPrior = borrowIndex; /* Calculate the current borrow interest rate */ - uint256 borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior, badDebt); - require(borrowRateMantissa <= MAX_BORROW_RATE_MANTISSA, "borrow rate is absurdly high"); + uint256 borrowRateMantissa = interestRateModel.getBorrowRate( + utilizationRate(cashPrior, borrowsPrior, reservesPrior) + ); + require(borrowRateMantissa <= borrowRateMaxMantissa, "vToken: borrow rate is absurdly high"); /* Calculate the number of blocks elapsed since the last accrual */ uint256 blockDelta = currentBlockNumber - accrualBlockNumberPrior; @@ -1741,6 +1755,26 @@ contract VToken is emit ReservesAdded(address(this), protocolSeizeAmount, totalReservesNew); } + /** + * @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - reserves)` + * @param cash The amount of cash in the market + * @param borrows The amount of borrows in the market + * @param reserves The amount of reserves in the market (currently unused) + * @return The utilization rate as a mantissa between [0, BASE] + */ + function utilizationRate( + uint256 cash, + uint256 borrows, + uint256 reserves + ) public pure override returns (uint256) { + // Utilization rate is 0 when there are no borrows + if (borrows == 0) { + return 0; + } + + return (borrows * mantissaOne) / (cash + borrows - reserves); + } + /** * @notice Rebalances the stable interest rate of a user to the current stable borrow rate. * - Users can be rebalanced if the following conditions are satisfied: @@ -1767,12 +1801,12 @@ contract VToken is require(rebalanceUtilizationRateThreshold > 0, "vToken: rebalanceUtilizationRateThreshold is not set."); require(rebalanceRateFractionThreshold > 0, "vToken: rebalanceRateFractionThreshold is not set."); - uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves); /// Utilization rate is above rebalanceUtilizationRateThreshold. - require(utilizationRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); + require(utilRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); - uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 variableBorrowRate = interestRateModel.getBorrowRate(rebalanceUtilizationRateThreshold); /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. require( diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index ccd5266e3..28e386e0b 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -421,6 +421,14 @@ abstract contract VTokenInterface is VTokenStorage { function addReserves(uint256 addAmount) external virtual; + function utilizationRate( + uint256 cash, + uint256 borrows, + uint256 reserves + ) public pure virtual returns (uint256); + + function borrowRatePerBlock() external view virtual returns (uint256); + function stableBorrowRatePerBlock() public view virtual returns (uint256); function supplyRatePerBlock() external view virtual returns (uint256); diff --git a/contracts/WhitePaperInterestRateModel.sol b/contracts/WhitePaperInterestRateModel.sol index beeed30ea..54112aa2e 100644 --- a/contracts/WhitePaperInterestRateModel.sol +++ b/contracts/WhitePaperInterestRateModel.sol @@ -36,44 +36,11 @@ contract WhitePaperInterestRateModel is InterestRateModel { /** * @notice Calculates the current borrow rate per block, with the error code expected by the market - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market - * @param badDebt The amount of badDebt in the market - * @return The borrow rate percentage per block as a mantissa (scaled by EXP_SCALE) - */ - function getBorrowRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 badDebt - ) public view override returns (uint256) { - uint256 ur = utilizationRate(cash, borrows, reserves, badDebt); - return ((ur * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock; - } - - /** - * @notice Calculates the current supply rate per block - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market - * @param reserveFactorMantissa The current reserve factor for the market - * @param badDebt The amount of badDebt in the market - * @return The supply rate percentage per block as a mantissa (scaled by EXP_SCALE) + * @param utRate The utilization rate as per total borrows and cash available + * @return The borrow rate percentage per block as a mantissa (scaled by BASE) */ - function getSupplyRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 reserveFactorMantissa, - uint256 badDebt - ) public view override returns (uint256) { - uint256 oneMinusReserveFactor = MANTISSA_ONE - reserveFactorMantissa; - uint256 borrowRate = getBorrowRate(cash, borrows, reserves, badDebt); - uint256 rateToPool = (borrowRate * oneMinusReserveFactor) / EXP_SCALE; - uint256 incomeToDistribute = borrows * rateToPool; - uint256 supply = cash + borrows + badDebt - reserves; - return incomeToDistribute / supply; + function getBorrowRate(uint256 utRate) public view override returns (uint256) { + return ((utRate * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock; } /** diff --git a/tests/hardhat/StableRateModel.ts b/tests/hardhat/StableRateModel.ts index 8faea6f86..7c32a8c33 100644 --- a/tests/hardhat/StableRateModel.ts +++ b/tests/hardhat/StableRateModel.ts @@ -86,13 +86,12 @@ describe("StableRateModel: Tests", function () { }); it("Calculate Supply rate of the market", async function () { - interestRateModel.utilizationRate.returns(convertToUnit(5, 17)); interestRateModel.getBorrowRate.returns(convertToUnit(3, 17)); await vToken.harnessSetTotalBorrows(convertToUnit(2, 20)); await vToken.harnessSetReserveFactorFresh(convertToUnit(1, 17)); await vToken.harnessSetAvgStableBorrowRate(convertToUnit(4, 17)); await vToken.harnessStableBorrows(convertToUnit(2, 18)); - expect((await vToken.supplyRatePerBlock()).toString()).to.equal("135450000000000000"); + expect((await vToken.supplyRatePerBlock()).toString()).to.equal("270900000000000000"); }); }); diff --git a/tests/hardhat/Tokens/stableRateRebalancing.ts b/tests/hardhat/Tokens/stableRateRebalancing.ts index c701615d4..ea60764fb 100644 --- a/tests/hardhat/Tokens/stableRateRebalancing.ts +++ b/tests/hardhat/Tokens/stableRateRebalancing.ts @@ -87,28 +87,26 @@ describe("VToken", function () { }); it("Revert on average borrow rate higher than variable rate threshold", async () => { - await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(7, 17)); await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); await preBorrow(contracts, borrower, borrowAmount); await borrowStable(vToken, borrower, convertToUnit(900, 18)); - interestRateModel.utilizationRate.returns(convertToUnit(90, 17)); await expect(vToken.validateRebalanceStableBorrowRate()).to.be.revertedWith( "vToken: average borrow rate higher than variable rate threshold.", ); }); it("Satisfy both conditions", async () => { - await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(7, 17)); await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); await preBorrow(contracts, borrower, borrowAmount); await borrowStable(vToken, borrower, convertToUnit(900, 18)); - interestRateModel.utilizationRate.returns(convertToUnit(90, 17)); - interestRateModel.getBorrowRate.returns(convertToUnit(5, 8)); + await interestRateModel.getBorrowRate.returns(convertToUnit(5, 8)); await vToken.validateRebalanceStableBorrowRate(); }); it("Rebalacing the stable rate for a user", async () => { - await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(70, 17)); + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(7, 17)); await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); await preBorrow(contracts, borrower, borrowAmount, convertToUnit(5, 6)); await borrowStable(vToken, borrower, convertToUnit(900, 18)); @@ -116,7 +114,6 @@ describe("VToken", function () { let accountBorrows = await vToken.harnessAccountStableBorrows(borrower.getAddress()); expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(5, 6)); - interestRateModel.utilizationRate.returns(convertToUnit(90, 17)); interestRateModel.getBorrowRate.returns(convertToUnit(5, 8)); stableInterestRateModel.getBorrowRate.returns(convertToUnit(8, 8)); await vToken.rebalanceStableBorrowRate(borrower.getAddress()); From cbcef80e2ab5583d3600f66627026843207ee664 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Tue, 21 Feb 2023 14:13:31 +0530 Subject: [PATCH 11/23] Refactored: swapBorrowRateMode method. --- contracts/VToken.sol | 57 ++++------------------ contracts/VTokenInterfaces.sol | 7 +-- tests/hardhat/Tokens/swapBorrowRateMode.ts | 12 +---- 3 files changed, 12 insertions(+), 64 deletions(-) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index cb31388f6..8f7a9f6cb 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1511,48 +1511,6 @@ contract VToken is return (stableBorrowsNew, averageStableBorrowRateNew); } - /** - * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa - * @param rateMode The rate mode that the user wants to swap to - **/ - function swapBorrowRateMode(uint256 rateMode) external { - address account = msg.sender; - (uint256 variableDebt, uint256 stableDebt) = _swapBorrowRateModePreCalculation(account); - - uint256 accountBorrowsNew = stableDebt + variableDebt; - uint256 stableBorrowsNew; - uint256 averageStableBorrowRateNew; - uint256 amount; - - if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { - require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); - amount = variableDebt; - - (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForStableRateSwap( - amount, - stableDebt, - variableDebt, - account, - accountBorrowsNew - ); - } else { - require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); - amount = stableDebt; - - (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForVariableRateSwap( - amount, - stableDebt, - variableDebt, - account - ); - } - - stableBorrows = stableBorrowsNew; - averageStableBorrowRate = averageStableBorrowRateNew; - - emit SwapBorrowRateMode(account, rateMode); - } - /** * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa with specific amount * @param rateMode The rate mode that the user wants to swap to @@ -1563,16 +1521,18 @@ contract VToken is address account = msg.sender; (uint256 variableDebt, uint256 stableDebt) = _swapBorrowRateModePreCalculation(account); - uint256 accountBorrowsNew = stableDebt + amount; uint256 stableBorrowsNew; uint256 averageStableBorrowRateNew; + uint256 finalAmount; if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); - require(variableDebt >= amount, "Insufficient amount"); + + finalAmount = amount > variableDebt ? variableDebt : amount; + uint256 accountBorrowsNew = stableDebt + finalAmount; (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForStableRateSwap( - amount, + finalAmount, stableDebt, variableDebt, account, @@ -1580,10 +1540,11 @@ contract VToken is ); } else { require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); - require(stableDebt >= amount, "Insufficient amount"); + + finalAmount = amount > stableDebt ? stableDebt : amount; (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForVariableRateSwap( - amount, + finalAmount, stableDebt, variableDebt, account @@ -1593,7 +1554,7 @@ contract VToken is stableBorrows = stableBorrowsNew; averageStableBorrowRate = averageStableBorrowRateNew; - emit SwapBorrowRateModeWithAmount(account, rateMode, amount); + emit SwapBorrowRateMode(account, rateMode, amount); } /** diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 28e386e0b..d077db35c 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -259,15 +259,10 @@ abstract contract VTokenInterface is VTokenStorage { uint256 seizeTokens ); - /** - * @notice Event emitted when a borrow rate mode is swapped for account - */ - event SwapBorrowRateMode(address account, uint256 swappedBorrowMode); - /** * @notice Event emitted when a borrow rate mode is swapped for account with amount */ - event SwapBorrowRateModeWithAmount(address account, uint256 swappedBorrowMode, uint256 amount); + event SwapBorrowRateMode(address account, uint256 swappedBorrowMode, uint256 amount); /*** Admin Events ***/ diff --git a/tests/hardhat/Tokens/swapBorrowRateMode.ts b/tests/hardhat/Tokens/swapBorrowRateMode.ts index e94ccb6b5..04873fb0a 100644 --- a/tests/hardhat/Tokens/swapBorrowRateMode.ts +++ b/tests/hardhat/Tokens/swapBorrowRateMode.ts @@ -54,14 +54,6 @@ describe("VToken", function () { }); describe("swapBorrowRateMode: tests", () => { - it("fails if variable debt is 0", async () => { - await expect(vToken.swapBorrowRateMode(1)).to.be.revertedWith("vToken: swapBorrowRateMode variable debt is 0"); - }); - - it("fails if stable debt is 0", async () => { - await expect(vToken.swapBorrowRateMode(2)).to.be.revertedWith("vToken: swapBorrowRateMode stable debt is 0"); - }); - it("Swapping borrow rate mode from variable to stable", async () => { await preBorrow(contracts, borrower, borrowAmount); await borrow(vToken, borrower, borrowAmount); @@ -72,7 +64,7 @@ describe("VToken", function () { stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); expect(stableBorrow.principal).equal(0); - await vToken.connect(borrower).swapBorrowRateMode(1); + await vToken.connect(borrower).swapBorrowRateModeWithAmount(1, convertToUnit("1001", 18)); variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); expect(variableBorrow.principal).equal(0); @@ -91,7 +83,7 @@ describe("VToken", function () { stableBorrow = await vToken.harnessAccountStableBorrows(borrowerAddress); expect(stableBorrow.principal).equal(borrowAmount); - await vToken.connect(borrower).swapBorrowRateMode(2); + await vToken.connect(borrower).swapBorrowRateModeWithAmount(2, convertToUnit("1001", 18)); variableBorrow = await vToken.harnessAccountBorrows(borrowerAddress); expect(variableBorrow.principal).equal(borrowAmount); From c281ffb758cfc28fac08812f79e822c0071289f8 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Thu, 23 Feb 2023 12:19:33 +0530 Subject: [PATCH 12/23] Fix: PR comments. --- contracts/InterestRateModel.sol | 5 ++++- contracts/JumpRateModelV2.sol | 2 +- contracts/VTokenInterfaces.sol | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/contracts/InterestRateModel.sol b/contracts/InterestRateModel.sol index e37f4d741..1a1ea2d2b 100644 --- a/contracts/InterestRateModel.sol +++ b/contracts/InterestRateModel.sol @@ -30,7 +30,10 @@ abstract contract InterestRateModel { function isInterestRateModel() external pure virtual returns (bool) { return true; } - * @param utilizationRate The utilization rate as per total borrows and cash available + + /** + * @notice Calculates the current borrow interest rate per block + * @param utilizationRate The utilization rate per total borrows and cash available * @return The borrow rate per block (as a percentage, and scaled by 1e18) */ function getBorrowRate(uint256 utilizationRate) external view virtual returns (uint256); diff --git a/contracts/JumpRateModelV2.sol b/contracts/JumpRateModelV2.sol index 435783fd3..d94beaa07 100644 --- a/contracts/JumpRateModelV2.sol +++ b/contracts/JumpRateModelV2.sol @@ -26,7 +26,7 @@ contract JumpRateModelV2 is BaseJumpRateModelV2 { /** * @notice Calculates the current borrow rate per block - * @param utilizationRate The utilization rate as per total borrows and cash available + * @param utilizationRate The utilization rate per total borrows and cash available * @return The borrow rate percentage per block as a mantissa (scaled by 1e18) */ function getBorrowRate(uint256 utilizationRate) external view override returns (uint256) { diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index d077db35c..c9760fb2e 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -168,7 +168,7 @@ contract VTokenStorage { VARIABLE } - /// @notice Utilization rate threshold where rebalancing condition get satisfied for stable rate borrowing. + /// @notice Utilization rate threshold for rebalancing stable borrowing rate uint256 internal rebalanceUtilizationRateThreshold; /// @notice Rate fraction for variable rate borrwing where rebalancing condition get satisfied for stable rate borrowing. From 1059cfb82a7478bd76ee6230abea32af1e672fc1 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Thu, 23 Feb 2023 15:23:23 +0530 Subject: [PATCH 13/23] Fix: PR comments. --- contracts/VToken.sol | 24 +++++++++++++----------- contracts/VTokenInterfaces.sol | 8 ++++++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 8f7a9f6cb..283eca9ee 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1514,25 +1514,25 @@ contract VToken is /** * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa with specific amount * @param rateMode The rate mode that the user wants to swap to - * @param amount The amount that the user wants to convert form stable to variable mode or vice versa. + * @param sentAmount The amount that the user wants to convert form stable to variable mode or vice versa. * @custom:access Not restricted **/ - function swapBorrowRateModeWithAmount(uint256 rateMode, uint256 amount) external { + function swapBorrowRateModeWithAmount(uint256 rateMode, uint256 sentAmount) external { address account = msg.sender; (uint256 variableDebt, uint256 stableDebt) = _swapBorrowRateModePreCalculation(account); uint256 stableBorrowsNew; uint256 averageStableBorrowRateNew; - uint256 finalAmount; + uint256 swappedAmount; if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); - finalAmount = amount > variableDebt ? variableDebt : amount; - uint256 accountBorrowsNew = stableDebt + finalAmount; + swappedAmount = sentAmount > variableDebt ? variableDebt : sentAmount; + uint256 accountBorrowsNew = stableDebt + swappedAmount; (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForStableRateSwap( - finalAmount, + swappedAmount, stableDebt, variableDebt, account, @@ -1541,10 +1541,10 @@ contract VToken is } else { require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); - finalAmount = amount > stableDebt ? stableDebt : amount; + swappedAmount = sentAmount > stableDebt ? stableDebt : sentAmount; (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForVariableRateSwap( - finalAmount, + swappedAmount, stableDebt, variableDebt, account @@ -1554,7 +1554,7 @@ contract VToken is stableBorrows = stableBorrowsNew; averageStableBorrowRate = averageStableBorrowRateNew; - emit SwapBorrowRateMode(account, rateMode, amount); + emit SwapBorrowRateMode(account, rateMode, swappedAmount); } /** @@ -1752,9 +1752,10 @@ contract VToken is uint256 stableBorrowRate = stableBorrowRatePerBlock(); + uint256 previousStableRateMantissa = accountStableBorrows[account].stableRateMantissa; accountStableBorrows[account].stableRateMantissa = stableBorrowRate; - emit RebalancedStableBorrowRate(account, stableBorrowRate); + emit RebalancedStableBorrowRate(account, previousStableRateMantissa, stableBorrowRate); } /// Validate the conditions to rebalance the stable borrow rate. @@ -1769,7 +1770,8 @@ contract VToken is uint256 variableBorrowRate = interestRateModel.getBorrowRate(rebalanceUtilizationRateThreshold); - /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. + /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of + /// variable borrow rate when utilization rate is rebalanceUtilizationRateThreshold require( _averageMarketBorrowRate() < (variableBorrowRate * rebalanceRateFractionThreshold), "vToken: average borrow rate higher than variable rate threshold." diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index c9760fb2e..bd9b30b05 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -262,7 +262,7 @@ abstract contract VTokenInterface is VTokenStorage { /** * @notice Event emitted when a borrow rate mode is swapped for account with amount */ - event SwapBorrowRateMode(address account, uint256 swappedBorrowMode, uint256 amount); + event SwapBorrowRateMode(address indexed account, uint256 swappedBorrowMode, uint256 swappedAmount); /*** Admin Events ***/ @@ -342,7 +342,11 @@ abstract contract VTokenInterface is VTokenStorage { /** * @notice Event emitted on stable rate rebalacing */ - event RebalancedStableBorrowRate(address account, uint256 stableRateMantissa); + event RebalancedStableBorrowRate( + address indexed account, + uint256 previousStableRateMantissa, + uint256 stableRateMantissa + ); /** * @notice Event emitted when rebalanceUtilizationRateThreshold is updated From bc8116754fd3ddbd87159497b35151470a95470a Mon Sep 17 00:00:00 2001 From: defcon022 Date: Fri, 24 Feb 2023 14:01:14 +0530 Subject: [PATCH 14/23] VEN-1123: Encapsulate the ACM check in one function. --- contracts/ErrorReporter.sol | 8 ++-- contracts/VToken.sol | 65 ++++++++++++-------------- scenario/src/ErrorReporterConstants.ts | 2 - 3 files changed, 35 insertions(+), 40 deletions(-) diff --git a/contracts/ErrorReporter.sol b/contracts/ErrorReporter.sol index b81dc3fec..52e9d8b73 100644 --- a/contracts/ErrorReporter.sol +++ b/contracts/ErrorReporter.sol @@ -33,6 +33,8 @@ contract TokenErrorReporter { error LiquidateSeizeLiquidatorIsBorrower(); + error SetComptrollerOwnerCheck(); + error ProtocolSeizeShareTooBig(); error SetReserveFactorFreshCheck(); @@ -45,12 +47,10 @@ contract TokenErrorReporter { error ReduceReservesCashValidation(); error SetInterestRateModelFreshCheck(); - - error SetStableInterestRateModelOwnerCheck(); error SetStableInterestRateModelFreshCheck(); error SwapBorrowRateModeFreshnessCheck(); - error SetRebalanceUtilizationRateThresholdAdminCheck(); - error SetRebalanceRateFractionThresholdAdminCheck(); + + error Unauthorized(address sender, address calledContract, string methodSignature); } diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 283eca9ee..f68554c8b 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -1770,7 +1770,7 @@ contract VToken is uint256 variableBorrowRate = interestRateModel.getBorrowRate(rebalanceUtilizationRateThreshold); - /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of + /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of /// variable borrow rate when utilization rate is rebalanceUtilizationRateThreshold require( _averageMarketBorrowRate() < (variableBorrowRate * rebalanceRateFractionThreshold), @@ -1813,19 +1813,12 @@ contract VToken is * @dev must be less than liquidation incentive - 1 * @param newProtocolSeizeShareMantissa_ new protocol share mantissa * @custom:events Emits NewProtocolSeizeShare event on success - * @custom:error SetProtocolSeizeShareUnauthorized is thrown when the call is not authorized by AccessControlManager + * @custom:error Unauthorized is thrown when the call is not authorized by AccessControlManager * @custom:error ProtocolSeizeShareTooBig is thrown when the new seize share is too high * @custom:access Controlled by AccessControlManager */ function setProtocolSeizeShare(uint256 newProtocolSeizeShareMantissa_) external { - bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( - msg.sender, - "setProtocolSeizeShare(uint256)" - ); - // Check caller is allowed to call this function - if (!canCallFunction) { - revert SetProtocolSeizeShareUnauthorized(); - } + _checkAccessAllowed("setProtocolSeizeShare(uint256)"); uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).liquidationIncentiveMantissa(); if (newProtocolSeizeShareMantissa_ + 1e18 > liquidationIncentive) { @@ -1938,6 +1931,21 @@ contract VToken is emit ReservesReduced(protocolShareReserve, reduceAmount, totalReservesNew); } + /** + * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @custom:events Emits NewMarketInterestRateModel event; may emit AccrueInterest + * @custom:error Unauthorized is thrown when the call is not authorized by AccessControlManager + * @custom:access Controlled by AccessControlManager + */ + function setInterestRateModel(InterestRateModel newInterestRateModel) public override { + _checkAccessAllowed("setInterestRateModel(address)"); + + accrueInterest(); + _setInterestRateModelFresh(newInterestRateModel); + } + /** * @notice updates the interest rate model (*requires fresh interest accrual) * @dev Admin function to update the interest rate model @@ -1976,15 +1984,8 @@ contract VToken is * @custom:access Controlled by AccessControlManager */ function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public override { - bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( - msg.sender, - "setStableInterestRateModel(address)" - ); + _checkAccessAllowed("setStableInterestRateModel(address)"); - // Check if caller has call permissions - if (!canCallFunction) { - revert SetStableInterestRateModelOwnerCheck(); - } accrueInterest(); // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. _setStableInterestRateModelFresh(newStableInterestRateModel); @@ -2050,14 +2051,7 @@ contract VToken is * @custom:access Controlled by AccessControlManager */ function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { - bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( - msg.sender, - "setRebalanceUtilizationRateThreshold(uint256)" - ); - // Check caller is allowed to call this function - if (!canCallFunction) { - revert SetRebalanceUtilizationRateThresholdAdminCheck(); - } + _checkAccessAllowed("setRebalanceUtilizationRateThreshold(uint256)"); uint256 oldThreshold = rebalanceUtilizationRateThreshold; rebalanceUtilizationRateThreshold = utilizationRateThreshold; @@ -2071,14 +2065,7 @@ contract VToken is * @custom:access Controlled by AccessControlManager */ function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { - bool canCallFunction = AccessControlManager(accessControlManager).isAllowedToCall( - msg.sender, - "setRebalanceRateFractionThreshold(uint256)" - ); - // Check caller is allowed to call this function - if (!canCallFunction) { - revert SetRebalanceRateFractionThresholdAdminCheck(); - } + _checkAccessAllowed("setRebalanceRateFractionThreshold(uint256)"); uint256 oldThreshold = rebalanceRateFractionThreshold; rebalanceRateFractionThreshold = fractionThreshold; @@ -2280,4 +2267,14 @@ contract VToken is return exchangeRate; } + + /// @notice Reverts if the call is not allowed by AccessControlManager + /// @param signature Method signature + function _checkAccessAllowed(string memory signature) internal view { + bool isAllowedToCall = AccessControlManager(accessControlManager).isAllowedToCall(msg.sender, signature); + + if (!isAllowedToCall) { + revert Unauthorized(msg.sender, address(this), signature); + } + } } diff --git a/scenario/src/ErrorReporterConstants.ts b/scenario/src/ErrorReporterConstants.ts index fa4e13d7e..a02d49062 100644 --- a/scenario/src/ErrorReporterConstants.ts +++ b/scenario/src/ErrorReporterConstants.ts @@ -202,7 +202,6 @@ const TokenErrorReporter = { "error SetComptrollerOwnerCheck()", "error SetPendingAdminOwnerCheck()", - "error SetReserveFactorAdminCheck()", "error SetReserveFactorFreshCheck()", "error SetReserveFactorBoundsCheck()", @@ -213,7 +212,6 @@ const TokenErrorReporter = { "error ReduceReservesCashNotAvailable()", "error ReduceReservesCashValidation()", - "error SetInterestRateModelOwnerCheck()", "error SetInterestRateModelFreshCheck();", ] }; From f007edfdbde02eec65c2f63187b7ec68fe63b834 Mon Sep 17 00:00:00 2001 From: defcon022 Date: Wed, 1 Mar 2023 14:09:45 +0530 Subject: [PATCH 15/23] Refactored functions order. --- contracts/ComptrollerInterface.sol | 4 +- contracts/InterestRate/StableRateModel.sol | 34 +- contracts/VToken.sol | 585 +++++++++++---------- contracts/VTokenInterfaces.sol | 4 +- tests/hardhat/Tokens/accrueInterestTest.ts | 2 +- tests/hardhat/Tokens/borrowAndRepayTest.ts | 12 +- 6 files changed, 328 insertions(+), 313 deletions(-) diff --git a/contracts/ComptrollerInterface.sol b/contracts/ComptrollerInterface.sol index c8e0cc9fa..e5a9778e5 100644 --- a/contracts/ComptrollerInterface.sol +++ b/contracts/ComptrollerInterface.sol @@ -45,9 +45,9 @@ interface ComptrollerInterface { function preTransferHook(address vToken, address src, address dst, uint256 transferTokens) external; - function isComptroller() external view returns (bool); + function preSwapBorrowRateModeHook(address vToken) external; - function preSwapBorrowRateModeHook(address vToken) external virtual; + function isComptroller() external view returns (bool); /*** Liquidity/Liquidation Calculations ***/ diff --git a/contracts/InterestRate/StableRateModel.sol b/contracts/InterestRate/StableRateModel.sol index 9236565d2..e3bad6118 100644 --- a/contracts/InterestRate/StableRateModel.sol +++ b/contracts/InterestRate/StableRateModel.sol @@ -7,8 +7,6 @@ import "hardhat/console.sol"; * @title Logic for Venus stable rate. */ contract StableRateModel { - event NewStableInterestParams(uint256 baseRatePerBlock, uint256 stableRatePremium, uint256 optimalStableLoanRatio); - /// @notice Indicator that this is an InterestRateModel contract (for inspection) bool public constant isInterestRateModel = true; @@ -29,6 +27,8 @@ contract StableRateModel { /// @notice The address of the owner, i.e. the Timelock contract, which can update parameters directly address public owner; + event NewStableInterestParams(uint256 baseRatePerBlock, uint256 stableRatePremium, uint256 optimalStableLoanRatio); + /** * @param baseRatePerYear_ The approximate target base APR, as a mantissa (scaled by BASE) * @param stableRatePremium_ The multiplierPerBlock after hitting a specified utilization point @@ -64,21 +64,6 @@ contract StableRateModel { updateStableRateModelInternal(baseRatePerYear_, stableRatePremium_, optimalStableLoanRatio_); } - /** - * @notice Calculates the ratio of the stable borrows to total borrows - * @param stableBorrows The amount of stable borrows in the market - * @param totalBorrows The amount of total borrows in the market - * @return The stable loan rate as a mantissa between [0, BASE] - */ - function stableLoanRatio(uint256 stableBorrows, uint256 totalBorrows) public pure returns (uint256) { - // Loan ratio is 0 when there are no stable borrows - if (totalBorrows == 0) { - return 0; - } - - return (stableBorrows * BASE) / totalBorrows; - } - /** * @notice Calculates the current borrow rate per block, with the error code expected by the market * @param stableBorrows The amount of stable borrows in the market @@ -97,6 +82,21 @@ contract StableRateModel { return (variableBorrowRate + baseRatePerBlock + ((stableRatePremium * excessLoanRatio) / BASE)); } + /** + * @notice Calculates the ratio of the stable borrows to total borrows + * @param stableBorrows The amount of stable borrows in the market + * @param totalBorrows The amount of total borrows in the market + * @return The stable loan rate as a mantissa between [0, BASE] + */ + function stableLoanRatio(uint256 stableBorrows, uint256 totalBorrows) public pure returns (uint256) { + // Loan ratio is 0 when there are no stable borrows + if (totalBorrows == 0) { + return 0; + } + + return (stableBorrows * BASE) / totalBorrows; + } + /** * @notice Internal function to update the parameters of the interest rate model * @param baseRatePerYear_ The approximate target base APR, as a mantissa (scaled by BASE) diff --git a/contracts/VToken.sol b/contracts/VToken.sol index f68554c8b..a2fa158a7 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -640,6 +640,102 @@ contract VToken is emit SweepToken(address(token)); } + /** + * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa with specific amount + * @param rateMode The rate mode that the user wants to swap to + * @param sentAmount The amount that the user wants to convert form stable to variable mode or vice versa. + * @custom:access Not restricted + **/ + function swapBorrowRateModeWithAmount(uint256 rateMode, uint256 sentAmount) external { + address account = msg.sender; + (uint256 variableDebt, uint256 stableDebt) = _swapBorrowRateModePreCalculation(account); + + uint256 stableBorrowsNew; + uint256 averageStableBorrowRateNew; + uint256 swappedAmount; + + if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { + require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); + + swappedAmount = sentAmount > variableDebt ? variableDebt : sentAmount; + uint256 accountBorrowsNew = stableDebt + swappedAmount; + + (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForStableRateSwap( + swappedAmount, + stableDebt, + variableDebt, + account, + accountBorrowsNew + ); + } else { + require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); + + swappedAmount = sentAmount > stableDebt ? stableDebt : sentAmount; + + (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForVariableRateSwap( + swappedAmount, + stableDebt, + variableDebt, + account + ); + } + + stableBorrows = stableBorrowsNew; + averageStableBorrowRate = averageStableBorrowRateNew; + + emit SwapBorrowRateMode(account, rateMode, swappedAmount); + } + + /** + * @notice Rebalances the stable interest rate of a user to the current stable borrow rate. + * - Users can be rebalanced if the following conditions are satisfied: + * 1. Utilization rate is above rebalanceUtilizationRateThreshold. + * 2. Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. + * @param account The address of the account to be rebalanced + * @custom:events RebalancedStableBorrowRate - Emits after rebalancing the stable borrow rate for the user. + **/ + function rebalanceStableBorrowRate(address account) external { + accrueInterest(); + + validateRebalanceStableBorrowRate(); + _updateUserStableBorrowBalance(account); + + uint256 stableBorrowRate = stableBorrowRatePerBlock(); + + uint256 previousStableRateMantissa = accountStableBorrows[account].stableRateMantissa; + accountStableBorrows[account].stableRateMantissa = stableBorrowRate; + + emit RebalancedStableBorrowRate(account, previousStableRateMantissa, stableBorrowRate); + } + + /** + * @notice Sets the utilization threshold for stable rate rebalancing + * @param utilizationRateThreshold The utilization rate threshold + * @custom:access Controlled by AccessControlManager + */ + function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { + _checkAccessAllowed("setRebalanceUtilizationRateThreshold(uint256)"); + + uint256 oldThreshold = rebalanceUtilizationRateThreshold; + rebalanceUtilizationRateThreshold = utilizationRateThreshold; + + emit RebalanceUtilizationRateThresholdUpdated(oldThreshold, rebalanceUtilizationRateThreshold); + } + + /** + * @notice Sets the fraction threshold for stable rate rebalancing + * @param fractionThreshold The fraction threshold for the validation of the stable rate rebalancing + * @custom:access Controlled by AccessControlManager + */ + function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { + _checkAccessAllowed("setRebalanceRateFractionThreshold(uint256)"); + + uint256 oldThreshold = rebalanceRateFractionThreshold; + rebalanceRateFractionThreshold = fractionThreshold; + + emit RebalanceRateFractionThresholdUpdated(oldThreshold, rebalanceRateFractionThreshold); + } + /** * @notice Get the current allowance from `owner` for `spender` * @param owner The address of the account which owns the tokens to be spent @@ -700,17 +796,6 @@ contract VToken is return interestRateModel.getBorrowRate(utilizationRate(_getCashPrior(), totalBorrows, totalReserves)); } - /** - * @notice Returns the current per-block borrow interest rate for this vToken - * @return rate The borrow interest rate per block, scaled by 1e18 - */ - function stableBorrowRatePerBlock() public view override returns (uint256) { - uint256 variableBorrowRate = interestRateModel.getBorrowRate( - utilizationRate(_getCashPrior(), totalBorrows, totalReserves) - ); - return stableRateModel.getBorrowRate(stableBorrows, totalBorrows, variableBorrowRate); - } - /** * @notice Returns the current per-block supply interest rate for this v * @return rate The supply interest rate per block, scaled by 1e18 @@ -725,18 +810,10 @@ contract VToken is (averageMarketBorrowRate * utilRate * (mantissaOne - reserveFactorMantissa)) / (mantissaOne * mantissaOne); } - /// @notice Calculate the average market borrow rate with respect to variable and stable borrows - function _averageMarketBorrowRate() internal view returns (uint256) { - uint256 variableBorrowRate = interestRateModel.getBorrowRate( - utilizationRate(_getCashPrior(), totalBorrows, totalReserves) - ); - uint256 variableBorrows = totalBorrows - stableBorrows; - return ((variableBorrows * variableBorrowRate) + (stableBorrows * averageStableBorrowRate)) / totalBorrows; - } - /** - * @notice Returns the current total borrows plus accrued interest - * @return totalBorrows The total borrows with interest + * @notice Calculates the exchange rate from the underlying to the VToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return exchangeRate Calculated exchange rate scaled by 1e18 */ function totalBorrowsCurrent() external override nonReentrant returns (uint256) { accrueInterest(); @@ -788,91 +865,6 @@ contract VToken is return principalTimesIndex / borrowSnapshot.interestIndex; } - /** - * @notice Return the borrow balance of account based on stored data - * @param account The address whose balance should be calculated - * @return borrowBalance the calculated balance - */ - function _stableBorrowBalanceStored(address account) - internal - view - returns ( - uint256, - uint256, - Exp memory - ) - { - /* Get borrowBalance and borrowIndex */ - StableBorrowSnapshot storage borrowSnapshot = accountStableBorrows[account]; - Exp memory simpleStableInterestFactor; - - /* If borrowBalance = 0 then borrowIndex is likely also 0. - * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. - */ - if (borrowSnapshot.principal == 0) { - return (0, borrowSnapshot.interestIndex, simpleStableInterestFactor); - } - - uint256 currentBlockNumber = _getBlockNumber(); - - /* Short-circuit accumulating 0 interest */ - if (borrowSnapshot.lastBlockAccrued == currentBlockNumber) { - return (borrowSnapshot.principal, borrowSnapshot.interestIndex, simpleStableInterestFactor); - } - - /* Calculate the number of blocks elapsed since the last accrual */ - uint256 blockDelta = currentBlockNumber - borrowSnapshot.lastBlockAccrued; - - simpleStableInterestFactor = mul_(Exp({ mantissa: borrowSnapshot.stableRateMantissa }), blockDelta); - - uint256 stableBorrowIndexNew = mul_ScalarTruncateAddUInt( - simpleStableInterestFactor, - borrowSnapshot.interestIndex, - borrowSnapshot.interestIndex - ); - uint256 principalUpdated = (borrowSnapshot.principal * stableBorrowIndexNew) / borrowSnapshot.interestIndex; - - return (principalUpdated, stableBorrowIndexNew, simpleStableInterestFactor); - } - - /** - * @notice Return the stable borrow balance of account based on stored data - * @param account The address whose balance should be calculated - * @return Stable borrowBalance the calculated balance - * @custom:events UpdatedUserStableBorrowBalance event emitted after updating account's borrow - */ - function _updateUserStableBorrowBalance(address account) internal returns (uint256) { - StableBorrowSnapshot storage borrowSnapshot = accountStableBorrows[account]; - - Exp memory simpleStableInterestFactor; - uint256 principalUpdated; - uint256 stableBorrowIndexNew; - - (principalUpdated, stableBorrowIndexNew, simpleStableInterestFactor) = _stableBorrowBalanceStored(account); - uint256 stableBorrowsPrior = stableBorrows; - uint256 totalBorrowsPrior = totalBorrows; - uint256 totalReservesPrior = totalReserves; - - uint256 stableInterestAccumulated = mul_ScalarTruncate(simpleStableInterestFactor, borrowSnapshot.principal); - uint256 stableBorrowsUpdated = stableBorrowsPrior + stableInterestAccumulated; - uint256 totalBorrowsUpdated = totalBorrowsPrior + stableInterestAccumulated; - uint256 totalReservesUpdated = mul_ScalarTruncateAddUInt( - Exp({ mantissa: reserveFactorMantissa }), - stableInterestAccumulated, - totalReservesPrior - ); - - stableBorrows = stableBorrowsUpdated; - totalBorrows = totalBorrowsUpdated; - totalReserves = totalReservesUpdated; - borrowSnapshot.interestIndex = stableBorrowIndexNew; - borrowSnapshot.principal = principalUpdated; - borrowSnapshot.lastBlockAccrued = _getBlockNumber(); - - emit UpdatedUserStableBorrowBalance(account, principalUpdated); - return principalUpdated; - } - /** * @notice Accrue interest then return the up-to-date exchange rate * @return exchangeRate Calculated exchange rate scaled by 1e18 @@ -968,10 +960,141 @@ contract VToken is return err; } - /* We emit an AccrueInterest event */ - emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew, stableBorrowIndex); + /* We emit an AccrueInterest event */ + emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew, stableBorrowIndex); + + return NO_ERROR; + } + + /** + * @notice Sets a new comptroller for the market + * @dev Admin function to set a new comptroller + * @custom:events Emits NewComptroller event + * @custom:error SetComptrollerOwnerCheck is thrown when the call is not from owner + * @custom:access Only Governance + */ + function setComptroller(ComptrollerInterface newComptroller) public override { + // Check caller is admin + if (msg.sender != owner()) { + revert SetComptrollerOwnerCheck(); + } + + _setComptroller(newComptroller); + } + + /** + * @notice Accrues interest and updates the stable interest rate model using _setStableInterestRateModelFresh + * @dev Admin function to accrue interest and update the stable interest rate model + * @param newStableInterestRateModel The new interest rate model to use + * @custom:events Emits NewMarketInterestRateModel event; may emit AccrueInterest + * @custom:events Emits NewMarketStableInterestRateModel, after setting the new stable rate model + * @custom:access Controlled by AccessControlManager + */ + function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public override { + _checkAccessAllowed("setStableInterestRateModel(address)"); + + accrueInterest(); + // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. + _setStableInterestRateModelFresh(newStableInterestRateModel); + } + + /// Validate the conditions to rebalance the stable borrow rate. + function validateRebalanceStableBorrowRate() public view { + require(rebalanceUtilizationRateThreshold > 0, "vToken: rebalanceUtilizationRateThreshold is not set."); + require(rebalanceRateFractionThreshold > 0, "vToken: rebalanceRateFractionThreshold is not set."); + + uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + + /// Utilization rate is above rebalanceUtilizationRateThreshold. + require(utilRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); + + uint256 variableBorrowRate = interestRateModel.getBorrowRate(rebalanceUtilizationRateThreshold); + + /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of + /// variable borrow rate when utilization rate is rebalanceUtilizationRateThreshold + require( + _averageMarketBorrowRate() < (variableBorrowRate * rebalanceRateFractionThreshold), + "vToken: average borrow rate higher than variable rate threshold." + ); + } + + /** + * @notice Returns the current per-block borrow interest rate for this vToken + * @return rate The borrow interest rate per block, scaled by 1e18 + */ + function stableBorrowRatePerBlock() public view override returns (uint256) { + uint256 variableBorrowRate = interestRateModel.getBorrowRate( + utilizationRate(_getCashPrior(), totalBorrows, totalReserves) + ); + return stableRateModel.getBorrowRate(stableBorrows, totalBorrows, variableBorrowRate); + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return borrowBalance The calculated balance + */ + function borrowBalanceStored(address account) public view override returns (uint256) { + (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); + return _borrowBalanceStored(account) + stableBorrowAmount; + } + + /** + * @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - reserves)` + * @param cash The amount of cash in the market + * @param borrows The amount of borrows in the market + * @param reserves The amount of reserves in the market (currently unused) + * @return The utilization rate as a mantissa between [0, BASE] + */ + function utilizationRate( + uint256 cash, + uint256 borrows, + uint256 reserves + ) public pure override returns (uint256) { + // Utilization rate is 0 when there are no borrows + if (borrows == 0) { + return 0; + } + + return (borrows * mantissaOne) / (cash + borrows - reserves); + } + + /** + * @notice Return the stable borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return Stable borrowBalance the calculated balance + * @custom:events UpdatedUserStableBorrowBalance event emitted after updating account's borrow + */ + function _updateUserStableBorrowBalance(address account) internal returns (uint256) { + StableBorrowSnapshot storage borrowSnapshot = accountStableBorrows[account]; + + Exp memory simpleStableInterestFactor; + uint256 principalUpdated; + uint256 stableBorrowIndexNew; + + (principalUpdated, stableBorrowIndexNew, simpleStableInterestFactor) = _stableBorrowBalanceStored(account); + uint256 stableBorrowsPrior = stableBorrows; + uint256 totalBorrowsPrior = totalBorrows; + uint256 totalReservesPrior = totalReserves; + + uint256 stableInterestAccumulated = mul_ScalarTruncate(simpleStableInterestFactor, borrowSnapshot.principal); + uint256 stableBorrowsUpdated = stableBorrowsPrior + stableInterestAccumulated; + uint256 totalBorrowsUpdated = totalBorrowsPrior + stableInterestAccumulated; + uint256 totalReservesUpdated = mul_ScalarTruncateAddUInt( + Exp({ mantissa: reserveFactorMantissa }), + stableInterestAccumulated, + totalReservesPrior + ); + + stableBorrows = stableBorrowsUpdated; + totalBorrows = totalBorrowsUpdated; + totalReserves = totalReservesUpdated; + borrowSnapshot.interestIndex = stableBorrowIndexNew; + borrowSnapshot.principal = principalUpdated; + borrowSnapshot.lastBlockAccrued = _getBlockNumber(); - return NO_ERROR; + emit UpdatedUserStableBorrowBalance(account, principalUpdated); + return principalUpdated; } /** @@ -1511,52 +1634,6 @@ contract VToken is return (stableBorrowsNew, averageStableBorrowRateNew); } - /** - * @dev Allows a borrower to swap his debt between stable and variable mode, or vice versa with specific amount - * @param rateMode The rate mode that the user wants to swap to - * @param sentAmount The amount that the user wants to convert form stable to variable mode or vice versa. - * @custom:access Not restricted - **/ - function swapBorrowRateModeWithAmount(uint256 rateMode, uint256 sentAmount) external { - address account = msg.sender; - (uint256 variableDebt, uint256 stableDebt) = _swapBorrowRateModePreCalculation(account); - - uint256 stableBorrowsNew; - uint256 averageStableBorrowRateNew; - uint256 swappedAmount; - - if (InterestRateMode(rateMode) == InterestRateMode.STABLE) { - require(variableDebt > 0, "vToken: swapBorrowRateMode variable debt is 0"); - - swappedAmount = sentAmount > variableDebt ? variableDebt : sentAmount; - uint256 accountBorrowsNew = stableDebt + swappedAmount; - - (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForStableRateSwap( - swappedAmount, - stableDebt, - variableDebt, - account, - accountBorrowsNew - ); - } else { - require(stableDebt > 0, "vToken: swapBorrowRateMode stable debt is 0"); - - swappedAmount = sentAmount > stableDebt ? stableDebt : sentAmount; - - (stableBorrowsNew, averageStableBorrowRateNew) = _updateStatesForVariableRateSwap( - swappedAmount, - stableDebt, - variableDebt, - account - ); - } - - stableBorrows = stableBorrowsNew; - averageStableBorrowRate = averageStableBorrowRateNew; - - emit SwapBorrowRateMode(account, rateMode, swappedAmount); - } - /** * @notice The sender liquidates the borrowers collateral. * The collateral seized is transferred to the liquidator. @@ -1716,86 +1793,6 @@ contract VToken is emit ReservesAdded(address(this), protocolSeizeAmount, totalReservesNew); } - /** - * @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - reserves)` - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market (currently unused) - * @return The utilization rate as a mantissa between [0, BASE] - */ - function utilizationRate( - uint256 cash, - uint256 borrows, - uint256 reserves - ) public pure override returns (uint256) { - // Utilization rate is 0 when there are no borrows - if (borrows == 0) { - return 0; - } - - return (borrows * mantissaOne) / (cash + borrows - reserves); - } - - /** - * @notice Rebalances the stable interest rate of a user to the current stable borrow rate. - * - Users can be rebalanced if the following conditions are satisfied: - * 1. Utilization rate is above rebalanceUtilizationRateThreshold. - * 2. Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of variable borrow rate. - * @param account The address of the account to be rebalanced - * @custom:events RebalancedStableBorrowRate - Emits after rebalancing the stable borrow rate for the user. - **/ - function rebalanceStableBorrowRate(address account) external { - accrueInterest(); - - validateRebalanceStableBorrowRate(); - _updateUserStableBorrowBalance(account); - - uint256 stableBorrowRate = stableBorrowRatePerBlock(); - - uint256 previousStableRateMantissa = accountStableBorrows[account].stableRateMantissa; - accountStableBorrows[account].stableRateMantissa = stableBorrowRate; - - emit RebalancedStableBorrowRate(account, previousStableRateMantissa, stableBorrowRate); - } - - /// Validate the conditions to rebalance the stable borrow rate. - function validateRebalanceStableBorrowRate() public view { - require(rebalanceUtilizationRateThreshold > 0, "vToken: rebalanceUtilizationRateThreshold is not set."); - require(rebalanceRateFractionThreshold > 0, "vToken: rebalanceRateFractionThreshold is not set."); - - uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves); - - /// Utilization rate is above rebalanceUtilizationRateThreshold. - require(utilRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); - - uint256 variableBorrowRate = interestRateModel.getBorrowRate(rebalanceUtilizationRateThreshold); - - /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of - /// variable borrow rate when utilization rate is rebalanceUtilizationRateThreshold - require( - _averageMarketBorrowRate() < (variableBorrowRate * rebalanceRateFractionThreshold), - "vToken: average borrow rate higher than variable rate threshold." - ); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new comptroller for the market - * @dev Admin function to set a new comptroller - * @custom:events Emits NewComptroller event - * @custom:error SetComptrollerOwnerCheck is thrown when the call is not from owner - * @custom:access Only Governance - */ - function setComptroller(ComptrollerInterface newComptroller) public override { - // Check caller is admin - if (msg.sender != owner()) { - revert SetComptrollerOwnerCheck(); - } - - _setComptroller(newComptroller); - } - function _setComptroller(ComptrollerInterface newComptroller) internal { ComptrollerInterface oldComptroller = comptroller; // Ensure invoke comptroller.isComptroller() returns true @@ -1975,22 +1972,6 @@ contract VToken is /*** Safe Token ***/ - /** - * @notice Accrues interest and updates the stable interest rate model using _setStableInterestRateModelFresh - * @dev Admin function to accrue interest and update the stable interest rate model - * @param newStableInterestRateModel The new interest rate model to use - * @custom:events Emits NewMarketInterestRateModel event; may emit AccrueInterest - * @custom:events Emits NewMarketStableInterestRateModel, after setting the new stable rate model - * @custom:access Controlled by AccessControlManager - */ - function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public override { - _checkAccessAllowed("setStableInterestRateModel(address)"); - - accrueInterest(); - // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. - _setStableInterestRateModelFresh(newStableInterestRateModel); - } - /** * @notice Updates the stable interest rate model (requires fresh interest accrual) * @dev Admin function to update the stable interest rate model @@ -2045,35 +2026,7 @@ contract VToken is token.safeTransfer(to, amount); } - /** - * @notice Sets the utilization threshold for stable rate rebalancing - * @param utilizationRateThreshold The utilization rate threshold - * @custom:access Controlled by AccessControlManager - */ - function setRebalanceUtilizationRateThreshold(uint256 utilizationRateThreshold) external { - _checkAccessAllowed("setRebalanceUtilizationRateThreshold(uint256)"); - - uint256 oldThreshold = rebalanceUtilizationRateThreshold; - rebalanceUtilizationRateThreshold = utilizationRateThreshold; - - emit RebalanceUtilizationRateThresholdUpdated(oldThreshold, rebalanceUtilizationRateThreshold); - } - - /** - * @notice Sets the fraction threshold for stable rate rebalancing - * @param fractionThreshold The fraction threshold for the validation of the stable rate rebalancing - * @custom:access Controlled by AccessControlManager - */ - function setRebalanceRateFractionThreshold(uint256 fractionThreshold) external { - _checkAccessAllowed("setRebalanceRateFractionThreshold(uint256)"); - - uint256 oldThreshold = rebalanceRateFractionThreshold; - rebalanceRateFractionThreshold = fractionThreshold; - - emit RebalanceRateFractionThresholdUpdated(oldThreshold, rebalanceRateFractionThreshold); - } - - /*** Reentrancy Guard ***/ + /*** Safe Token ***/ /** * @notice Transfer `tokens` tokens from `src` to `dst` by `spender` @@ -2277,4 +2230,74 @@ contract VToken is revert Unauthorized(msg.sender, address(this), signature); } } + + /** + * @notice Reverts if the call is not allowed by AccessControlManager + * @param signature Method signature + */ + function _checkAccessAllowed(string memory signature) internal view { + bool isAllowedToCall = AccessControlManager(accessControlManager).isAllowedToCall(msg.sender, signature); + + if (!isAllowedToCall) { + revert Unauthorized(msg.sender, address(this), signature); + } + } + + /** + * @notice Calculate the average market borrow rate with respect to variable and stable borrows + */ + function _averageMarketBorrowRate() internal view returns (uint256) { + uint256 variableBorrowRate = interestRateModel.getBorrowRate( + utilizationRate(_getCashPrior(), totalBorrows, totalReserves) + ); + uint256 variableBorrows = totalBorrows - stableBorrows; + return ((variableBorrows * variableBorrowRate) + (stableBorrows * averageStableBorrowRate)) / totalBorrows; + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return borrowBalance the calculated balance + */ + function _stableBorrowBalanceStored(address account) + internal + view + returns ( + uint256, + uint256, + Exp memory + ) + { + /* Get borrowBalance and borrowIndex */ + StableBorrowSnapshot storage borrowSnapshot = accountStableBorrows[account]; + Exp memory simpleStableInterestFactor; + + /* If borrowBalance = 0 then borrowIndex is likely also 0. + * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. + */ + if (borrowSnapshot.principal == 0) { + return (0, borrowSnapshot.interestIndex, simpleStableInterestFactor); + } + + uint256 currentBlockNumber = _getBlockNumber(); + + /* Short-circuit accumulating 0 interest */ + if (borrowSnapshot.lastBlockAccrued == currentBlockNumber) { + return (borrowSnapshot.principal, borrowSnapshot.interestIndex, simpleStableInterestFactor); + } + + /* Calculate the number of blocks elapsed since the last accrual */ + uint256 blockDelta = currentBlockNumber - borrowSnapshot.lastBlockAccrued; + + simpleStableInterestFactor = mul_(Exp({ mantissa: borrowSnapshot.stableRateMantissa }), blockDelta); + + uint256 stableBorrowIndexNew = mul_ScalarTruncateAddUInt( + simpleStableInterestFactor, + borrowSnapshot.interestIndex, + borrowSnapshot.interestIndex + ); + uint256 principalUpdated = (borrowSnapshot.principal * stableBorrowIndexNew) / borrowSnapshot.interestIndex; + + return (principalUpdated, stableBorrowIndexNew, simpleStableInterestFactor); + } } diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index bd9b30b05..5f4c9535a 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -442,6 +442,8 @@ abstract contract VTokenInterface is VTokenStorage { function decreaseAllowance(address spender, uint256 subtractedValue) external virtual returns (bool); + function setComptroller(ComptrollerInterface newComptroller) external virtual; + function allowance(address owner, address spender) external view virtual returns (uint256); function balanceOf(address owner) external view virtual returns (uint256); @@ -467,6 +469,4 @@ abstract contract VTokenInterface is VTokenStorage { } function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public virtual; - - function addReserves(uint256 addAmount) external virtual; } diff --git a/tests/hardhat/Tokens/accrueInterestTest.ts b/tests/hardhat/Tokens/accrueInterestTest.ts index fa28950e2..7c165cc19 100644 --- a/tests/hardhat/Tokens/accrueInterestTest.ts +++ b/tests/hardhat/Tokens/accrueInterestTest.ts @@ -81,7 +81,7 @@ describe("VToken", () => { it("fails if new borrow rate calculation fails", async () => { await pretendBlock(vToken, blockNumber, 1); stableInterestRateModel.getBorrowRate.reverts("Oups"); - await expect(vToken.accrueInterest()).to.be.reverted; //With("INTEREST_RATE_MODEL_ERROR"); + await expect(vToken.accrueInterest()).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); }); it("fails if simple interest factor calculation fails", async () => { diff --git a/tests/hardhat/Tokens/borrowAndRepayTest.ts b/tests/hardhat/Tokens/borrowAndRepayTest.ts index d33d3129a..99d3ac254 100644 --- a/tests/hardhat/Tokens/borrowAndRepayTest.ts +++ b/tests/hardhat/Tokens/borrowAndRepayTest.ts @@ -499,9 +499,7 @@ describe("VToken", function () { it("stores new borrow principal and interest index", async () => { const beforeProtocolBorrows = await vToken.totalBorrows(); const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); - //expect( await repayBorrowStableFresh(vToken, payer, borrower, repayAmount); - //).toSucceed(); const afterAccountBorrows = await vToken.harnessAccountStableBorrows(borrowerAddress); expect(afterAccountBorrows.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); expect(afterAccountBorrows.interestIndex).to.equal(convertToUnit("1", 18)); @@ -559,7 +557,7 @@ describe("VToken", function () { it("emits a repay borrow failure if interest accrual fails", async () => { interestRateModel.getBorrowRate.reverts("Oups"); - await expect(repayBorrowStable(vToken, borrower, repayAmount)).to.be.reverted; //With("INTEREST_RATE_MODEL_ERROR"); + await expect(repayBorrowStable(vToken, borrower, repayAmount)).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); }); it("returns error from repayBorrowFresh without emitting any extra logs", async () => { @@ -570,18 +568,14 @@ describe("VToken", function () { it("returns success from repayBorrowFresh and repays the right amount", async () => { await vToken.harnessFastForward(5); const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); - //expect( await repayBorrowStable(vToken, borrower, repayAmount); - //).toSucceed(); const afterAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); expect(afterAccountBorrowSnap.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); }); it("repays the full amount owed if payer has enough", async () => { await vToken.harnessFastForward(5); - //expect( await repayBorrowStable(vToken, borrower, constants.MaxUint256); - //).toSucceed(); const afterAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); expect(afterAccountBorrowSnap.principal).to.equal(0); }); @@ -740,7 +734,7 @@ describe("VToken", function () { it("emits a repay borrow failure if interest accrual fails", async () => { interestRateModel.getBorrowRate.reverts("Oups"); - await expect(repayBorrowStableBehalf(vToken, payer, borrower, repayAmount)).to.be.reverted; //With("INTEREST_RATE_MODEL_ERROR"); + await expect(repayBorrowStableBehalf(vToken, payer, borrower, repayAmount)).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); }); it("returns error from repayBorrowFresh without emitting any extra logs", async () => { @@ -753,9 +747,7 @@ describe("VToken", function () { it("returns success from repayBorrowFresh and repays the right amount", async () => { await vToken.harnessFastForward(5); const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); - //expect( await repayBorrowStableBehalf(vToken, payer, borrower, repayAmount); - //).toSucceed(); const afterAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); expect(afterAccountBorrowSnap.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); }); From 21706c5bb7ab3d54bdf75585554621a6053e948f Mon Sep 17 00:00:00 2001 From: defcon022 Date: Wed, 1 Mar 2023 17:18:33 +0530 Subject: [PATCH 16/23] Fix: deployment script. --- helpers/deploymentConfig.ts | 55 ++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/helpers/deploymentConfig.ts b/helpers/deploymentConfig.ts index 63c80a843..d99e006f7 100644 --- a/helpers/deploymentConfig.ts +++ b/helpers/deploymentConfig.ts @@ -65,14 +65,16 @@ export type VTokenConfig = { initialSupply: string; supplyCap: string; borrowCap: string; - vTokenReceiver: string; + baseRatePerBlockForStable: number; + stableRatePremium: string; + optimalStableLoanRatio: string; }; export type AccessControlEntry = { caller: string; target: string; method: string; -}; +} export enum InterestRateModels { WhitePaper, @@ -300,9 +302,11 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.7, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(932019, 18), - borrowCap: convertToUnit(478980, 18), - vTokenReceiver: "account:deployer", + supplyCap: convertToUnit(10000, 18), + borrowCap: convertToUnit(10000, 18), + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }, { name: "Venus BTCB", @@ -317,9 +321,11 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.8, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(1000, 18), - borrowCap: convertToUnit(1000, 18), - vTokenReceiver: "account:deployer", + supplyCap: convertToUnit(10000, 18), + borrowCap: convertToUnit(10000, 18), + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }, ], rewards: [ @@ -374,9 +380,11 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.7, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(100, 18), - borrowCap: convertToUnit(100, 18), - vTokenReceiver: "account:deployer", + supplyCap: convertToUnit(10000, 18), + borrowCap: convertToUnit(10000, 18), + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }, { name: "Venus MBOX", @@ -442,26 +450,11 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.7, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(1963, 18), - borrowCap: convertToUnit(324, 18), - vTokenReceiver: "account:deployer", - }, - { - name: "Venus USDD", - asset: "USDD", - symbol: "vUSDD", - rateModel: InterestRateModels.JumpRate.toString(), - baseRatePerYear: "0", - multiplierPerYear: convertToUnit(0.15, 18), - jumpMultiplierPerYear: convertToUnit(3, 18), - kink_: convertToUnit(0.6, 18), - collateralFactor: convertToUnit(0.7, 18), - liquidationThreshold: convertToUnit(0.8, 18), - reserveFactor: convertToUnit(0.1, 18), - initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(10601805, 18), - borrowCap: convertToUnit(1698253, 18), - vTokenReceiver: "account:deployer", + supplyCap: convertToUnit(10000, 18), + borrowCap: convertToUnit(10000, 18), + baseRatePerBlockForStable: 0, + stableRatePremium: convertToUnit(2, 12), + optimalStableLoanRatio: convertToUnit(5, 17), }, ], rewards: [ From 2eff72848ff9c938c4d6e3eebf8f8c66ffd0aa70 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 3 Oct 2023 23:54:56 +0530 Subject: [PATCH 17/23] fix: merge conflicts in contracts --- contracts/BaseJumpRateModelV2.sol | 90 +---- contracts/ComptrollerStorage.sol | 3 +- contracts/ErrorReporter.sol | 2 - contracts/InterestRate/StableRateModel.sol | 7 +- contracts/InterestRateModel.sol | 17 - contracts/JumpRateModelV2.sol | 2 +- contracts/Pool/PoolRegistry.sol | 4 - contracts/VToken.sol | 379 +++--------------- contracts/VTokenInterfaces.sol | 30 +- contracts/WhitePaperInterestRateModel.sol | 28 -- contracts/test/ComptrollerHarness.sol | 2 - contracts/test/UpgradedVToken.sol | 33 +- contracts/test/VTokenHarness.sol | 139 +------ .../contracts/InterestRateModelModel.sol | 15 +- 14 files changed, 110 insertions(+), 641 deletions(-) diff --git a/contracts/BaseJumpRateModelV2.sol b/contracts/BaseJumpRateModelV2.sol index 97c5adac8..feda88c31 100644 --- a/contracts/BaseJumpRateModelV2.sol +++ b/contracts/BaseJumpRateModelV2.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.13; import { IAccessControlManagerV8 } from "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol"; import { InterestRateModel } from "./InterestRateModel.sol"; -import { BLOCKS_PER_YEAR, EXP_SCALE, MANTISSA_ONE } from "./lib/constants.sol"; +import { BLOCKS_PER_YEAR, EXP_SCALE } from "./lib/constants.sol"; /** * @title Logic for Compound's JumpRateModel Contract V2. @@ -93,60 +93,8 @@ abstract contract BaseJumpRateModelV2 is InterestRateModel { if (!isAllowedToCall) { revert Unauthorized(msg.sender, address(this), signature); } - - updateJumpRateModelInternal(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_); - } - - /** - * @notice Calculates the current supply rate per block - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market - * @param reserveFactorMantissa The current reserve factor for the market - * @param badDebt The amount of badDebt in the market - * @return The supply rate percentage per block as a mantissa (scaled by EXP_SCALE) - */ - function getSupplyRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 reserveFactorMantissa, - uint256 badDebt - ) public view virtual override returns (uint256) { - uint256 oneMinusReserveFactor = MANTISSA_ONE - reserveFactorMantissa; - uint256 borrowRate = _getBorrowRate(cash, borrows, reserves, badDebt); - uint256 rateToPool = (borrowRate * oneMinusReserveFactor) / EXP_SCALE; - uint256 incomeToDistribute = borrows * rateToPool; - uint256 supply = cash + borrows + badDebt - reserves; - return incomeToDistribute / supply; - } - - /** - * @notice Calculates the utilization rate of the market: `(borrows + badDebt) / (cash + borrows + badDebt - reserves)` - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market (currently unused) - * @param badDebt The amount of badDebt in the market - * @return The utilization rate as a mantissa between [0, MANTISSA_ONE] - */ - function utilizationRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 badDebt - ) public pure returns (uint256) { - // Utilization rate is 0 when there are no borrows and badDebt - if ((borrows + badDebt) == 0) { - return 0; - } - uint256 rate = ((borrows + badDebt) * EXP_SCALE) / (cash + borrows + badDebt - reserves); - - if (rate > EXP_SCALE) { - rate = EXP_SCALE; - } - - return rate; + _updateJumpRateModel(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_); } /** @@ -172,29 +120,19 @@ abstract contract BaseJumpRateModelV2 is InterestRateModel { /** * @notice Calculates the current borrow rate per block, with the error code expected by the market - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market - * @param badDebt The amount of badDebt in the market - * @return The borrow rate percentage per block as a mantissa (scaled by EXP_SCALE) + * @param utRate The utilization rate per total borrows and cash available + * @return The borrow rate percentage per block as a mantissa (scaled by BASE) */ - function _getBorrowRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 badDebt - ) internal view returns (uint256) { - uint256 util = utilizationRate(cash, borrows, reserves, badDebt); - uint256 kink_ = kink; - - if (util <= kink_) { - return ((util * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock; - } - uint256 normalRate = ((kink_ * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock; - uint256 excessUtil; - unchecked { - excessUtil = util - kink_; + function _getBorrowRate(uint256 utRate) internal view returns (uint256) { + if (utRate <= kink) { + return ((utRate * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock; + } else { + uint256 normalRate = ((kink * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock; + uint256 excessUtil; + unchecked { + excessUtil = utRate - kink; + } + return ((excessUtil * jumpMultiplierPerBlock) / EXP_SCALE) + normalRate; } - return ((excessUtil * jumpMultiplierPerBlock) / EXP_SCALE) + normalRate; } } diff --git a/contracts/ComptrollerStorage.sol b/contracts/ComptrollerStorage.sol index b710ca4a4..ca02e2721 100644 --- a/contracts/ComptrollerStorage.sol +++ b/contracts/ComptrollerStorage.sol @@ -57,7 +57,8 @@ contract ComptrollerStorage { LIQUIDATE, TRANSFER, ENTER_MARKET, - EXIT_MARKET + EXIT_MARKET, + SWAP_RATE_MODE } /** diff --git a/contracts/ErrorReporter.sol b/contracts/ErrorReporter.sol index 52e9d8b73..53b7706c3 100644 --- a/contracts/ErrorReporter.sol +++ b/contracts/ErrorReporter.sol @@ -51,6 +51,4 @@ contract TokenErrorReporter { error SwapBorrowRateModeFreshnessCheck(); error SetRebalanceUtilizationRateThresholdAdminCheck(); - - error Unauthorized(address sender, address calledContract, string methodSignature); } diff --git a/contracts/InterestRate/StableRateModel.sol b/contracts/InterestRate/StableRateModel.sol index e3bad6118..c614487c6 100644 --- a/contracts/InterestRate/StableRateModel.sol +++ b/contracts/InterestRate/StableRateModel.sol @@ -35,12 +35,7 @@ contract StableRateModel { * @param optimalStableLoanRatio_ Optimal stable loan rate percentage. * @param owner_ Address of the owner for this model(Governance) */ - constructor( - uint256 baseRatePerYear_, - uint256 stableRatePremium_, - uint256 optimalStableLoanRatio_, - address owner_ - ) { + constructor(uint256 baseRatePerYear_, uint256 stableRatePremium_, uint256 optimalStableLoanRatio_, address owner_) { owner = owner_; updateStableRateModelInternal(baseRatePerYear_, stableRatePremium_, optimalStableLoanRatio_); diff --git a/contracts/InterestRateModel.sol b/contracts/InterestRateModel.sol index 1a1ea2d2b..42c317130 100644 --- a/contracts/InterestRateModel.sol +++ b/contracts/InterestRateModel.sol @@ -6,23 +6,6 @@ pragma solidity 0.8.13; * @author Compound */ abstract contract InterestRateModel { - /** - * @notice Calculates the utilization rate for the market - * @param cash The total amount of cash the market has - * @param borrows The total amount of borrows the market has outstanding - * @param reserves The total amount of reserves the market has - * @param reserveFactorMantissa The current reserve factor the market has - * @param badDebt The amount of badDebt in the market - * @return The supply rate per block (as a percentage, and scaled by 1e18) - */ - function utilizationRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 reserveFactorMantissa, - uint256 badDebt - ) external view virtual returns (uint256); - /** * @notice Indicator that this is an InterestRateModel contract (for inspection) * @return Always true diff --git a/contracts/JumpRateModelV2.sol b/contracts/JumpRateModelV2.sol index d94beaa07..cc3185157 100644 --- a/contracts/JumpRateModelV2.sol +++ b/contracts/JumpRateModelV2.sol @@ -30,6 +30,6 @@ contract JumpRateModelV2 is BaseJumpRateModelV2 { * @return The borrow rate percentage per block as a mantissa (scaled by 1e18) */ function getBorrowRate(uint256 utilizationRate) external view override returns (uint256) { - return getBorrowRateInternal(utilizationRate); + return _getBorrowRate(utilizationRate); } } diff --git a/contracts/Pool/PoolRegistry.sol b/contracts/Pool/PoolRegistry.sol index 077067190..4a5b0e16e 100644 --- a/contracts/Pool/PoolRegistry.sol +++ b/contracts/Pool/PoolRegistry.sol @@ -10,7 +10,6 @@ import { PoolRegistryInterface } from "./PoolRegistryInterface.sol"; import { Comptroller } from "../Comptroller.sol"; import { VToken } from "../VToken.sol"; import { ensureNonzeroAddress } from "../lib/validators.sol"; -import {StableRateModel} from "../InterestRate/StableRateModel.sol"; /** * @title PoolRegistry @@ -45,9 +44,6 @@ contract PoolRegistry is Ownable2StepUpgradeable, AccessControlledV8, PoolRegist address vTokenReceiver; uint256 supplyCap; uint256 borrowCap; - uint256 baseRatePerBlockForStable; - uint256 stableRatePremium; - uint256 optimalStableLoanRatio; } uint256 internal constant MAX_POOL_NAME_LENGTH = 100; diff --git a/contracts/VToken.sol b/contracts/VToken.sol index a2fa158a7..3de4485f2 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -73,53 +73,16 @@ contract VToken is /** * @notice Construct a new money market - * @param underlying_ The address of the underlying asset - * @param comptroller_ The address of the Comptroller - * @param interestRateModel_ The address of the interest rate model - * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 - * @param name_ ERC-20 name of this token - * @param symbol_ ERC-20 symbol of this token - * @param decimals_ ERC-20 decimal precision of this token - * @param admin_ Address of the administrator of this token - * @param accessControlManager_ AccessControlManager contract address - * @param riskManagement Addresses of risk & income related contracts - * @param stableRateModel_ Address of the stable interest rate model - * @param reserveFactorMantissa_ Percentage of borrow interest that goes to reserves (from 0 to 1e18) + * @param params InitializeParams * @custom:error ZeroAddressNotAllowed is thrown when admin address is zero * @custom:error ZeroAddressNotAllowed is thrown when shortfall contract address is zero * @custom:error ZeroAddressNotAllowed is thrown when protocol share reserve address is zero */ - function initialize( - address underlying_, - ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint256 initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint8 decimals_, - address admin_, - address accessControlManager_, - RiskManagementInit memory riskManagement, - uint256 reserveFactorMantissa_, - StableRateModel stableRateModel_ - ) external initializer { - ensureNonzeroAddress(admin_); + function initialize(InitializeParams memory params) external initializer { + ensureNonzeroAddress(params.admin_); // Initialize the market - _initialize( - underlying_, - comptroller_, - interestRateModel_, - initialExchangeRateMantissa_, - name_, - symbol_, - decimals_, - admin_, - accessControlManager_, - riskManagement, - reserveFactorMantissa_, - stableRateModel_ - ); + _initialize(params); } /** @@ -228,25 +191,6 @@ contract VToken is return mul_ScalarTruncate(exchangeRate, accountTokens[owner]); } - /** - * @notice Returns the current total borrows plus accrued interest - * @return totalBorrows The total borrows with interest - */ - function totalBorrowsCurrent() external override nonReentrant returns (uint256) { - accrueInterest(); - return totalBorrows; - } - - /** - * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex - * @param account The address whose balance should be calculated after updating borrowIndex - * @return borrowBalance The calculated balance - */ - function borrowBalanceCurrent(address account) external override nonReentrant returns (uint256) { - accrueInterest(); - return _borrowBalanceStored(account); - } - /** * @notice Sender supplies assets into the market and receives vTokens in exchange * @dev Accrues interest whether or not the operation succeeds, unless reverted @@ -281,33 +225,6 @@ contract VToken is return NO_ERROR; } - /** - * @notice Returns the current per-block borrow interest rate for this vToken - * @return rate The borrow interest rate per block, scaled by 1e18 - */ - function stableBorrowRatePerBlock() public view override returns (uint256) { - uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); - return stableRateModel.getBorrowRate(stableBorrows, totalBorrows, variableBorrowRate); - } - - /** - * @notice Returns the current per-block supply interest rate for this v - * @return rate The supply interest rate per block, scaled by 1e18 - */ - function supplyRatePerBlock() external view override returns (uint256) { - if (totalBorrows == 0) { - return 0; - } - uint256 utilizationRate = interestRateModel.utilizationRate(_getCashPrior(), totalBorrows, totalReserves); - uint256 variableBorrowRate = interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves); - uint256 variableBorrows = totalBorrows - stableBorrows; - uint256 averageMarketBorrowRate = ((variableBorrows * variableBorrowRate) + - (stableBorrows * averageStableBorrowRate)) / totalBorrows; - return - (averageMarketBorrowRate * utilizationRate * (mantissaOne - reserveFactorMantissa)) / - (mantissaOne * mantissaOne); - } - /** * @notice Sender redeems vTokens in exchange for the underlying asset * @dev Accrues interest whether or not the operation succeeds, unless reverted @@ -337,50 +254,6 @@ contract VToken is return NO_ERROR; } - /** - * @notice Sender borrows assets from the protocol to their own address - * @param borrowAmount The amount of the underlying asset to borrow - * @return error Always NO_ERROR for compatibility with Venus core tooling - * @custom:event Emits Borrow event; may emit AccrueInterest - * @custom:error BorrowCashNotAvailable is thrown when the protocol has insufficient cash - * @custom:access Not restricted - */ - function borrow(uint256 borrowAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // borrowFresh emits borrow-specific logs on errors, so we don't need to - _borrowFresh(msg.sender, borrowAmount); - return NO_ERROR; - } - - /** - * @notice Sender repays their own borrow - * @param repayAmount The amount to repay, or type(uint256).max for the full outstanding amount - * @return error Always NO_ERROR for compatibility with Venus core tooling - * @custom:event Emits RepayBorrow event; may emit AccrueInterest - * @custom:access Not restricted - */ - function repayBorrow(uint256 repayAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - _repayBorrowFresh(msg.sender, msg.sender, repayAmount); - return NO_ERROR; - } - - /** - * @notice Sender repays a borrow belonging to borrower - * @param borrower the account with the debt being payed off - * @param repayAmount The amount to repay, or type(uint256).max for the full outstanding amount - * @return error Always NO_ERROR for compatibility with Venus core tooling - * @custom:event Emits RepayBorrow event; may emit AccrueInterest - * @custom:access Not restricted - */ - function repayBorrowBehalf(address borrower, uint256 repayAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - _repayBorrowFresh(msg.sender, borrower, repayAmount); - return NO_ERROR; - } - /** * @notice The sender liquidates the borrowers collateral. * The collateral seized is transferred to the liquidator. @@ -405,27 +278,6 @@ contract VToken is return NO_ERROR; } - /** - * @notice sets protocol share accumulated from liquidations - * @dev must be equal or less than liquidation incentive - 1 - * @param newProtocolSeizeShareMantissa_ new protocol share mantissa - * @custom:event Emits NewProtocolSeizeShare event on success - * @custom:error Unauthorized error is thrown when the call is not authorized by AccessControlManager - * @custom:error ProtocolSeizeShareTooBig is thrown when the new seize share is too high - * @custom:access Controlled by AccessControlManager - */ - function setProtocolSeizeShare(uint256 newProtocolSeizeShareMantissa_) external { - _checkAccessAllowed("setProtocolSeizeShare(uint256)"); - uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).liquidationIncentiveMantissa(); - if (newProtocolSeizeShareMantissa_ + MANTISSA_ONE > liquidationIncentive) { - revert ProtocolSeizeShareTooBig(); - } - - uint256 oldProtocolSeizeShareMantissa = protocolSeizeShareMantissa; - protocolSeizeShareMantissa = newProtocolSeizeShareMantissa_; - emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa_); - } - /** * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh * @dev Admin function to accrue interest and set a new reserve factor @@ -466,21 +318,6 @@ contract VToken is _addReservesFresh(addAmount); } - /** - * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh - * @dev Admin function to accrue interest and update the interest rate model - * @param newInterestRateModel the new interest rate model to use - * @custom:event Emits NewMarketInterestRateModel event; may emit AccrueInterest - * @custom:error Unauthorized error is thrown when the call is not authorized by AccessControlManager - * @custom:access Controlled by AccessControlManager - */ - function setInterestRateModel(InterestRateModel newInterestRateModel) external override { - _checkAccessAllowed("setInterestRateModel(address)"); - - accrueInterest(); - _setInterestRateModelFresh(newInterestRateModel); - } - /** * @notice Repays a certain amount of debt, treats the rest of the borrow as bad debt, essentially * "forgiving" the borrower. Healing is a situation that should rarely happen. However, some pools @@ -764,16 +601,13 @@ contract VToken is * @return borrowBalance Amount owed in terms of underlying * @return exchangeRate Stored exchange rate */ - function getAccountSnapshot(address account) + function getAccountSnapshot( + address account + ) external view override - returns ( - uint256 error, - uint256 vTokenBalance, - uint256 borrowBalance, - uint256 exchangeRate - ) + returns (uint256 error, uint256 vTokenBalance, uint256 borrowBalance, uint256 exchangeRate) { (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); uint256 borrowAmount = _borrowBalanceStored(account) + stableBorrowAmount; @@ -793,7 +627,7 @@ contract VToken is * @return rate The borrow interest rate per block, scaled by 1e18 */ function borrowRatePerBlock() external view override returns (uint256) { - return interestRateModel.getBorrowRate(utilizationRate(_getCashPrior(), totalBorrows, totalReserves)); + return interestRateModel.getBorrowRate(utilizationRate(_getCashPrior(), totalBorrows, totalReserves, badDebt)); } /** @@ -804,10 +638,11 @@ contract VToken is if (totalBorrows == 0) { return 0; } - uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves, badDebt); uint256 averageMarketBorrowRate = _averageMarketBorrowRate(); return - (averageMarketBorrowRate * utilRate * (mantissaOne - reserveFactorMantissa)) / (mantissaOne * mantissaOne); + (averageMarketBorrowRate * utilRate * (MANTISSA_ONE - reserveFactorMantissa)) / + (MANTISSA_ONE * MANTISSA_ONE); } /** @@ -831,49 +666,6 @@ contract VToken is return _borrowBalanceStored(account) + stableBorrowAmount; } - /** - * @notice Return the borrow balance of account based on stored data - * @param account The address whose balance should be calculated - * @return borrowBalance The calculated balance - */ - function borrowBalanceStored(address account) public view override returns (uint256) { - (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); - return _borrowBalanceStored(account) + stableBorrowAmount; - } - - /** - * @notice Return the borrow balance of account based on stored data - * @param account The address whose balance should be calculated - * @return borrowBalance the calculated balance - */ - function _borrowBalanceStored(address account) internal view returns (uint256) { - /* Get borrowBalance and borrowIndex */ - BorrowSnapshot storage borrowSnapshot = accountBorrows[account]; - - /* If borrowBalance = 0 then borrowIndex is likely also 0. - * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. - */ - if (borrowSnapshot.principal == 0) { - return 0; - } - - /* Calculate new borrow balance using the interest index: - * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex - */ - uint256 principalTimesIndex = borrowSnapshot.principal * borrowIndex; - - return principalTimesIndex / borrowSnapshot.interestIndex; - } - - /** - * @notice Accrue interest then return the up-to-date exchange rate - * @return exchangeRate Calculated exchange rate scaled by 1e18 - - function borrowBalanceStored(address account) public view override returns (uint256) { - (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); - return _borrowBalanceStored(account) + stableBorrowAmount; - } - /** * @notice Calculates the exchange rate from the underlying to the VToken * @dev This function does not accrue interest before calculating the exchange rate @@ -918,9 +710,9 @@ contract VToken is /* Calculate the current borrow interest rate */ uint256 borrowRateMantissa = interestRateModel.getBorrowRate( - utilizationRate(cashPrior, borrowsPrior, reservesPrior) + utilizationRate(cashPrior, borrowsPrior, reservesPrior, badDebt) ); - require(borrowRateMantissa <= borrowRateMaxMantissa, "vToken: borrow rate is absurdly high"); + require(borrowRateMantissa <= MAX_BORROW_RATE_MANTISSA, "vToken: borrow rate is absurdly high"); /* Calculate the number of blocks elapsed since the last accrual */ uint256 blockDelta = currentBlockNumber - accrualBlockNumberPrior; @@ -1003,13 +795,12 @@ contract VToken is require(rebalanceUtilizationRateThreshold > 0, "vToken: rebalanceUtilizationRateThreshold is not set."); require(rebalanceRateFractionThreshold > 0, "vToken: rebalanceRateFractionThreshold is not set."); - uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves); + uint256 utilRate = utilizationRate(_getCashPrior(), totalBorrows, totalReserves, badDebt); /// Utilization rate is above rebalanceUtilizationRateThreshold. require(utilRate >= rebalanceUtilizationRateThreshold, "vToken: low utilization rate for rebalacing."); uint256 variableBorrowRate = interestRateModel.getBorrowRate(rebalanceUtilizationRateThreshold); - /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of /// variable borrow rate when utilization rate is rebalanceUtilizationRateThreshold require( @@ -1024,7 +815,7 @@ contract VToken is */ function stableBorrowRatePerBlock() public view override returns (uint256) { uint256 variableBorrowRate = interestRateModel.getBorrowRate( - utilizationRate(_getCashPrior(), totalBorrows, totalReserves) + utilizationRate(_getCashPrior(), totalBorrows, totalReserves, badDebt) ); return stableRateModel.getBorrowRate(stableBorrows, totalBorrows, variableBorrowRate); } @@ -1040,23 +831,31 @@ contract VToken is } /** - * @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - reserves)` + * @notice Calculates the utilization rate of the market: `(borrows + badDebt) / (cash + borrows + badDebt - reserves)` * @param cash The amount of cash in the market * @param borrows The amount of borrows in the market * @param reserves The amount of reserves in the market (currently unused) - * @return The utilization rate as a mantissa between [0, BASE] + * @param badDebt The amount of badDebt in the market + * @return The utilization rate as a mantissa between [0, MANTISSA_ONE] */ function utilizationRate( uint256 cash, uint256 borrows, - uint256 reserves + uint256 reserves, + uint256 badDebt ) public pure override returns (uint256) { - // Utilization rate is 0 when there are no borrows - if (borrows == 0) { + // Utilization rate is 0 when there are no borrows and badDebt + if ((borrows + badDebt) == 0) { return 0; } - return (borrows * mantissaOne) / (cash + borrows - reserves); + uint256 rate = ((borrows + badDebt) * EXP_SCALE) / (cash + borrows + badDebt - reserves); + + if (rate > EXP_SCALE) { + rate = EXP_SCALE; + } + + return rate; } /** @@ -1290,11 +1089,7 @@ contract VToken is * @param borrowAmount The amount of the underlying asset to borrow * @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable */ - function _borrowFresh( - address payable borrower, - uint256 borrowAmount, - InterestRateMode interestRateMode - ) internal { + function _borrowFresh(address payable borrower, uint256 borrowAmount, InterestRateMode interestRateMode) internal { /* Fail if borrow not allowed */ comptroller.preBorrowHook(address(this), borrower, borrowAmount); @@ -1431,12 +1226,10 @@ contract VToken is * @custom:events Emits RepayBorrow event; may emit AccrueInterest * @custom:access Not restricted */ - function repayBorrowStableBehalf(address borrower, uint256 repayAmount) - external - override - nonReentrant - returns (uint256) - { + function repayBorrowStableBehalf( + address borrower, + uint256 repayAmount + ) external override nonReentrant returns (uint256) { accrueInterest(); // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to _repayBorrowFresh(msg.sender, borrower, repayAmount, InterestRateMode.STABLE); @@ -1793,18 +1586,6 @@ contract VToken is emit ReservesAdded(address(this), protocolSeizeAmount, totalReservesNew); } - function _setComptroller(ComptrollerInterface newComptroller) internal { - ComptrollerInterface oldComptroller = comptroller; - // Ensure invoke comptroller.isComptroller() returns true - require(newComptroller.isComptroller(), "marker method returned false"); - - // Set market's comptroller to newComptroller - comptroller = newComptroller; - - // Emit NewComptroller(oldComptroller, newComptroller) - emit NewComptroller(oldComptroller, newComptroller); - } - /** * @notice sets protocol share accumulated from liquidations * @dev must be less than liquidation incentive - 1 @@ -2075,68 +1856,45 @@ contract VToken is /** * @notice Initialize the money market - * @param underlying_ The address of the underlying asset - * @param comptroller_ The address of the Comptroller - * @param interestRateModel_ The address of the interest rate model - * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 - * @param name_ ERC-20 name of this token - * @param symbol_ ERC-20 symbol of this token - * @param decimals_ ERC-20 decimal precision of this token - * @param admin_ Address of the administrator of this token - * @param accessControlManager_ AccessControlManager contract address - * @param riskManagement Addresses of risk & income related contracts - * @param reserveFactorMantissa_ Percentage of borrow interest that goes to reserves (from 0 to 1e18) - */ - function _initialize( - address underlying_, - ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint256 initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint8 decimals_, - address admin_, - address accessControlManager_, - RiskManagementInit memory riskManagement, - uint256 reserveFactorMantissa_, - StableRateModel stableRateModel_ - ) internal onlyInitializing { + * @param params InitializeParams contains the required arguments to initialize + */ + function _initialize(InitializeParams memory params) internal onlyInitializing { __Ownable2Step_init(); - __AccessControlled_init_unchained(accessControlManager_); + __AccessControlled_init_unchained(params.accessControlManager_); require(accrualBlockNumber == 0 && borrowIndex == 0, "market may only be initialized once"); // Set initial exchange rate - initialExchangeRateMantissa = initialExchangeRateMantissa_; + initialExchangeRateMantissa = params.initialExchangeRateMantissa_; require(initialExchangeRateMantissa > 0, "initial exchange rate must be greater than zero."); - _setComptroller(comptroller_); + _setComptroller(params.comptroller_); // Initialize block number and borrow index (block number mocks depend on comptroller being set) accrualBlockNumber = _getBlockNumber(); borrowIndex = MANTISSA_ONE; // Set the interest rate model (depends on block number / borrow index) - _setInterestRateModelFresh(interestRateModel_); + _setInterestRateModelFresh(params.interestRateModel_); // Set the interest rate model (depends on block number / borrow index) - _setStableInterestRateModelFresh(stableRateModel_); + _setStableInterestRateModelFresh(params.stableRateModel_); - _setReserveFactorFresh(reserveFactorMantissa_); + _setReserveFactorFresh(params.reserveFactorMantissa_); - name = name_; - symbol = symbol_; - decimals = decimals_; - _setShortfallContract(riskManagement.shortfall); - _setProtocolShareReserve(riskManagement.protocolShareReserve); + name = params.name_; + symbol = params.symbol_; + decimals = params.decimals_; + _setShortfallContract(params.shortfall_); + _setProtocolShareReserve(params.protocolShareReserve_); protocolSeizeShareMantissa = DEFAULT_PROTOCOL_SEIZE_SHARE_MANTISSA; // Set underlying and sanity check it - underlying = underlying_; + underlying = params.underlying_; IERC20Upgradeable(underlying).totalSupply(); // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund) _notEntered = true; - _transferOwnership(admin_); + _transferOwnership(params.admin_); } function _setShortfallContract(address shortfall_) internal { @@ -2221,35 +1979,14 @@ contract VToken is return exchangeRate; } - /// @notice Reverts if the call is not allowed by AccessControlManager - /// @param signature Method signature - function _checkAccessAllowed(string memory signature) internal view { - bool isAllowedToCall = AccessControlManager(accessControlManager).isAllowedToCall(msg.sender, signature); - - if (!isAllowedToCall) { - revert Unauthorized(msg.sender, address(this), signature); - } - } - - /** - * @notice Reverts if the call is not allowed by AccessControlManager - * @param signature Method signature - */ - function _checkAccessAllowed(string memory signature) internal view { - bool isAllowedToCall = AccessControlManager(accessControlManager).isAllowedToCall(msg.sender, signature); - - if (!isAllowedToCall) { - revert Unauthorized(msg.sender, address(this), signature); - } - } - /** * @notice Calculate the average market borrow rate with respect to variable and stable borrows */ function _averageMarketBorrowRate() internal view returns (uint256) { uint256 variableBorrowRate = interestRateModel.getBorrowRate( - utilizationRate(_getCashPrior(), totalBorrows, totalReserves) + utilizationRate(_getCashPrior(), totalBorrows, totalReserves, badDebt) ); + uint256 variableBorrows = totalBorrows - stableBorrows; return ((variableBorrows * variableBorrowRate) + (stableBorrows * averageStableBorrowRate)) / totalBorrows; } @@ -2259,15 +1996,7 @@ contract VToken is * @param account The address whose balance should be calculated * @return borrowBalance the calculated balance */ - function _stableBorrowBalanceStored(address account) - internal - view - returns ( - uint256, - uint256, - Exp memory - ) - { + function _stableBorrowBalanceStored(address account) internal view returns (uint256, uint256, Exp memory) { /* Get borrowBalance and borrowIndex */ StableBorrowSnapshot storage borrowSnapshot = accountStableBorrows[account]; Exp memory simpleStableInterestFactor; diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 5f4c9535a..021e52a31 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -23,6 +23,22 @@ contract VTokenStorage { uint256 interestIndex; } + struct InitializeParams { + address underlying_; + ComptrollerInterface comptroller_; + InterestRateModel interestRateModel_; + uint256 initialExchangeRateMantissa_; + string name_; + string symbol_; + uint8 decimals_; + address admin_; + address accessControlManager_; + address shortfall_; + address payable protocolShareReserve_; + uint256 reserveFactorMantissa_; + StableRateModel stableRateModel_; + } + /** * @dev Guard variable for re-entrancy checks */ @@ -188,11 +204,6 @@ contract VTokenStorage { * @notice Interface implemented by the `VToken` contract */ abstract contract VTokenInterface is VTokenStorage { - struct RiskManagementInit { - address shortfall; - address payable protocolShareReserve; - } - /*** Market Events ***/ /** @@ -333,7 +344,7 @@ abstract contract VTokenInterface is VTokenStorage { * @notice Event emitted when tokens are swept */ event SweepToken(address indexed token); - + /** * @notice Event emitted after updating stable borrow balance for borrower */ @@ -423,15 +434,12 @@ abstract contract VTokenInterface is VTokenStorage { function utilizationRate( uint256 cash, uint256 borrows, - uint256 reserves + uint256 reserves, + uint256 badDebt ) public pure virtual returns (uint256); - function borrowRatePerBlock() external view virtual returns (uint256); - function stableBorrowRatePerBlock() public view virtual returns (uint256); - function supplyRatePerBlock() external view virtual returns (uint256); - function totalBorrowsCurrent() external virtual returns (uint256); function balanceOfUnderlying(address owner) external virtual returns (uint256); diff --git a/contracts/WhitePaperInterestRateModel.sol b/contracts/WhitePaperInterestRateModel.sol index 54112aa2e..0e72c691e 100644 --- a/contracts/WhitePaperInterestRateModel.sol +++ b/contracts/WhitePaperInterestRateModel.sol @@ -42,32 +42,4 @@ contract WhitePaperInterestRateModel is InterestRateModel { function getBorrowRate(uint256 utRate) public view override returns (uint256) { return ((utRate * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock; } - - /** - * @notice Calculates the utilization rate of the market: `(borrows + badDebt) / (cash + borrows + badDebt - reserves)` - * @param cash The amount of cash in the market - * @param borrows The amount of borrows in the market - * @param reserves The amount of reserves in the market (currently unused) - * @param badDebt The amount of badDebt in the market - * @return The utilization rate as a mantissa between [0, MANTISSA_ONE] - */ - function utilizationRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 badDebt - ) public pure returns (uint256) { - // Utilization rate is 0 when there are no borrows and badDebt - if ((borrows + badDebt) == 0) { - return 0; - } - - uint256 rate = ((borrows + badDebt) * EXP_SCALE) / (cash + borrows + badDebt - reserves); - - if (rate > EXP_SCALE) { - rate = EXP_SCALE; - } - - return rate; - } } diff --git a/contracts/test/ComptrollerHarness.sol b/contracts/test/ComptrollerHarness.sol index ff7fe00dc..009e82e77 100644 --- a/contracts/test/ComptrollerHarness.sol +++ b/contracts/test/ComptrollerHarness.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.10; -import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol"; - import { Comptroller } from "../Comptroller.sol"; contract ComptrollerHarness is Comptroller { diff --git a/contracts/test/UpgradedVToken.sol b/contracts/test/UpgradedVToken.sol index f9e7f3fd3..cea9347fe 100644 --- a/contracts/test/UpgradedVToken.sol +++ b/contracts/test/UpgradedVToken.sol @@ -2,9 +2,6 @@ pragma solidity ^0.8.10; import { VToken } from "../VToken.sol"; -import { ComptrollerInterface } from "../ComptrollerInterface.sol"; -import { InterestRateModel } from "../InterestRateModel.sol"; -import {StableRateModel} from "../InterestRate/StableRateModel.sol"; /** * @title Venus's VToken Contract @@ -31,34 +28,8 @@ contract UpgradedVToken is VToken { return 2; } - function initializeV2( - address underlying_, - ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint256 initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint8 decimals_, - address payable admin_, - address accessControlManager_, - RiskManagementInit memory riskManagement, - StableRateModel stableRateModel_, - uint256 reserveFactorMantissa_ - ) public reinitializer(2) { - super._initialize( - underlying_, - comptroller_, - interestRateModel_, - initialExchangeRateMantissa_, - name_, - symbol_, - decimals_, - admin_, - accessControlManager_, - riskManagement, - stableRateModel_, - reserveFactorMantissa_ - ); + function initializeV2(InitializeParams memory params) public reinitializer(2) { + super._initialize(params); } function getTokenUnderlying() public view returns (address) { diff --git a/contracts/test/VTokenHarness.sol b/contracts/test/VTokenHarness.sol index edee9fb68..baaa9f2c6 100644 --- a/contracts/test/VTokenHarness.sol +++ b/contracts/test/VTokenHarness.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.10; -import { AccessControlManager } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlManager.sol"; - import { VToken } from "../VToken.sol"; import { InterestRateModel } from "../InterestRateModel.sol"; -import {StableRateModel} from "../InterestRate/StableRateModel.sol"; +import { ComptrollerScenario } from "../test/ComptrollerScenario.sol"; contract VTokenHarness is VToken { uint256 public blockNumber; @@ -14,33 +12,10 @@ contract VTokenHarness is VToken { mapping(address => bool) public failTransferToAddresses; - function initializeHarness( - address underlying_, - ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint256 initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint8 decimals_, - address payable admin_, - AccessControlManager accessControlManager_, - RiskManagementInit memory riskManagement, - StableRateModel stableRateModel_ - ) external initializer { + function initializeHarness(InitializeParams memory params) external initializer { blockNumber = 100000; - super._initialize( - underlying_, - comptroller_, - interestRateModel_, - initialExchangeRateMantissa_, - name_, - symbol_, - decimals_, - admin_, - accessControlManager_, - riskManagement, - stableRateModel_ - ); + + super._initialize(params); } function _exchangeRateStored() internal view override returns (uint256) { @@ -54,10 +29,6 @@ contract VTokenHarness is VToken { return blockNumber; } - function getBorrowRateMaxMantissa() external pure returns (uint256) { - return borrowRateMaxMantissa; - } - function getStableBorrowRateMaxMantissa() external pure returns (uint256) { return stableBorrowRateMaxMantissa; } @@ -117,30 +88,18 @@ contract VTokenHarness is VToken { super._redeemFresh(account, vTokenAmount, underlyingAmount); } - function harnessAccountBorrows(address account) external view returns (uint256 principal, uint256 interestIndex) { - BorrowSnapshot memory snapshot = accountBorrows[account]; - return (snapshot.principal, snapshot.interestIndex); - } - - function harnessAccountStableBorrows(address account) + function harnessAccountStableBorrows( + address account + ) external view - returns ( - uint256 principal, - uint256 interestIndex, - uint256 lastBlockAccrued, - uint256 stableRateMantissa - ) + returns (uint256 principal, uint256 interestIndex, uint256 lastBlockAccrued, uint256 stableRateMantissa) { StableBorrowSnapshot memory snapshot = accountStableBorrows[account]; return (snapshot.principal, snapshot.interestIndex, snapshot.lastBlockAccrued, snapshot.stableRateMantissa); } - function harnessSetAccountBorrows( - address account, - uint256 principal, - uint256 interestIndex - ) external { + function harnessSetAccountBorrows(address account, uint256 principal, uint256 interestIndex) external { accountBorrows[account] = BorrowSnapshot({ principal: principal, interestIndex: interestIndex }); } @@ -175,19 +134,11 @@ contract VTokenHarness is VToken { _borrowFresh(account, borrowAmount, InterestRateMode.STABLE); } - function harnessRepayBorrowFresh( - address payer, - address account, - uint256 repayAmount - ) external { + function harnessRepayBorrowFresh(address payer, address account, uint256 repayAmount) external { _repayBorrowFresh(payer, account, repayAmount, InterestRateMode.VARIABLE); } - function harnessRepayBorrowStableFresh( - address payer, - address account, - uint256 repayAmount - ) external { + function harnessRepayBorrowStableFresh(address payer, address account, uint256 repayAmount) external { _repayBorrowFresh(payer, account, repayAmount, InterestRateMode.STABLE); } @@ -253,32 +204,8 @@ contract VTokenHarness is VToken { } contract VTokenScenario is VToken { - constructor( - address underlying_, - ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint256 initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint8 decimals_, - address payable admin_, - AccessControlManager accessControlManager_, - VTokenInterface.RiskManagementInit memory riskManagement, - StableRateModel stableRateModel_ - ) { - initialize( - underlying_, - comptroller_, - interestRateModel_, - initialExchangeRateMantissa_, - name_, - symbol_, - decimals_, - admin_, - accessControlManager_, - riskManagement, - stableRateModel_ - ); + constructor(InitializeParams memory params) { + _initialize(params); } function setTotalBorrows(uint256 totalBorrows_) public { @@ -286,9 +213,6 @@ contract VTokenScenario is VToken { } function _exchangeRateStored() internal view override returns (uint256) { - if (harnessExchangeRateStored) { - return harnessExchangeRate; - } return super._exchangeRateStored(); } @@ -299,40 +223,9 @@ contract VTokenScenario is VToken { } contract VEvil is VTokenScenario { - constructor( - address underlying_, - ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint256 initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint8 decimals_, - address payable admin_, - AccessControlManager accessControlManager_, - VTokenInterface.RiskManagementInit memory riskManagement, - StableRateModel stableRateModel_ - ) - VTokenScenario( - underlying_, - comptroller_, - interestRateModel_, - initialExchangeRateMantissa_, - name_, - symbol_, - decimals_, - admin_, - accessControlManager_, - riskManagement, - stableRateModel_ - ) - {} - - function evilSeize( - VToken treasure, - address liquidator, - address borrower, - uint256 seizeTokens - ) public { + constructor(InitializeParams memory params) VTokenScenario(params) {} + + function evilSeize(VToken treasure, address liquidator, address borrower, uint256 seizeTokens) public { treasure.seize(liquidator, borrower, seizeTokens); } } diff --git a/spec/certora/contracts/InterestRateModelModel.sol b/spec/certora/contracts/InterestRateModelModel.sol index 042e5aae6..1fdd46aef 100644 --- a/spec/certora/contracts/InterestRateModelModel.sol +++ b/spec/certora/contracts/InterestRateModelModel.sol @@ -10,20 +10,7 @@ contract InterestRateModelModel is InterestRateModel { return true; } - function getBorrowRate( - uint256 _cash, - uint256 _borrows, - uint256 _reserves - ) external view override returns (uint256) { + function getBorrowRate(uint256 utRate) external view override returns (uint256) { return borrowDummy; } - - function getSupplyRate( - uint256 _cash, - uint256 _borrows, - uint256 _reserves, - uint256 _reserveFactorMantissa - ) external view override returns (uint256) { - return supplyDummy; - } } From 6e73662b0d1fe0331d51c3d8b82ec58dc45859c0 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Tue, 3 Oct 2023 23:57:26 +0530 Subject: [PATCH 18/23] tests: fixed tests after merge conflicts --- tests/hardhat/Fork/RiskFundSwap.ts | 6 --- tests/hardhat/JumpRateModelV2.ts | 45 ++-------------- tests/hardhat/Lens/PoolLens.ts | 17 +++++-- tests/hardhat/PoolRegistry.ts | 36 +++---------- tests/hardhat/Tokens/accrueInterestTest.ts | 24 +++++---- tests/hardhat/Tokens/stableRateRebalancing.ts | 29 ++++------- tests/hardhat/Tokens/swapBorrowRateMode.ts | 5 +- tests/hardhat/WhitePaperInterestRateModel.ts | 38 +------------- tests/hardhat/util/TokenTestHelpers.ts | 51 ++++++++++++------- 9 files changed, 82 insertions(+), 169 deletions(-) diff --git a/tests/hardhat/Fork/RiskFundSwap.ts b/tests/hardhat/Fork/RiskFundSwap.ts index 216b24fa8..a3a883220 100644 --- a/tests/hardhat/Fork/RiskFundSwap.ts +++ b/tests/hardhat/Fork/RiskFundSwap.ts @@ -201,9 +201,6 @@ const riskFundFixture = async (): Promise => { vTokenReceiver: admin.address, supplyCap: initialSupply, borrowCap: initialSupply, - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), }); await BUSD.faucet(initialSupply); @@ -216,9 +213,6 @@ const riskFundFixture = async (): Promise => { vTokenReceiver: admin.address, supplyCap: initialSupply, borrowCap: initialSupply, - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), }); await riskFund.setPoolRegistry(poolRegistry.address); diff --git a/tests/hardhat/JumpRateModelV2.ts b/tests/hardhat/JumpRateModelV2.ts index f80f2dd93..f980f3d33 100644 --- a/tests/hardhat/JumpRateModelV2.ts +++ b/tests/hardhat/JumpRateModelV2.ts @@ -15,10 +15,6 @@ describe("Jump rate model tests", () => { let accessControlManager: FakeContract; const kink = convertToUnit(8, 17); - const cash = convertToUnit(10, 19); - const borrows = convertToUnit(4, 19); - const reserves = convertToUnit(2, 19); - const badDebt = convertToUnit(1, 19); const expScale = convertToUnit(1, 18); const blocksPerYear = 10512000; const baseRatePerYear = convertToUnit(2, 12); @@ -66,38 +62,20 @@ describe("Jump rate model tests", () => { expect(await jumpRateModel.kink()).equal(kink); }); - it("Utilization rate: borrows and badDebt is zero", async () => { - expect(await jumpRateModel.utilizationRate(cash, 0, reserves, 0)).equal(0); - }); - - it("Utilization rate", async () => { - const utilizationRate = new BigNumber(Number(borrows) + Number(badDebt)) - .multipliedBy(expScale) - .dividedBy(Number(cash) + Number(borrows) + Number(badDebt) - Number(reserves)) - .toFixed(0); - - expect(await jumpRateModel.utilizationRate(cash, borrows, reserves, badDebt)).equal(utilizationRate); - }); - it("Borrow Rate: below kink utilization", async () => { const multiplierPerBlock = (await jumpRateModel.multiplierPerBlock()).toString(); const baseRatePerBlock = (await jumpRateModel.baseRatePerBlock()).toString(); - const utilizationRate = (await jumpRateModel.utilizationRate(cash, borrows, reserves, badDebt)).toString(); - + const utilizationRate = convertToUnit(3, 17); const value = new BigNumber(utilizationRate).multipliedBy(multiplierPerBlock).dividedBy(expScale).toFixed(0); - expect(await jumpRateModel.getBorrowRate(cash, borrows, reserves, badDebt)).equal( - Number(value) + Number(baseRatePerBlock), - ); + expect(await jumpRateModel.getBorrowRate(utilizationRate)).equal(Number(value) + Number(baseRatePerBlock)); }); it("Borrow Rate: above kink utilization", async () => { const multiplierPerBlock = (await jumpRateModel.multiplierPerBlock()).toString(); const jumpMultiplierPerBlock = (await jumpRateModel.jumpMultiplierPerBlock()).toString(); const baseRatePerBlock = (await jumpRateModel.baseRatePerBlock()).toString(); - const utilizationRate = ( - await jumpRateModel.utilizationRate(convertToUnit(6, 19), convertToUnit(16, 19), reserves, badDebt) - ).toString(); + const utilizationRate = convertToUnit(8, 17); const value = new BigNumber(kink).multipliedBy(multiplierPerBlock).dividedBy(expScale).toFixed(0); @@ -106,21 +84,6 @@ describe("Jump rate model tests", () => { const jumpValue = new BigNumber(excessUtil).multipliedBy(jumpMultiplierPerBlock).dividedBy(expScale).toFixed(0); - expect(await jumpRateModel.getBorrowRate(convertToUnit(6, 19), convertToUnit(16, 19), reserves, badDebt)).equal( - Number(jumpValue) + Number(normalRate), - ); - }); - - it("Supply Rate", async () => { - const reserveMantissa = convertToUnit(1, 17); - const oneMinusReserveFactor = Number(expScale) - Number(reserveMantissa); - const borrowRate = (await jumpRateModel.getBorrowRate(cash, borrows, reserves, badDebt)).toString(); - const rateToPool = new BigNumber(borrowRate).multipliedBy(oneMinusReserveFactor).dividedBy(expScale).toFixed(0); - const rate = new BigNumber(borrows) - .multipliedBy(expScale) - .dividedBy(Number(cash) + Number(borrows) + Number(badDebt) - Number(reserves)); - const supplyRate = new BigNumber(rateToPool).multipliedBy(rate).dividedBy(expScale).toFixed(0); - - expect(await jumpRateModel.getSupplyRate(cash, borrows, reserves, reserveMantissa, badDebt)).equal(supplyRate); + expect(await jumpRateModel.getBorrowRate(utilizationRate)).equal(Number(jumpValue) + Number(normalRate)); }); }); diff --git a/tests/hardhat/Lens/PoolLens.ts b/tests/hardhat/Lens/PoolLens.ts index 0c78ddf95..1a037bd80 100644 --- a/tests/hardhat/Lens/PoolLens.ts +++ b/tests/hardhat/Lens/PoolLens.ts @@ -16,6 +16,7 @@ import { PoolLens, PoolLens__factory, PoolRegistry, + StableRateModel__factory, VToken, WhitePaperInterestRateModel__factory, } from "../../../typechain"; @@ -155,7 +156,10 @@ describe("PoolLens", async function () { const RateModel = await ethers.getContractFactory( "WhitePaperInterestRateModel", ); + const StableRateModel = await ethers.getContractFactory("StableRateModel"); const whitePaperInterestRateModel = await RateModel.deploy(0, parseUnits("0.04", 18)); + const stableRateModel = await StableRateModel.deploy(0, parseUnits("2", 12), parseUnits("5", 17), owner.address); + vWBTC = await makeVToken({ underlying: mockWBTC, comptroller: comptroller1Proxy, @@ -165,11 +169,12 @@ describe("PoolLens", async function () { admin: owner, interestRateModel: whitePaperInterestRateModel, beacon: vTokenBeacon, + stableRateModel: stableRateModel, }); await mockWBTC.faucet(initialSupply); await mockWBTC.approve(poolRegistry.address, initialSupply); - await poolRegistry.addMarket({ + const addMarketParams = { vToken: vWBTC.address, collateralFactor: parseUnits("0.7", 18), liquidationThreshold: parseUnits("0.7", 18), @@ -177,7 +182,9 @@ describe("PoolLens", async function () { vTokenReceiver: owner.address, supplyCap: parseUnits("4000", 18), borrowCap: parseUnits("2000", 18), - }); + }; + + await poolRegistry.addMarket(addMarketParams); vDAI = await makeVToken({ underlying: mockDAI, @@ -188,11 +195,12 @@ describe("PoolLens", async function () { admin: owner, interestRateModel: whitePaperInterestRateModel, beacon: vTokenBeacon, + stableRateModel: stableRateModel, }); await mockDAI.faucet(initialSupply); await mockDAI.approve(poolRegistry.address, initialSupply); - await poolRegistry.addMarket({ + const addMarketParamsDAI = { vToken: vDAI.address, collateralFactor: parseUnits("0.7", 18), liquidationThreshold: parseUnits("0.7", 18), @@ -200,7 +208,8 @@ describe("PoolLens", async function () { vTokenReceiver: owner.address, supplyCap: initialSupply, borrowCap: initialSupply, - }); + }; + await poolRegistry.addMarket(addMarketParamsDAI); await poolRegistry.updatePoolMetadata(comptroller1Proxy.address, { category: "High market cap", diff --git a/tests/hardhat/PoolRegistry.ts b/tests/hardhat/PoolRegistry.ts index 0a611cbfe..c4a50878f 100644 --- a/tests/hardhat/PoolRegistry.ts +++ b/tests/hardhat/PoolRegistry.ts @@ -65,9 +65,6 @@ describe("PoolRegistry: Tests", function () { vTokenReceiver: owner.address, supplyCap: INITIAL_SUPPLY, borrowCap: INITIAL_SUPPLY, - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), }; return { ...defaults, ...overwrites }; }; @@ -160,9 +157,6 @@ describe("PoolRegistry: Tests", function () { vTokenReceiver: owner.address, supplyCap: INITIAL_SUPPLY, borrowCap: INITIAL_SUPPLY, - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), }); vDAI = await makeVToken({ @@ -185,9 +179,6 @@ describe("PoolRegistry: Tests", function () { vTokenReceiver: owner.address, supplyCap: INITIAL_SUPPLY, borrowCap: INITIAL_SUPPLY, - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), }); vMockToken = await makeVToken({ @@ -536,33 +527,20 @@ describe("PoolRegistry: Tests", function () { }); it("Revert on addMarket by non owner user", async () => { - const [, user, proxyAdmin] = await ethers.getSigners(); + const [, user, fakeVToken] = await ethers.getSigners(); + await fakeAccessControlManager.isAllowedToCall.returns(false); await expect( poolRegistry.connect(user).addMarket({ - comptroller: comptroller2Proxy.address, - asset: mockWBTC.address, - decimals: 8, - name: "Compound WBTC", - symbol: "vWBTC", - rateModel: 0, - baseRatePerYear: 0, - multiplierPerYear: "40000000000000000", - jumpMultiplierPerYear: 0, - kink_: 0, - collateralFactor: convertToUnit(0.7, 18), - liquidationThreshold: convertToUnit(0.7, 18), - accessControlManager: fakeAccessControlManager.address, - vTokenProxyAdmin: proxyAdmin.address, - beaconAddress: vTokenBeacon.address, + vToken: fakeVToken.address, + collateralFactor: parseUnits("0.7", 18), + liquidationThreshold: parseUnits("0.7", 18), initialSupply: INITIAL_SUPPLY, + vTokenReceiver: owner.address, supplyCap: INITIAL_SUPPLY, borrowCap: INITIAL_SUPPLY, - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), }), - ).to.be.rejectedWith("Ownable: caller is not the owner"); + ).to.be.rejectedWith("Unauthorized"); }); }); }); diff --git a/tests/hardhat/Tokens/accrueInterestTest.ts b/tests/hardhat/Tokens/accrueInterestTest.ts index 7c165cc19..a478846f1 100644 --- a/tests/hardhat/Tokens/accrueInterestTest.ts +++ b/tests/hardhat/Tokens/accrueInterestTest.ts @@ -5,15 +5,16 @@ import chai from "chai"; import { BigNumber, BigNumberish, constants } from "ethers"; import { parseUnits } from "ethers/lib/utils"; +import { convertToUnit } from "../../../helpers/utils"; import { InterestRateModel, StableRateModel, VTokenHarness } from "../../../typechain"; import { vTokenTestFixture } from "../util/TokenTestHelpers"; -import { convertToUnit } from "../../../helpers/utils"; const { expect } = chai; chai.use(smock.matchers); const blockNumber = 2e7; const borrowIndex = parseUnits("1", 18).toBigInt(); +const stableBorrowIndex = parseUnits("1", 18).toBigInt(); const borrowRate = parseUnits("0.000001", 18).toBigInt(); async function pretendBlock( @@ -30,31 +31,31 @@ async function pretendBlock( async function preAccrue({ vToken, interestRateModel, - stableInterestRateModel, + stableRateModel, }: { vToken: VTokenHarness; interestRateModel: FakeContract; - stableInterestRateModel: FakeContract; + stableRateModel: FakeContract; }) { interestRateModel.getBorrowRate.reset(); interestRateModel.getBorrowRate.returns(borrowRate); - stableInterestRateModel.getBorrowRate.reset(); - stableInterestRateModel.getBorrowRate.returns(borrowRate); + stableRateModel.getBorrowRate.reset(); + stableRateModel.getBorrowRate.returns(borrowRate); await vToken.harnessExchangeRateDetails(0, 0, 0); } describe("VToken", () => { let vToken: VTokenHarness; let interestRateModel: FakeContract; - let stableInterestRateModel: FakeContract; + let stableRateModel: FakeContract; beforeEach(async () => { const contracts = await loadFixture(vTokenTestFixture); - ({ vToken, interestRateModel, stableInterestRateModel } = contracts); + ({ vToken, interestRateModel, stableRateModel } = contracts); }); beforeEach(async () => { - await preAccrue({ vToken, interestRateModel, stableInterestRateModel }); + await preAccrue({ vToken, interestRateModel, stableRateModel }); }); describe("accrueInterest", () => { @@ -68,7 +69,7 @@ describe("VToken", () => { it("reverts if the stable interest rate is absurdly high", async () => { await pretendBlock(vToken, blockNumber, 1); expect(await vToken.getStableBorrowRateMaxMantissa()).to.equal(convertToUnit("0.000005", 18)); // 0.0005% per block - stableInterestRateModel.getBorrowRate.returns(convertToUnit("0.00001", 18)); // 0.0010% per block + stableRateModel.getBorrowRate.returns(convertToUnit("0.00001", 18)); // 0.0010% per block await expect(vToken.accrueInterest()).to.be.revertedWith("vToken: stable borrow rate is absurdly high"); }); @@ -80,7 +81,7 @@ describe("VToken", () => { it("fails if new borrow rate calculation fails", async () => { await pretendBlock(vToken, blockNumber, 1); - stableInterestRateModel.getBorrowRate.reverts("Oups"); + stableRateModel.getBorrowRate.reverts("Oups"); await expect(vToken.accrueInterest()).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); }); @@ -157,10 +158,11 @@ describe("VToken", () => { const receipt = await vToken.accrueInterest(); const expectedInterestAccumulated = expectedTotalBorrows.sub(startingTotalBorrows); + const stableBorrowIndex = 0; await expect(receipt) .to.emit(vToken, "AccrueInterest") - .withArgs(0, expectedInterestAccumulated, expectedBorrowIndex, expectedTotalBorrows); + .withArgs(0, expectedInterestAccumulated, expectedBorrowIndex, expectedTotalBorrows, stableBorrowIndex); expect(await vToken.accrualBlockNumber()).to.equal(expectedAccrualBlockNumber); expect(await vToken.borrowIndex()).to.equal(expectedBorrowIndex); diff --git a/tests/hardhat/Tokens/stableRateRebalancing.ts b/tests/hardhat/Tokens/stableRateRebalancing.ts index ea60764fb..f2bf471a2 100644 --- a/tests/hardhat/Tokens/stableRateRebalancing.ts +++ b/tests/hardhat/Tokens/stableRateRebalancing.ts @@ -13,18 +13,10 @@ chai.use(smock.matchers); const borrowAmount = convertToUnit("1000", 18); -async function preBorrow( - contracts: VTokenTestFixture, - borrower: Signer, - borrowAmount: BigNumberish, - stableRateMantissa: BigNumberish = 0, -) { - const { comptroller, interestRateModel, underlying, vToken, stableInterestRateModel } = contracts; +async function preBorrow(contracts: VTokenTestFixture, borrower: Signer, borrowAmount: BigNumberish) { + const { comptroller, underlying, vToken } = contracts; comptroller.preBorrowHook.reset(); - interestRateModel.getBorrowRate.reset(); - stableInterestRateModel.getBorrowRate.returns(stableRateMantissa); - const borrowerAddress = await borrower.getAddress(); await underlying.harnessSetBalance(vToken.address, borrowAmount); await vToken.harnessSetFailTransferToAddress(borrowerAddress, false); @@ -42,14 +34,16 @@ describe("VToken", function () { let contracts: VTokenTestFixture; let vToken: MockContract; let interestRateModel: FakeContract; - let stableInterestRateModel: FakeContract; + let stableRateModel: FakeContract; let _root: Signer; let borrower: Signer; beforeEach(async () => { [_root, borrower] = await ethers.getSigners(); contracts = await loadFixture(vTokenTestFixture); - ({ vToken, interestRateModel, stableInterestRateModel } = contracts); + ({ vToken, interestRateModel, stableRateModel } = contracts); + await stableRateModel.getBorrowRate.returns(convertToUnit(1, 6)); + await interestRateModel.getBorrowRate.returns(convertToUnit(1, 12)); }); describe("Rebalance Stable rate", () => { @@ -101,25 +95,22 @@ describe("VToken", function () { await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); await preBorrow(contracts, borrower, borrowAmount); await borrowStable(vToken, borrower, convertToUnit(900, 18)); - await interestRateModel.getBorrowRate.returns(convertToUnit(5, 8)); await vToken.validateRebalanceStableBorrowRate(); }); it("Rebalacing the stable rate for a user", async () => { await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(7, 17)); await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); - await preBorrow(contracts, borrower, borrowAmount, convertToUnit(5, 6)); + await preBorrow(contracts, borrower, borrowAmount); await borrowStable(vToken, borrower, convertToUnit(900, 18)); let accountBorrows = await vToken.harnessAccountStableBorrows(borrower.getAddress()); - expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(5, 6)); + expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(1, 6)); - interestRateModel.getBorrowRate.returns(convertToUnit(5, 8)); - stableInterestRateModel.getBorrowRate.returns(convertToUnit(8, 8)); - await vToken.rebalanceStableBorrowRate(borrower.getAddress()); + await vToken.rebalanceStableBorrowRate(borrower.address); accountBorrows = await vToken.harnessAccountStableBorrows(borrower.getAddress()); - expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(8, 8)); + expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(1, 8)); }); }); }); diff --git a/tests/hardhat/Tokens/swapBorrowRateMode.ts b/tests/hardhat/Tokens/swapBorrowRateMode.ts index 04873fb0a..3aecab103 100644 --- a/tests/hardhat/Tokens/swapBorrowRateMode.ts +++ b/tests/hardhat/Tokens/swapBorrowRateMode.ts @@ -14,12 +14,9 @@ chai.use(smock.matchers); const borrowAmount = convertToUnit("1000", 18); async function preBorrow(contracts: VTokenTestFixture, borrower: Signer, borrowAmount: BigNumberish) { - const { comptroller, interestRateModel, underlying, vToken, stableInterestRateModel } = contracts; + const { comptroller, underlying, vToken } = contracts; comptroller.preBorrowHook.reset(); - interestRateModel.getBorrowRate.reset(); - stableInterestRateModel.getBorrowRate.reset(); - const borrowerAddress = await borrower.getAddress(); await underlying.harnessSetBalance(vToken.address, borrowAmount); await vToken.harnessSetFailTransferToAddress(borrowerAddress, false); diff --git a/tests/hardhat/WhitePaperInterestRateModel.ts b/tests/hardhat/WhitePaperInterestRateModel.ts index 5305db9ff..7e7535f16 100644 --- a/tests/hardhat/WhitePaperInterestRateModel.ts +++ b/tests/hardhat/WhitePaperInterestRateModel.ts @@ -12,10 +12,6 @@ chai.use(smock.matchers); describe("White paper interest rate model tests", () => { let whitePaperInterestRateModel: WhitePaperInterestRateModel; - const cash = convertToUnit(10, 19); - const borrows = convertToUnit(4, 19); - const reserves = convertToUnit(2, 19); - const badDebt = convertToUnit(1, 19); const expScale = convertToUnit(1, 18); const blocksPerYear = 10512000; const baseRatePerYear = convertToUnit(2, 12); @@ -39,45 +35,15 @@ describe("White paper interest rate model tests", () => { expect(await whitePaperInterestRateModel.multiplierPerBlock()).equal(multiplierPerBlock); }); - it("Utilization rate: borrows and badDebt is zero", async () => { - expect(await whitePaperInterestRateModel.utilizationRate(cash, 0, badDebt, 0)).equal(0); - }); - - it("Utilization rate", async () => { - const utilizationRate = new BigNumber(Number(borrows) + Number(badDebt)) - .multipliedBy(expScale) - .dividedBy(Number(cash) + Number(borrows) + Number(badDebt) - Number(reserves)) - .toFixed(0); - - expect(await whitePaperInterestRateModel.utilizationRate(cash, borrows, reserves, badDebt)).equal(utilizationRate); - }); - it("Borrow Rate", async () => { const multiplierPerBlock = (await whitePaperInterestRateModel.multiplierPerBlock()).toString(); const baseRatePerBlock = (await whitePaperInterestRateModel.baseRatePerBlock()).toString(); - const utilizationRate = ( - await whitePaperInterestRateModel.utilizationRate(cash, borrows, reserves, badDebt) - ).toString(); + const utilizationRate = convertToUnit(3, 17); const value = new BigNumber(utilizationRate).multipliedBy(multiplierPerBlock).dividedBy(expScale).toFixed(0); - expect(await whitePaperInterestRateModel.getBorrowRate(cash, borrows, reserves, badDebt)).equal( + expect(await whitePaperInterestRateModel.getBorrowRate(utilizationRate)).equal( Number(value) + Number(baseRatePerBlock), ); }); - - it("Supply Rate", async () => { - const reserveMantissa = convertToUnit(1, 17); - const oneMinusReserveFactor = Number(expScale) - Number(reserveMantissa); - const borrowRate = (await whitePaperInterestRateModel.getBorrowRate(cash, borrows, reserves, badDebt)).toString(); - const rateToPool = new BigNumber(borrowRate).multipliedBy(oneMinusReserveFactor).dividedBy(expScale).toFixed(0); - const rate = new BigNumber(borrows) - .multipliedBy(expScale) - .dividedBy(Number(cash) + Number(borrows) + Number(badDebt) - Number(reserves)); - const supplyRate = new BigNumber(rateToPool).multipliedBy(rate).dividedBy(expScale).toFixed(0); - - expect( - await whitePaperInterestRateModel.getSupplyRate(cash, borrows, reserves, convertToUnit(1, 17), badDebt), - ).equal(supplyRate); - }); }); diff --git a/tests/hardhat/util/TokenTestHelpers.ts b/tests/hardhat/util/TokenTestHelpers.ts index 9b6554b54..75d74843f 100644 --- a/tests/hardhat/util/TokenTestHelpers.ts +++ b/tests/hardhat/util/TokenTestHelpers.ts @@ -4,21 +4,21 @@ import { BigNumber, BigNumberish, Signer } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers, upgrades } from "hardhat"; +import { convertToUnit } from "../../../helpers/utils"; import { AccessControlManager, Comptroller, ERC20Harness, ERC20Harness__factory, InterestRateModel, - UpgradeableBeacon, StableRateModel, + UpgradeableBeacon, VTokenHarness, VTokenHarness__factory, VToken__factory, } from "../../../typechain"; import { AddressOrContract, getAddress } from "./AddressOrContract"; import { DeployedContract } from "./types"; -import { convertToUnit } from "../../../helpers/utils"; chai.use(smock.matchers); @@ -36,6 +36,7 @@ interface VTokenParameters { protocolShareReserve: AddressOrContract; reserveFactorMantissa: BigNumberish; beacon: UpgradeableBeacon; + stableRateModel: AddressOrContract; } const getNameAndSymbol = async (underlying: AddressOrContract): Promise<[string, string]> => { @@ -43,7 +44,7 @@ const getNameAndSymbol = async (underlying: AddressOrContract): Promise<[string, const name = await underlying_.name(); const symbol = await underlying_.symbol(); return [name, symbol]; -} +}; export type VTokenContracts = { vToken: MockContract; @@ -70,6 +71,12 @@ export const fakeInterestRateModel = async (): Promise> => { + const stableRateModel = await smock.fake("StableRateModel"); + stableRateModel.isInterestRateModel.returns(true); + return stableRateModel; +}; + export const fakeAccessControlManager = async (): Promise => { const accessControlManager = await smock.fake("AccessControlManager"); accessControlManager.isAllowedToCall.returns(true); @@ -109,6 +116,7 @@ const deployVTokenDependencies = async ({ kind })), + stableRateModel: params.stableRateModel || (await fakeStableRateModel()), }; }; @@ -119,21 +127,24 @@ export const makeVToken = async (params, { kind }); const VToken = await ethers.getContractFactory(kind); + const remainingParams = { + underlying_: getAddress(params_.underlying), + comptroller_: getAddress(params_.comptroller), + interestRateModel_: getAddress(params_.interestRateModel), + initialExchangeRateMantissa_: params_.initialExchangeRateMantissa, + name_: params_.name, + symbol_: params_.symbol, + decimals_: params_.decimals, + admin_: getAddress(params_.admin), + accessControlManager_: getAddress(params_.accessControlManager), + shortfall_: getAddress(params_.shortfall), + protocolShareReserve_: getAddress(params_.protocolShareReserve), + reserveFactorMantissa_: params_.reserveFactorMantissa, + stableRateModel_: getAddress(params_.stableRateModel), + }; + const vToken = (await upgrades.deployBeaconProxy(params_.beacon, VToken, [ - getAddress(params_.underlying), - getAddress(params_.comptroller), - getAddress(params_.interestRateModel), - params_.initialExchangeRateMantissa, - params_.name, - params_.symbol, - params_.decimals, - getAddress(params_.admin), - getAddress(params_.accessControlManager), - { - shortfall: getAddress(params_.shortfall), - protocolShareReserve: getAddress(params_.protocolShareReserve), - }, - params_.reserveFactorMantissa, + remainingParams, ])) as DeployedContract; return vToken; }; @@ -144,7 +155,7 @@ export type VTokenTestFixture = { vToken: VTokenHarness; underlying: MockContract; interestRateModel: FakeContract; - stableInterestRateModel: FakeContract; + stableRateModel: FakeContract; }; export async function vTokenTestFixture(): Promise { @@ -155,6 +166,7 @@ export async function vTokenTestFixture(): Promise { const [admin] = await ethers.getSigners(); const underlying = await mockUnderlying("BAT", "BAT"); const interestRateModel = await fakeInterestRateModel(); + const stableRateModel = await fakeStableRateModel(); const vToken = await makeVToken( { underlying, @@ -162,11 +174,12 @@ export async function vTokenTestFixture(): Promise { accessControlManager, admin, interestRateModel, + stableRateModel, }, { kind: "VTokenHarness" }, ); - return { accessControlManager, comptroller, vToken, interestRateModel, underlying, stableInterestRateModel }; + return { accessControlManager, comptroller, vToken, interestRateModel, underlying, stableRateModel }; } type BalancesSnapshot = { From 57cb334a69713df28a4f949360c0616149eb92f7 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 4 Oct 2023 18:29:37 +0530 Subject: [PATCH 19/23] tests: fixed vToken tests after merge conflicts --- tests/hardhat/Tokens/accrueInterestTest.ts | 5 +- tests/hardhat/Tokens/borrowAndRepayTest.ts | 98 +++++++++---------- tests/hardhat/Tokens/mintAndRedeemTest.ts | 6 +- tests/hardhat/Tokens/stableRateRebalancing.ts | 10 +- 4 files changed, 59 insertions(+), 60 deletions(-) diff --git a/tests/hardhat/Tokens/accrueInterestTest.ts b/tests/hardhat/Tokens/accrueInterestTest.ts index a478846f1..690c2e7ef 100644 --- a/tests/hardhat/Tokens/accrueInterestTest.ts +++ b/tests/hardhat/Tokens/accrueInterestTest.ts @@ -25,7 +25,7 @@ async function pretendBlock( await vToken.harnessSetAccrualBlockNumber(accrualBlock); await vToken.harnessSetBlockNumber(BigNumber.from(accrualBlock).add(deltaBlocks)); await vToken.harnessSetBorrowIndex(borrowIndex); - await vToken.harnessSetStableBorrowIndex(borrowIndex); + await vToken.harnessSetStableBorrowIndex(stableBorrowIndex); } async function preAccrue({ @@ -158,11 +158,10 @@ describe("VToken", () => { const receipt = await vToken.accrueInterest(); const expectedInterestAccumulated = expectedTotalBorrows.sub(startingTotalBorrows); - const stableBorrowIndex = 0; await expect(receipt) .to.emit(vToken, "AccrueInterest") - .withArgs(0, expectedInterestAccumulated, expectedBorrowIndex, expectedTotalBorrows, stableBorrowIndex); + .withArgs(0, expectedInterestAccumulated, expectedBorrowIndex, expectedTotalBorrows, "1000001000000000000"); expect(await vToken.accrualBlockNumber()).to.equal(expectedAccrualBlockNumber); expect(await vToken.borrowIndex()).to.equal(expectedBorrowIndex); diff --git a/tests/hardhat/Tokens/borrowAndRepayTest.ts b/tests/hardhat/Tokens/borrowAndRepayTest.ts index 99d3ac254..bbd273f0d 100644 --- a/tests/hardhat/Tokens/borrowAndRepayTest.ts +++ b/tests/hardhat/Tokens/borrowAndRepayTest.ts @@ -29,11 +29,11 @@ async function preBorrow( borrowAmount: BigNumberish, stableRateMantissa: BigNumberish = 0, ) { - const { comptroller, interestRateModel, underlying, vToken, stableInterestRateModel } = contracts; + const { comptroller, interestRateModel, underlying, vToken, stableRateModel } = contracts; comptroller.preBorrowHook.reset(); interestRateModel.getBorrowRate.reset(); - stableInterestRateModel.getBorrowRate.returns(stableRateMantissa); + stableRateModel.getBorrowRate.returns(stableRateMantissa); await underlying.harnessSetBalance(vToken.address, borrowAmount); await vToken.harnessSetFailTransferToAddress(borrower.address, false); @@ -139,7 +139,7 @@ async function repayBorrowStableBehalf( ) { // make sure to have a block delta so we accrue interest await vToken.harnessFastForward(1); - return vToken.connect(payer).repayBorrowStableBehalf(await borrower.getAddress(), repayAmount); + return vToken.connect(payer).repayBorrowStableBehalf(borrower.address, repayAmount); } describe("VToken", function () { @@ -148,16 +148,20 @@ describe("VToken", function () { let vToken: VTokenHarness; let underlying: MockContract; let interestRateModel: FakeContract; - let stableInterestRateModel: FakeContract; + let stableRateModel: FakeContract; let _root: Signer; let borrower: Signer; let benefactor: Signer; let borrowerAddress: string; + let payerAddress: string; beforeEach(async () => { [_root, borrower, benefactor] = await ethers.getSigners(); + borrowerAddress = borrower.address; contracts = await loadFixture(vTokenTestFixture); - ({ comptroller, vToken, underlying, interestRateModel, stableInterestRateModel } = contracts); + ({ comptroller, vToken, underlying, interestRateModel, stableRateModel } = contracts); + await interestRateModel.getBorrowRate.returns(convertToUnit(1, 8)); + await stableRateModel.getBorrowRate.returns(convertToUnit(1, 6)); }); describe("borrowFresh", () => { @@ -253,33 +257,31 @@ describe("VToken", function () { const beforeProtocolCash = await underlying.balanceOf(vToken.address); const beforeProtocolBorrows = await vToken.totalBorrows(); const beforeStableBorrows = await vToken.stableBorrows(); - const beforeAccountCash = await underlying.balanceOf(borrowerAddress); - await stableInterestRateModel.getBorrowRate.returns(convertToUnit(25, 12)); - + const beforeAccountCash = await underlying.balanceOf(borrower.address); const result = await borrowStableFresh(vToken, borrower, borrowAmount); - expect(await underlying.balanceOf(borrowerAddress)).to.equal(beforeAccountCash.add(borrowAmount)); + expect(await underlying.balanceOf(borrower.address)).to.equal(beforeAccountCash.add(borrowAmount)); expect(await underlying.balanceOf(vToken.address)).to.equal(beforeProtocolCash.sub(borrowAmount)); expect(await vToken.totalBorrows()).to.equal(beforeProtocolBorrows.add(borrowAmount)); expect(await vToken.stableBorrows()).to.equal(beforeStableBorrows.add(borrowAmount)); - await expect(result).to.emit(underlying, "Transfer").withArgs(vToken.address, borrowerAddress, borrowAmount); + await expect(result).to.emit(underlying, "Transfer").withArgs(vToken.address, borrower.address, borrowAmount); await expect(result) .to.emit(vToken, "Borrow") - .withArgs(borrowerAddress, borrowAmount, borrowAmount, beforeProtocolBorrows.add(borrowAmount).toString()); + .withArgs(borrower.address, borrowAmount, borrowAmount, beforeProtocolBorrows.add(borrowAmount).toString()); }); it("stores new borrow principal and interest index for stable rate borrowing", async () => { let beforeProtocolBorrows = await vToken.totalBorrows(); - let borrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + let borrowSnap = await vToken.harnessAccountStableBorrows(borrower.address); expect(borrowSnap.principal).to.equal(0); expect(borrowSnap.interestIndex).to.equal(0); expect(await vToken.totalBorrows()).to.equal(0); await pretendStableBorrow(vToken, borrower, 1, 1, borrowAmount, convertToUnit(5, 8)); beforeProtocolBorrows = await vToken.totalBorrows(); - borrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + borrowSnap = await vToken.harnessAccountStableBorrows(borrower.address); expect(borrowSnap.principal).to.equal(convertToUnit("1000", 18)); expect(borrowSnap.interestIndex).to.equal(convertToUnit("1", 18)); @@ -287,7 +289,7 @@ describe("VToken", function () { await borrowStableFresh(vToken, borrower, borrowAmount); beforeProtocolBorrows = await vToken.totalBorrows(); - borrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + borrowSnap = await vToken.harnessAccountStableBorrows(borrower.address); expect(borrowSnap.principal).to.equal(convertToUnit("2000", 18)); expect(borrowSnap.interestIndex).to.equal(convertToUnit("1", 18)); @@ -414,12 +416,11 @@ describe("VToken", function () { it("stores new borrow principal and interest index", async () => { const beforeProtocolBorrows = await vToken.totalBorrows(); const beforeAccountBorrowSnap = await vToken.harnessAccountBorrows(borrower.address); - // expect( await repayBorrowFresh(vToken, payer, borrower, repayAmount); - // ).toSucceed(); + const afterAccountBorrows = await vToken.harnessAccountBorrows(borrower.address); expect(afterAccountBorrows.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); - expect(afterAccountBorrows.interestIndex).to.equal(convertToUnit("1", 18)); + expect(await vToken.borrowIndex()).to.equal(afterAccountBorrows.interestIndex); expect(await vToken.totalBorrows()).to.equal(beforeProtocolBorrows.sub(repayAmount)); }); }); @@ -429,8 +430,8 @@ describe("VToken", function () { describe("repayBorrowFresh for stable rate borrowing", () => { [true, false].forEach(benefactorIsPayer => { let payer: Signer; - let payerAddress: string; const label = benefactorIsPayer ? "benefactor paying" : "borrower paying"; + describe(label, () => { beforeEach(async () => { payer = benefactorIsPayer ? benefactor : borrower; @@ -498,9 +499,10 @@ describe("VToken", function () { it("stores new borrow principal and interest index", async () => { const beforeProtocolBorrows = await vToken.totalBorrows(); - const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrowerAddress); + const beforeAccountBorrowSnap = await vToken.harnessAccountStableBorrows(borrower.address); + await repayBorrowStableFresh(vToken, payer, borrower, repayAmount); - const afterAccountBorrows = await vToken.harnessAccountStableBorrows(borrowerAddress); + const afterAccountBorrows = await vToken.harnessAccountStableBorrows(borrower.address); expect(afterAccountBorrows.principal).to.equal(beforeAccountBorrowSnap.principal.sub(repayAmount)); expect(afterAccountBorrows.interestIndex).to.equal(convertToUnit("1", 18)); expect(await vToken.totalBorrows()).to.equal(beforeProtocolBorrows.sub(repayAmount)); @@ -556,7 +558,7 @@ describe("VToken", function () { }); it("emits a repay borrow failure if interest accrual fails", async () => { - interestRateModel.getBorrowRate.reverts("Oups"); + stableRateModel.getBorrowRate.reverts("Oups"); await expect(repayBorrowStable(vToken, borrower, repayAmount)).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); }); @@ -614,51 +616,51 @@ describe("VToken", function () { await preBorrow(contracts, borrower, borrowAmount, convertToUnit(1, 6)); await borrowStable(vToken, borrower, borrowAmount); await vToken.harnessFastForward(10); - let borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); + let borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.address); expect(borrowSnap1.principal).to.equal(convertToUnit(1, 21)); - expect(borrowSnap1.lastBlockAccrued).to.equal(100001); + expect(borrowSnap1.lastBlockAccrued).to.equal(1); await preBorrow(contracts, borrower2, borrowAmount, convertToUnit(1, 8)); await vToken.harnessSetTotalBorrows(borrowAmount); await borrowStable(vToken, borrower2, borrowAmount); await vToken.harnessFastForward(10); - borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); - let borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.address); + let borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.address); expect(borrowSnap1.principal).to.equal(convertToUnit(1, 21)); expect(borrowSnap2.principal).to.equal(convertToUnit(1, 21)); - expect(borrowSnap1.lastBlockAccrued).to.equal(100001); - expect(borrowSnap2.lastBlockAccrued).to.equal(100012); + expect(borrowSnap1.lastBlockAccrued).to.equal(1); + expect(borrowSnap2.lastBlockAccrued).to.equal(12); await vToken.harnessFastForward(10); - await vToken.harnessUpdateUserStableBorrowBalance(borrower.getAddress()); + await vToken.harnessUpdateUserStableBorrowBalance(borrower.address); - borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); - borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.address); + borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.address); expect(borrowSnap1.principal).to.equal("1000000000030999999999"); - expect(borrowSnap1.lastBlockAccrued).to.equal(100032); - expect(borrowSnap2.lastBlockAccrued).to.equal(100012); + expect(borrowSnap1.lastBlockAccrued).to.equal(32); + expect(borrowSnap2.lastBlockAccrued).to.equal(12); await vToken.harnessFastForward(10); - await vToken.harnessUpdateUserStableBorrowBalance(borrower2.getAddress()); + await vToken.harnessUpdateUserStableBorrowBalance(borrower2.address); - borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); - borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.address); + borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.address); expect(borrowSnap1.principal).to.equal("1000000000030999999999"); expect(borrowSnap2.principal).to.equal("1000000002999999999697"); - expect(borrowSnap1.lastBlockAccrued).to.equal(100032); - expect(borrowSnap2.lastBlockAccrued).to.equal(100042); + expect(borrowSnap1.lastBlockAccrued).to.equal(32); + expect(borrowSnap2.lastBlockAccrued).to.equal(42); await vToken.harnessFastForward(10); - await vToken.harnessUpdateUserStableBorrowBalance(borrower.getAddress()); - await vToken.harnessUpdateUserStableBorrowBalance(borrower2.getAddress()); + await vToken.harnessUpdateUserStableBorrowBalance(borrower.address); + await vToken.harnessUpdateUserStableBorrowBalance(borrower2.address); - borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.getAddress()); - borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.getAddress()); + borrowSnap1 = await vToken.harnessAccountStableBorrows(borrower.address); + borrowSnap2 = await vToken.harnessAccountStableBorrows(borrower2.address); expect(borrowSnap1.principal).to.equal("1000000000050999999998"); expect(borrowSnap2.principal).to.equal("1000000004000000002595"); - expect(borrowSnap1.lastBlockAccrued).to.equal(100052); - expect(borrowSnap2.lastBlockAccrued).to.equal(100052); + expect(borrowSnap1.lastBlockAccrued).to.equal(52); + expect(borrowSnap2.lastBlockAccrued).to.equal(52); }); it("Average stable borrow rate, stable borrows after borrow and repay", async () => { @@ -681,7 +683,7 @@ describe("VToken", function () { averageBorrowRate = await vToken.averageStableBorrowRate(); expect(averageBorrowRate).to.equal(50500000); - await underlying.harnessSetFailTransferFromAddress(await borrower.getAddress(), false); + await underlying.harnessSetFailTransferFromAddress(borrower.address, false); await preApprove(underlying, vToken, borrower, borrowAmount, { faucet: true }); await vToken.connect(borrower).repayBorrowStable(borrowAmount); @@ -702,7 +704,7 @@ describe("VToken", function () { }); it("emits a repay borrow failure if interest accrual fails", async () => { - interestRateModel.getBorrowRate.reverts("Oups"); + stableRateModel.getBorrowRate.reverts("Oups"); await expect(repayBorrowBehalf(vToken, payer, borrower, repayAmount)).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); }); @@ -724,7 +726,6 @@ describe("VToken", function () { describe("repayBorrowBehalf for stable rate", () => { let payer: Signer; - let payerAddress: string; beforeEach(async () => { payer = benefactor; @@ -732,11 +733,6 @@ describe("VToken", function () { await preRepay(contracts, payer, borrower, repayAmount, 1); }); - it("emits a repay borrow failure if interest accrual fails", async () => { - interestRateModel.getBorrowRate.reverts("Oups"); - await expect(repayBorrowStableBehalf(vToken, payer, borrower, repayAmount)).to.be.reverted; // With("INTEREST_RATE_MODEL_ERROR"); - }); - it("returns error from repayBorrowFresh without emitting any extra logs", async () => { await underlying.harnessSetBalance(payerAddress, 1); await expect(repayBorrowStableBehalf(vToken, payer, borrower, repayAmount)).to.be.revertedWith( diff --git a/tests/hardhat/Tokens/mintAndRedeemTest.ts b/tests/hardhat/Tokens/mintAndRedeemTest.ts index b429ad469..990b0e5ff 100644 --- a/tests/hardhat/Tokens/mintAndRedeemTest.ts +++ b/tests/hardhat/Tokens/mintAndRedeemTest.ts @@ -260,11 +260,11 @@ describe("VToken", function () { it("emits an AccrueInterest event", async () => { await expect(await quickMint(underlying, vToken, minter, mintAmount)) .to.emit(vToken, "AccrueInterest") - .withArgs("0", "0", "1000000000000000000", "0"); + .withArgs("0", "0", "1000000000000000000", "0", "1000000000000000000"); await expect(await quickMint(underlying, vToken, minter, mintAmount)) .to.emit(vToken, "AccrueInterest") - .withArgs("10000000000000000000000", "0", "1000000000000000000", "0"); + .withArgs("10000000000000000000000", "0", "1000000000000000000", "0", "1000000000000000000"); }); }); @@ -402,7 +402,7 @@ describe("VToken", function () { it("emits an AccrueInterest event", async () => { await expect(await quickRedeem(vToken, redeemer, redeemTokens, { exchangeRate })) .to.emit(vToken, "AccrueInterest") - .withArgs("50000000000000000000000000", "0", "1000000000000000000", "0"); + .withArgs("50000000000000000000000000", "0", "1000000000000000000", "0", "1000000000000000000"); }); }); }); diff --git a/tests/hardhat/Tokens/stableRateRebalancing.ts b/tests/hardhat/Tokens/stableRateRebalancing.ts index f2bf471a2..a7099425b 100644 --- a/tests/hardhat/Tokens/stableRateRebalancing.ts +++ b/tests/hardhat/Tokens/stableRateRebalancing.ts @@ -43,7 +43,7 @@ describe("VToken", function () { contracts = await loadFixture(vTokenTestFixture); ({ vToken, interestRateModel, stableRateModel } = contracts); await stableRateModel.getBorrowRate.returns(convertToUnit(1, 6)); - await interestRateModel.getBorrowRate.returns(convertToUnit(1, 12)); + await interestRateModel.getBorrowRate.returns(convertToUnit(1, 8)); }); describe("Rebalance Stable rate", () => { @@ -81,8 +81,9 @@ describe("VToken", function () { }); it("Revert on average borrow rate higher than variable rate threshold", async () => { - await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(7, 17)); - await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); + await stableRateModel.getBorrowRate.returns(convertToUnit(1, 10)); + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(5, 17)); + await vToken.setRebalanceRateFractionThreshold(convertToUnit(5, 17)); await preBorrow(contracts, borrower, borrowAmount); await borrowStable(vToken, borrower, convertToUnit(900, 18)); await expect(vToken.validateRebalanceStableBorrowRate()).to.be.revertedWith( @@ -99,6 +100,8 @@ describe("VToken", function () { }); it("Rebalacing the stable rate for a user", async () => { + await stableRateModel.getBorrowRate.returns(convertToUnit(1, 6)); + await vToken.setRebalanceUtilizationRateThreshold(convertToUnit(7, 17)); await vToken.setRebalanceRateFractionThreshold(convertToUnit(50, 17)); await preBorrow(contracts, borrower, borrowAmount); @@ -107,6 +110,7 @@ describe("VToken", function () { let accountBorrows = await vToken.harnessAccountStableBorrows(borrower.getAddress()); expect(accountBorrows.stableRateMantissa).to.equal(convertToUnit(1, 6)); + await stableRateModel.getBorrowRate.returns(convertToUnit(1, 8)); await vToken.rebalanceStableBorrowRate(borrower.address); accountBorrows = await vToken.harnessAccountStableBorrows(borrower.getAddress()); From 8fa918639e1be0ce7a83db9e5f9ce2e77d8659e4 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 5 Oct 2023 01:06:05 +0530 Subject: [PATCH 20/23] tests: fixed integration tests, deployment config, deployment scripts, and lint errors --- contracts/Comptroller.sol | 8 +- contracts/InterestRate/StableRateModel.sol | 17 +- contracts/InterestRateModel.sol | 14 +- contracts/VToken.sol | 300 +++++++++++---------- contracts/VTokenInterfaces.sol | 52 ++-- contracts/test/VTokenHarness.sol | 62 ++--- deploy/009-deploy-vtokens.ts | 46 ++-- helpers/deploymentConfig.ts | 141 ++++++++-- tests/hardhat/Tokens/mintAndRedeemTest.ts | 4 +- 9 files changed, 386 insertions(+), 258 deletions(-) diff --git a/contracts/Comptroller.sol b/contracts/Comptroller.sol index c040c0add..b9301f753 100644 --- a/contracts/Comptroller.sol +++ b/contracts/Comptroller.sol @@ -580,10 +580,6 @@ contract Comptroller is } } - function preSwapBorrowRateModeHook(address vToken) external view override { - _checkActionPauseState(vToken, Action.SWAP_RATE_MODE); - } - /*** Pool-level operations ***/ /** @@ -987,6 +983,10 @@ contract Comptroller is _setMaxLoopsLimit(limit); } + function preSwapBorrowRateModeHook(address vToken) external view override { + _checkActionPauseState(vToken, Action.SWAP_RATE_MODE); + } + /** * @notice Determine the current account liquidity with respect to liquidation threshold requirements * @dev The interface of this function is intentionally kept compatible with Compound and Venus Core diff --git a/contracts/InterestRate/StableRateModel.sol b/contracts/InterestRate/StableRateModel.sol index c614487c6..a97b58d96 100644 --- a/contracts/InterestRate/StableRateModel.sol +++ b/contracts/InterestRate/StableRateModel.sol @@ -1,19 +1,14 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.13; -import "hardhat/console.sol"; - /** * @title Logic for Venus stable rate. */ contract StableRateModel { - /// @notice Indicator that this is an InterestRateModel contract (for inspection) - bool public constant isInterestRateModel = true; - uint256 private constant BASE = 1e18; /// @notice The approximate number of blocks per year that is assumed by the interest rate model - uint256 public constant blocksPerYear = 2102400; + uint256 public constant BLOCK_PER_YEAR = 2102400; /// @notice The stable base interest rate which is the y-intercept when utilization rate is 0(also known by base_premium) uint256 public baseRatePerBlock; @@ -77,6 +72,14 @@ contract StableRateModel { return (variableBorrowRate + baseRatePerBlock + ((stableRatePremium * excessLoanRatio) / BASE)); } + /** + * @notice Indicator that this is an InterestRateModel contract (for inspection) + * @return Always true + */ + function isStableRateModel() external pure virtual returns (bool) { + return true; + } + /** * @notice Calculates the ratio of the stable borrows to total borrows * @param stableBorrows The amount of stable borrows in the market @@ -103,7 +106,7 @@ contract StableRateModel { uint256 stableRatePremium_, uint256 optimalStableLoanRatio_ ) internal { - baseRatePerBlock = baseRatePerYear_ / blocksPerYear; + baseRatePerBlock = baseRatePerYear_ / BLOCK_PER_YEAR; stableRatePremium = stableRatePremium_; optimalStableLoanRatio = optimalStableLoanRatio_; diff --git a/contracts/InterestRateModel.sol b/contracts/InterestRateModel.sol index 42c317130..e1fca81b1 100644 --- a/contracts/InterestRateModel.sol +++ b/contracts/InterestRateModel.sol @@ -6,6 +6,13 @@ pragma solidity 0.8.13; * @author Compound */ abstract contract InterestRateModel { + /** + * @notice Calculates the current borrow interest rate per block + * @param utilizationRate The utilization rate per total borrows and cash available + * @return The borrow rate per block (as a percentage, and scaled by 1e18) + */ + function getBorrowRate(uint256 utilizationRate) external view virtual returns (uint256); + /** * @notice Indicator that this is an InterestRateModel contract (for inspection) * @return Always true @@ -13,11 +20,4 @@ abstract contract InterestRateModel { function isInterestRateModel() external pure virtual returns (bool) { return true; } - - /** - * @notice Calculates the current borrow interest rate per block - * @param utilizationRate The utilization rate per total borrows and cash available - * @return The borrow rate per block (as a percentage, and scaled by 1e18) - */ - function getBorrowRate(uint256 utilizationRate) external view virtual returns (uint256); } diff --git a/contracts/VToken.sol b/contracts/VToken.sol index 3de4485f2..c0274ad4c 100644 --- a/contracts/VToken.sol +++ b/contracts/VToken.sol @@ -573,6 +573,155 @@ contract VToken is emit RebalanceRateFractionThresholdUpdated(oldThreshold, rebalanceRateFractionThreshold); } + /** + * @notice Calculates the exchange rate from the underlying to the VToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return exchangeRate Calculated exchange rate scaled by 1e18 + */ + function totalBorrowsCurrent() external override nonReentrant returns (uint256) { + accrueInterest(); + return totalBorrows; + } + + /** + * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + * @param account The address whose balance should be calculated after updating borrowIndex + * @return borrowBalance The calculated balance + */ + function borrowBalanceCurrent(address account) external override nonReentrant returns (uint256) { + accrueInterest(); + (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); + return _borrowBalanceStored(account) + stableBorrowAmount; + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits Borrow event; may emit AccrueInterest + * @custom:error BorrowCashNotAvailable is thrown when the protocol has insufficient cash + * @custom:access Not restricted + */ + function borrow(uint256 borrowAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // borrowFresh emits borrow-specific logs on errors, so we don't need to + _borrowFresh(payable(msg.sender), borrowAmount, InterestRateMode.VARIABLE); + return NO_ERROR; + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow at stable borrow rate + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits Borrow event; may emit AccrueInterest + * @custom:error BorrowCashNotAvailable is thrown when the protocol has insufficient cash + * @custom:access Not restricted + */ + function borrowStable(uint256 borrowAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // borrowFresh emits borrow-specific logs on errors, so we don't need to + _borrowFresh(payable(msg.sender), borrowAmount, InterestRateMode.STABLE); + return NO_ERROR; + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrow(uint256 repayAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, msg.sender, repayAmount, InterestRateMode.VARIABLE); + return NO_ERROR; + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrowStable(uint256 repayAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, msg.sender, repayAmount, InterestRateMode.STABLE); + return NO_ERROR; + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrowBehalf(address borrower, uint256 repayAmount) external override nonReentrant returns (uint256) { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, borrower, repayAmount, InterestRateMode.VARIABLE); + return NO_ERROR; + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay, or -1 for the full outstanding amount + * @return error Always NO_ERROR for compatilibily with Venus core tooling + * @custom:events Emits RepayBorrow event; may emit AccrueInterest + * @custom:access Not restricted + */ + function repayBorrowStableBehalf( + address borrower, + uint256 repayAmount + ) external override nonReentrant returns (uint256) { + accrueInterest(); + // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + _repayBorrowFresh(msg.sender, borrower, repayAmount, InterestRateMode.STABLE); + return NO_ERROR; + } + + /** + * @notice sets protocol share accumulated from liquidations + * @dev must be less than liquidation incentive - 1 + * @param newProtocolSeizeShareMantissa_ new protocol share mantissa + * @custom:events Emits NewProtocolSeizeShare event on success + * @custom:error Unauthorized is thrown when the call is not authorized by AccessControlManager + * @custom:error ProtocolSeizeShareTooBig is thrown when the new seize share is too high + * @custom:access Controlled by AccessControlManager + */ + function setProtocolSeizeShare(uint256 newProtocolSeizeShareMantissa_) external { + _checkAccessAllowed("setProtocolSeizeShare(uint256)"); + + uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).liquidationIncentiveMantissa(); + if (newProtocolSeizeShareMantissa_ + 1e18 > liquidationIncentive) { + revert ProtocolSeizeShareTooBig(); + } + + uint256 oldProtocolSeizeShareMantissa = protocolSeizeShareMantissa; + protocolSeizeShareMantissa = newProtocolSeizeShareMantissa_; + emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa_); + } + + /** + * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @custom:events Emits NewMarketInterestRateModel event; may emit AccrueInterest + * @custom:error Unauthorized is thrown when the call is not authorized by AccessControlManager + * @custom:access Controlled by AccessControlManager + */ + function setInterestRateModel(InterestRateModel newInterestRateModel) external override { + _checkAccessAllowed("setInterestRateModel(address)"); + + accrueInterest(); + _setInterestRateModelFresh(newInterestRateModel); + } + /** * @notice Get the current allowance from `owner` for `spender` * @param owner The address of the account which owns the tokens to be spent @@ -645,27 +794,6 @@ contract VToken is (MANTISSA_ONE * MANTISSA_ONE); } - /** - * @notice Calculates the exchange rate from the underlying to the VToken - * @dev This function does not accrue interest before calculating the exchange rate - * @return exchangeRate Calculated exchange rate scaled by 1e18 - */ - function totalBorrowsCurrent() external override nonReentrant returns (uint256) { - accrueInterest(); - return totalBorrows; - } - - /** - * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex - * @param account The address whose balance should be calculated after updating borrowIndex - * @return borrowBalance The calculated balance - */ - function borrowBalanceCurrent(address account) external override nonReentrant returns (uint256) { - accrueInterest(); - (uint256 stableBorrowAmount, , ) = _stableBorrowBalanceStored(account); - return _borrowBalanceStored(account) + stableBorrowAmount; - } - /** * @notice Calculates the exchange rate from the underlying to the VToken * @dev This function does not accrue interest before calculating the exchange rate @@ -804,7 +932,7 @@ contract VToken is /// Average market borrow rate should be less than the rebalanceRateFractionThreshold fraction of /// variable borrow rate when utilization rate is rebalanceUtilizationRateThreshold require( - _averageMarketBorrowRate() < (variableBorrowRate * rebalanceRateFractionThreshold), + _averageMarketBorrowRate() < ((variableBorrowRate * rebalanceRateFractionThreshold) / EXP_SCALE), "vToken: average borrow rate higher than variable rate threshold." ); } @@ -909,7 +1037,10 @@ contract VToken is uint256 stableIndexPrior = stableBorrowIndex; uint256 stableBorrowRateMantissa = stableBorrowRatePerBlock(); - require(stableBorrowRateMantissa <= stableBorrowRateMaxMantissa, "vToken: stable borrow rate is absurdly high"); + require( + stableBorrowRateMantissa <= MAX_STABLE_BORROW_RATE_MANTISSA, + "vToken: stable borrow rate is absurdly high" + ); Exp memory simpleStableInterestFactor = mul_(Exp({ mantissa: stableBorrowRateMantissa }), blockDelta); @@ -1061,28 +1192,6 @@ contract VToken is emit Redeem(redeemer, redeemAmount, redeemTokens, balanceAfter); } - /** - * @notice Sender borrows assets from the protocol to their own address - * @param borrowAmount The amount of the underlying asset to borrow - * @return error Always NO_ERROR for compatilibily with Venus core tooling - * @custom:events Emits Borrow event; may emit AccrueInterest - * @custom:error BorrowCashNotAvailable is thrown when the protocol has insufficient cash - * @custom:access Not restricted - */ - function borrow(uint256 borrowAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // borrowFresh emits borrow-specific logs on errors, so we don't need to - _borrowFresh(payable(msg.sender), borrowAmount, InterestRateMode.VARIABLE); - return NO_ERROR; - } - - function borrowStable(uint256 borrowAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // borrowFresh emits borrow-specific logs on errors, so we don't need to - _borrowFresh(payable(msg.sender), borrowAmount, InterestRateMode.STABLE); - return NO_ERROR; - } - /** * @notice Users borrow assets from the protocol to their own address * @param borrower User who borrows the assets @@ -1175,67 +1284,6 @@ contract VToken is emit Borrow(borrower, borrowAmount, accountBorrowsNew, totalBorrowsNew); } - /** - * @notice Sender repays their own borrow - * @param repayAmount The amount to repay, or -1 for the full outstanding amount - * @return error Always NO_ERROR for compatilibily with Venus core tooling - * @custom:events Emits RepayBorrow event; may emit AccrueInterest - * @custom:access Not restricted - */ - function repayBorrow(uint256 repayAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - _repayBorrowFresh(msg.sender, msg.sender, repayAmount, InterestRateMode.VARIABLE); - return NO_ERROR; - } - - /** - * @notice Sender repays their own borrow - * @param repayAmount The amount to repay, or -1 for the full outstanding amount - * @return error Always NO_ERROR for compatilibily with Venus core tooling - * @custom:events Emits RepayBorrow event; may emit AccrueInterest - * @custom:access Not restricted - */ - function repayBorrowStable(uint256 repayAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - _repayBorrowFresh(msg.sender, msg.sender, repayAmount, InterestRateMode.STABLE); - return NO_ERROR; - } - - /** - * @notice Sender repays a borrow belonging to borrower - * @param borrower the account with the debt being payed off - * @param repayAmount The amount to repay, or -1 for the full outstanding amount - * @return error Always NO_ERROR for compatilibily with Venus core tooling - * @custom:events Emits RepayBorrow event; may emit AccrueInterest - * @custom:access Not restricted - */ - function repayBorrowBehalf(address borrower, uint256 repayAmount) external override nonReentrant returns (uint256) { - accrueInterest(); - // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - _repayBorrowFresh(msg.sender, borrower, repayAmount, InterestRateMode.VARIABLE); - return NO_ERROR; - } - - /** - * @notice Sender repays a borrow belonging to borrower - * @param borrower the account with the debt being payed off - * @param repayAmount The amount to repay, or -1 for the full outstanding amount - * @return error Always NO_ERROR for compatilibily with Venus core tooling - * @custom:events Emits RepayBorrow event; may emit AccrueInterest - * @custom:access Not restricted - */ - function repayBorrowStableBehalf( - address borrower, - uint256 repayAmount - ) external override nonReentrant returns (uint256) { - accrueInterest(); - // _repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - _repayBorrowFresh(msg.sender, borrower, repayAmount, InterestRateMode.STABLE); - return NO_ERROR; - } - /** * @notice Borrows are repaid by another user (possibly the borrower). * @param payer the account paying off the borrow @@ -1586,28 +1634,6 @@ contract VToken is emit ReservesAdded(address(this), protocolSeizeAmount, totalReservesNew); } - /** - * @notice sets protocol share accumulated from liquidations - * @dev must be less than liquidation incentive - 1 - * @param newProtocolSeizeShareMantissa_ new protocol share mantissa - * @custom:events Emits NewProtocolSeizeShare event on success - * @custom:error Unauthorized is thrown when the call is not authorized by AccessControlManager - * @custom:error ProtocolSeizeShareTooBig is thrown when the new seize share is too high - * @custom:access Controlled by AccessControlManager - */ - function setProtocolSeizeShare(uint256 newProtocolSeizeShareMantissa_) external { - _checkAccessAllowed("setProtocolSeizeShare(uint256)"); - - uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).liquidationIncentiveMantissa(); - if (newProtocolSeizeShareMantissa_ + 1e18 > liquidationIncentive) { - revert ProtocolSeizeShareTooBig(); - } - - uint256 oldProtocolSeizeShareMantissa = protocolSeizeShareMantissa; - protocolSeizeShareMantissa = newProtocolSeizeShareMantissa_; - emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa_); - } - function _setComptroller(ComptrollerInterface newComptroller) internal { ComptrollerInterface oldComptroller = comptroller; // Ensure invoke comptroller.isComptroller() returns true @@ -1709,21 +1735,6 @@ contract VToken is emit ReservesReduced(protocolShareReserve, reduceAmount, totalReservesNew); } - /** - * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh - * @dev Admin function to accrue interest and update the interest rate model - * @param newInterestRateModel the new interest rate model to use - * @custom:events Emits NewMarketInterestRateModel event; may emit AccrueInterest - * @custom:error Unauthorized is thrown when the call is not authorized by AccessControlManager - * @custom:access Controlled by AccessControlManager - */ - function setInterestRateModel(InterestRateModel newInterestRateModel) public override { - _checkAccessAllowed("setInterestRateModel(address)"); - - accrueInterest(); - _setInterestRateModelFresh(newInterestRateModel); - } - /** * @notice updates the interest rate model (*requires fresh interest accrual) * @dev Admin function to update the interest rate model @@ -1771,7 +1782,7 @@ contract VToken is oldStableInterestRateModel = stableRateModel; // Ensure invoke newInterestRateModel.isInterestRateModel() returns true - require(newStableInterestRateModel.isInterestRateModel(), "marker method returned false"); + require(newStableInterestRateModel.isStableRateModel(), "marker method returned false"); // Set the interest rate model to newStableInterestRateModel stableRateModel = newStableInterestRateModel; @@ -1872,6 +1883,7 @@ contract VToken is // Initialize block number and borrow index (block number mocks depend on comptroller being set) accrualBlockNumber = _getBlockNumber(); borrowIndex = MANTISSA_ONE; + stableBorrowIndex = MANTISSA_ONE; // Set the interest rate model (depends on block number / borrow index) _setInterestRateModelFresh(params.interestRateModel_); diff --git a/contracts/VTokenInterfaces.sol b/contracts/VTokenInterfaces.sol index 021e52a31..80904056f 100644 --- a/contracts/VTokenInterfaces.sol +++ b/contracts/VTokenInterfaces.sol @@ -39,6 +39,22 @@ contract VTokenStorage { StableRateModel stableRateModel_; } + struct StableBorrowSnapshot { + uint256 principal; + uint256 stableRateMantissa; + uint256 interestIndex; + uint256 lastBlockAccrued; + } + + /** + * @notice Types of the Interest rate model + */ + enum InterestRateMode { + NONE, + STABLE, + VARIABLE + } + /** * @dev Guard variable for re-entrancy checks */ @@ -76,7 +92,7 @@ contract VTokenStorage { uint256 internal constant MAX_RESERVE_FACTOR_MANTISSA = 1e18; // Maximum stable borrow rate that can ever be applied (.0005% / block) - uint256 internal constant stableBorrowRateMaxMantissa = 0.0005e16; + uint256 internal constant MAX_STABLE_BORROW_RATE_MANTISSA = 0.0005e16; /** * @notice Contract which oversees inter-vToken operations @@ -165,25 +181,9 @@ contract VTokenStorage { */ uint256 public averageStableBorrowRate; - struct StableBorrowSnapshot { - uint256 principal; - uint256 stableRateMantissa; - uint256 interestIndex; - uint256 lastBlockAccrued; - } - // Mapping of account addresses to outstanding stable borrow balances mapping(address => StableBorrowSnapshot) internal accountStableBorrows; - /** - * @notice Types of the Interest rate model - */ - enum InterestRateMode { - NONE, - STABLE, - VARIABLE - } - /// @notice Utilization rate threshold for rebalancing stable borrowing rate uint256 internal rebalanceUtilizationRateThreshold; @@ -431,15 +431,6 @@ abstract contract VTokenInterface is VTokenStorage { function addReserves(uint256 addAmount) external virtual; - function utilizationRate( - uint256 cash, - uint256 borrows, - uint256 reserves, - uint256 badDebt - ) public pure virtual returns (uint256); - - function stableBorrowRatePerBlock() public view virtual returns (uint256); - function totalBorrowsCurrent() external virtual returns (uint256); function balanceOfUnderlying(address owner) external virtual returns (uint256); @@ -477,4 +468,13 @@ abstract contract VTokenInterface is VTokenStorage { } function setStableInterestRateModel(StableRateModel newStableInterestRateModel) public virtual; + + function stableBorrowRatePerBlock() public view virtual returns (uint256); + + function utilizationRate( + uint256 cash, + uint256 borrows, + uint256 reserves, + uint256 badDebt + ) public pure virtual returns (uint256); } diff --git a/contracts/test/VTokenHarness.sol b/contracts/test/VTokenHarness.sol index baaa9f2c6..4227ef5ad 100644 --- a/contracts/test/VTokenHarness.sol +++ b/contracts/test/VTokenHarness.sol @@ -18,21 +18,6 @@ contract VTokenHarness is VToken { super._initialize(params); } - function _exchangeRateStored() internal view override returns (uint256) { - if (harnessExchangeRateStored) { - return harnessExchangeRate; - } - return super._exchangeRateStored(); - } - - function _getBlockNumber() internal view override returns (uint256) { - return blockNumber; - } - - function getStableBorrowRateMaxMantissa() external pure returns (uint256) { - return stableBorrowRateMaxMantissa; - } - function harnessSetAccrualBlockNumber(uint256 accrualBlockNumber_) external { accrualBlockNumber = accrualBlockNumber_; } @@ -88,17 +73,6 @@ contract VTokenHarness is VToken { super._redeemFresh(account, vTokenAmount, underlyingAmount); } - function harnessAccountStableBorrows( - address account - ) - external - view - returns (uint256 principal, uint256 interestIndex, uint256 lastBlockAccrued, uint256 stableRateMantissa) - { - StableBorrowSnapshot memory snapshot = accountStableBorrows[account]; - return (snapshot.principal, snapshot.interestIndex, snapshot.lastBlockAccrued, snapshot.stableRateMantissa); - } - function harnessSetAccountBorrows(address account, uint256 principal, uint256 interestIndex) external { accountBorrows[account] = BorrowSnapshot({ principal: principal, interestIndex: interestIndex }); } @@ -169,10 +143,25 @@ contract VTokenHarness is VToken { return (snapshot.principal, snapshot.interestIndex); } + function harnessAccountStableBorrows( + address account + ) + external + view + returns (uint256 principal, uint256 interestIndex, uint256 lastBlockAccrued, uint256 stableRateMantissa) + { + StableBorrowSnapshot memory snapshot = accountStableBorrows[account]; + return (snapshot.principal, snapshot.interestIndex, snapshot.lastBlockAccrued, snapshot.stableRateMantissa); + } + function getBorrowRateMaxMantissa() external pure returns (uint256) { return MAX_BORROW_RATE_MANTISSA; } + function getStableBorrowRateMaxMantissa() external pure returns (uint256) { + return MAX_STABLE_BORROW_RATE_MANTISSA; + } + function harnessSetInterestRateModel(address newInterestRateModelAddress) public { interestRateModel = InterestRateModel(newInterestRateModelAddress); } @@ -181,11 +170,6 @@ contract VTokenHarness is VToken { comptroller.preBorrowHook(address(this), msg.sender, amount); } - function _doTransferOut(address to, uint256 amount) internal override { - require(failTransferToAddresses[to] == false, "HARNESS_TOKEN_TRANSFER_OUT_FAILED"); - return super._doTransferOut(to, amount); - } - function harnessSetAvgStableBorrowRate(uint256 averageStableBorrowRate_) public { averageStableBorrowRate = averageStableBorrowRate_; } @@ -201,6 +185,22 @@ contract VTokenHarness is VToken { function harnessUpdateUserStableBorrowBalance(address account) public returns (uint256) { return _updateUserStableBorrowBalance(account); } + + function _doTransferOut(address to, uint256 amount) internal override { + require(failTransferToAddresses[to] == false, "HARNESS_TOKEN_TRANSFER_OUT_FAILED"); + return super._doTransferOut(to, amount); + } + + function _exchangeRateStored() internal view override returns (uint256) { + if (harnessExchangeRateStored) { + return harnessExchangeRate; + } + return super._exchangeRateStored(); + } + + function _getBlockNumber() internal view override returns (uint256) { + return blockNumber; + } } contract VTokenScenario is VToken { diff --git a/deploy/009-deploy-vtokens.ts b/deploy/009-deploy-vtokens.ts index 96907417f..dba4b4933 100644 --- a/deploy/009-deploy-vtokens.ts +++ b/deploy/009-deploy-vtokens.ts @@ -59,6 +59,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { jumpMultiplierPerYear, kink_, reserveFactor, + baseRatePerYearForStable, + stableRatePremium, + optimalStableLoanRatio, } = vtoken; const token = getTokenConfig(asset, tokensConfig); @@ -99,32 +102,45 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { rateModelAddress = result.address; } + const [b, p, o] = [baseRatePerYearForStable, stableRatePremium, optimalStableLoanRatio].map(mantissaToBps); + const stableRateModelName = `JumpRateModelV2_base${b}st_premium${p}st_loan_ratio${o}`; + console.log(`Deploying stable rate model ${stableRateModelName}`); + const result: DeployResult = await deploy(stableRateModelName, { + from: deployer, + contract: "StableRateModel", + args: [baseRatePerYearForStable, stableRatePremium, optimalStableLoanRatio, accessControlManagerAddress], + log: true, + autoMine: true, + }); + const stableRateModelAddress = result.address; + console.log(`Deploying VToken proxy for ${symbol}`); const VToken = await ethers.getContractFactory("VToken"); const underlyingDecimals = Number(await tokenContract.decimals()); const vTokenDecimals = 8; const treasuryAddress = await toAddress(preconfiguredAddresses.VTreasury || "VTreasury", hre); - const args = [ - tokenContract.address, - comptrollerProxy.address, - rateModelAddress, - parseUnits("1", underlyingDecimals + 18 - vTokenDecimals), - name, - symbol, - vTokenDecimals, - preconfiguredAddresses.NormalTimelock || deployer, // admin - accessControlManagerAddress, - [ADDRESS_ONE, treasuryAddress], - reserveFactor, - ]; + const args = { + underlying_: tokenContract.address, + comptroller_: comptrollerProxy.address, + interestRateModel_: rateModelAddress, + initialExchangeRateMantissa_: parseUnits("1", underlyingDecimals + 18 - vTokenDecimals), + name_: name, + symbol_: symbol, + decimals_: vTokenDecimals, + admin_: preconfiguredAddresses.NormalTimelock || deployer, // admin + accessControlManager_: accessControlManagerAddress, + shortfall_: ADDRESS_ONE, + protocolShareReserve_: treasuryAddress, + reserveFactorMantissa_: reserveFactor, + stableRateModel_: stableRateModelAddress, + }; await deploy(`VToken_${symbol}`, { from: deployer, contract: "BeaconProxy", - args: [vTokenBeacon.address, VToken.interface.encodeFunctionData("initialize", args)], + args: [vTokenBeacon.address, VToken.interface.encodeFunctionData("initialize", [args])], log: true, autoMine: true, }); - console.log(`-----------------------------------------`); } } }; diff --git a/helpers/deploymentConfig.ts b/helpers/deploymentConfig.ts index d99e006f7..8d8d67d5f 100644 --- a/helpers/deploymentConfig.ts +++ b/helpers/deploymentConfig.ts @@ -65,7 +65,8 @@ export type VTokenConfig = { initialSupply: string; supplyCap: string; borrowCap: string; - baseRatePerBlockForStable: number; + vTokenReceiver: string; + baseRatePerYearForStable: string; stableRatePremium: string; optimalStableLoanRatio: string; }; @@ -74,7 +75,7 @@ export type AccessControlEntry = { caller: string; target: string; method: string; -} +}; export enum InterestRateModels { WhitePaper, @@ -302,11 +303,12 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.7, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(10000, 18), - borrowCap: convertToUnit(10000, 18), - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), + supplyCap: convertToUnit(932019, 18), + borrowCap: convertToUnit(478980, 18), + vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, { name: "Venus BTCB", @@ -321,11 +323,12 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.8, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(10000, 18), - borrowCap: convertToUnit(10000, 18), - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), + supplyCap: convertToUnit(1000, 18), + borrowCap: convertToUnit(1000, 18), + vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, ], rewards: [ @@ -366,6 +369,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(3000000, 18), borrowCap: convertToUnit(3000000, 18), vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, { name: "Venus ankrBNB", @@ -380,11 +386,12 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.7, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(10000, 18), - borrowCap: convertToUnit(10000, 18), - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), + supplyCap: convertToUnit(100, 18), + borrowCap: convertToUnit(100, 18), + vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, { name: "Venus MBOX", @@ -402,6 +409,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(7000000, 18), borrowCap: convertToUnit(3184294, 18), vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, { name: "Venus NFT", @@ -419,6 +429,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(84985800573, 18), borrowCap: convertToUnit(24654278679, 18), vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, { name: "Venus RACA", @@ -436,6 +449,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(23758811062, 18), borrowCap: convertToUnit(3805812642, 18), vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, { name: "Venus stkBNB", @@ -450,11 +466,32 @@ export const globalConfig: NetworkConfig = { liquidationThreshold: convertToUnit(0.7, 18), reserveFactor: convertToUnit(0.25, 18), initialSupply: convertToUnit(10, 18), - supplyCap: convertToUnit(10000, 18), - borrowCap: convertToUnit(10000, 18), - baseRatePerBlockForStable: 0, - stableRatePremium: convertToUnit(2, 12), - optimalStableLoanRatio: convertToUnit(5, 17), + supplyCap: convertToUnit(1963, 18), + borrowCap: convertToUnit(324, 18), + vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), + }, + { + name: "Venus USDD", + asset: "USDD", + symbol: "vUSDD", + rateModel: InterestRateModels.JumpRate.toString(), + baseRatePerYear: "0", + multiplierPerYear: convertToUnit(0.15, 18), + jumpMultiplierPerYear: convertToUnit(3, 18), + kink_: convertToUnit(0.6, 18), + collateralFactor: convertToUnit(0.7, 18), + liquidationThreshold: convertToUnit(0.8, 18), + reserveFactor: convertToUnit(0.1, 18), + initialSupply: convertToUnit(10, 18), + supplyCap: convertToUnit(10601805, 18), + borrowCap: convertToUnit(1698253, 18), + vTokenReceiver: "account:deployer", + baseRatePerYearForStable: convertToUnit(2, 16), + stableRatePremium: convertToUnit(0.25, 18), + optimalStableLoanRatio: convertToUnit(4, 18), }, ], rewards: [ @@ -653,6 +690,13 @@ export const globalConfig: NetworkConfig = { decimals: 18, tokenAddress: ethers.constants.AddressZero, }, + { + isMock: false, + name: "Synclub Staked BNB", + symbol: "SnBNB", + decimals: 18, + tokenAddress: "0xd2aF6A916Bc77764dc63742BC30f71AF4cF423F4", + }, ], poolConfig: [ { @@ -1054,6 +1098,23 @@ export const globalConfig: NetworkConfig = { borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", }, + { + name: "Venus SnBNB (Liquid Staked BNB)", + asset: "SnBNB", + symbol: "vSnBNB_LiquidStakedBNB", + rateModel: InterestRateModels.JumpRate.toString(), + baseRatePerYear: convertToUnit("0.02", 18), + multiplierPerYear: convertToUnit("0.2", 18), + jumpMultiplierPerYear: convertToUnit("3", 18), + kink_: convertToUnit("0.5", 18), + collateralFactor: convertToUnit("0.87", 18), + liquidationThreshold: convertToUnit("0.9", 18), + reserveFactor: convertToUnit("0.25", 18), + initialSupply: convertToUnit("47", 18), + supplyCap: convertToUnit("1000", 18), + borrowCap: convertToUnit("100", 18), + vTokenReceiver: "0xDC2D855A95Ee70d7282BebD35c96f905CDE31f55", + }, ], rewards: [ { @@ -1080,6 +1141,12 @@ export const globalConfig: NetworkConfig = { supplySpeeds: ["1157407407407407"], // 1000 SD over 30 days (864000 blocks) borrowSpeeds: ["1157407407407407"], // 1000 SD over 30 days (864000 blocks) }, + { + asset: "HAY", + markets: ["SnBNB"], + supplySpeeds: ["930059523809523"], // 1500 HAY over 56 days (1612800 blocks) + borrowSpeeds: ["930059523809523"], // 1500 HAY over 56 days (1612800 blocks) + }, ], }, { @@ -1356,6 +1423,13 @@ export const globalConfig: NetworkConfig = { decimals: 18, tokenAddress: "0x3BC5AC0dFdC871B365d159f728dd1B9A0B5481E8", }, + { + isMock: false, + name: "Synclub Staked BNB", + symbol: "SnBNB", + decimals: 18, + tokenAddress: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B", + }, ], poolConfig: [ { @@ -1759,6 +1833,23 @@ export const globalConfig: NetworkConfig = { borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", }, + { + name: "Venus SnBNB (Liquid Staked BNB)", + asset: "SnBNB", + symbol: "vSnBNB_LiquidStakedBNB", + rateModel: InterestRateModels.JumpRate.toString(), + baseRatePerYear: convertToUnit("0.02", 18), + multiplierPerYear: convertToUnit("0.2", 18), + jumpMultiplierPerYear: convertToUnit("3", 18), + kink_: convertToUnit("0.5", 18), + collateralFactor: convertToUnit("0.87", 18), + liquidationThreshold: convertToUnit("0.9", 18), + reserveFactor: convertToUnit("0.25", 18), + initialSupply: convertToUnit("47", 18), + supplyCap: convertToUnit("1000", 18), + borrowCap: convertToUnit("100", 18), + vTokenReceiver: "0xDC2D855A95Ee70d7282BebD35c96f905CDE31f55", + }, ], rewards: [ { @@ -1785,6 +1876,12 @@ export const globalConfig: NetworkConfig = { supplySpeeds: ["1157407407407407"], // 1000 SD over 30 days (864000 blocks) borrowSpeeds: ["1157407407407407"], // 1000 SD over 30 days (864000 blocks) }, + { + asset: "HAY", + markets: ["SnBNB"], + supplySpeeds: ["930059523809523"], // 1500 HAY over 56 days (1612800 blocks) + borrowSpeeds: ["930059523809523"], // 1500 HAY over 56 days (1612800 blocks) + }, ], }, { diff --git a/tests/hardhat/Tokens/mintAndRedeemTest.ts b/tests/hardhat/Tokens/mintAndRedeemTest.ts index 990b0e5ff..3dc2e805b 100644 --- a/tests/hardhat/Tokens/mintAndRedeemTest.ts +++ b/tests/hardhat/Tokens/mintAndRedeemTest.ts @@ -260,11 +260,11 @@ describe("VToken", function () { it("emits an AccrueInterest event", async () => { await expect(await quickMint(underlying, vToken, minter, mintAmount)) .to.emit(vToken, "AccrueInterest") - .withArgs("0", "0", "1000000000000000000", "0", "1000000000000000000"); + .withArgs("0", "0", "1000000000000000000", "0", "1000000000001000000"); await expect(await quickMint(underlying, vToken, minter, mintAmount)) .to.emit(vToken, "AccrueInterest") - .withArgs("10000000000000000000000", "0", "1000000000000000000", "0", "1000000000000000000"); + .withArgs("10000000000000000000000", "0", "1000000000000000000", "0", "1000000000001000000"); }); }); From 6a43338e208614b15e7106c97197cada483c8c93 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Thu, 5 Oct 2023 01:16:02 +0530 Subject: [PATCH 21/23] refactor: deployment config --- helpers/deploymentConfig.ts | 156 ++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/helpers/deploymentConfig.ts b/helpers/deploymentConfig.ts index 8d8d67d5f..c60df4a64 100644 --- a/helpers/deploymentConfig.ts +++ b/helpers/deploymentConfig.ts @@ -722,6 +722,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(500_000, 18), borrowCap: convertToUnit(200_000, 18), vTokenReceiver: preconfiguredAddresses.bsctestnet.VTreasury, + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (Stable Coins)", @@ -739,6 +742,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(1_000_000, 6), // USDT has 6 decimals on testnet borrowCap: convertToUnit(400_000, 6), // USDT has 6 decimals on testnet vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (Stable Coins)", @@ -756,6 +762,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(1_000_000, 18), borrowCap: convertToUnit(400_000, 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -796,6 +805,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("15000000", 18), borrowCap: convertToUnit("10500000", 18), vTokenReceiver: "0x109E8083a64c7DedE513e8b580c5b08B96f9cE73", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus ALPACA (DeFi)", @@ -813,6 +825,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2500000", 18), borrowCap: convertToUnit("1750000", 18), vTokenReceiver: "0xAD9CADe20100B8b945da48e1bCbd805C38d8bE77", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (DeFi)", @@ -830,6 +845,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 6), // USDT has 6 decimals on testnet borrowCap: convertToUnit("14880000", 6), // USDT has 6 decimals on testnet vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (DeFi)", @@ -847,6 +865,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus ANKR (DeFi)", @@ -864,6 +885,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("9508802", 18), borrowCap: convertToUnit("6656161", 18), vTokenReceiver: "0xAE1c38847Fb90A13a2a1D7E5552cCD80c62C6508", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus ankrBNB (DeFi)", @@ -881,6 +905,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("5000", 18), borrowCap: convertToUnit("4000", 18), vTokenReceiver: "0xAE1c38847Fb90A13a2a1D7E5552cCD80c62C6508", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -921,6 +948,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("4000000000", 18), borrowCap: convertToUnit("2800000000", 18), vTokenReceiver: "0x6Ee74536B3Ff10Ff639aa781B7220121287F6Fa5", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus FLOKI (GameFi)", @@ -938,6 +968,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("40000000000", 18), // FLOKI has 18 decimals on testnet borrowCap: convertToUnit("28000000000", 18), // FLOKI has 18 decimals on testnet vTokenReceiver: "0x17e98a24f992BB7bcd62d6722d714A3C74814B94", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (GameFi)", @@ -955,6 +988,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 6), // USDT has 6 decimals on testnet borrowCap: convertToUnit("14880000", 6), // USDT has 6 decimals on testnet vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (GameFi)", @@ -972,6 +1008,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1012,6 +1051,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("8000", 18), borrowCap: convertToUnit("5600", 18), vTokenReceiver: "0xAE1c38847Fb90A13a2a1D7E5552cCD80c62C6508", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus BNBx (Liquid Staked BNB)", @@ -1029,6 +1071,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1818", 18), borrowCap: convertToUnit("1272", 18), vTokenReceiver: "0xF0348E1748FCD45020151C097D234DbbD5730BE7", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus stkBNB (Liquid Staked BNB)", @@ -1046,6 +1091,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("540", 18), borrowCap: convertToUnit("378", 18), vTokenReceiver: "0xccc022502d6c65e1166fd34147040f05880f7972", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus WBNB (Liquid Staked BNB)", @@ -1063,6 +1111,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("80000", 18), borrowCap: convertToUnit("56000", 18), vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (Liquid Staked BNB)", @@ -1080,6 +1131,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 6), // USDT has 6 decimals on testnet borrowCap: convertToUnit("14880000", 6), // USDT has 6 decimals on testnet vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (Liquid Staked BNB)", @@ -1097,6 +1151,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus SnBNB (Liquid Staked BNB)", @@ -1114,6 +1171,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1000", 18), borrowCap: convertToUnit("100", 18), vTokenReceiver: "0xDC2D855A95Ee70d7282BebD35c96f905CDE31f55", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1172,6 +1232,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1500000000000", 18), borrowCap: convertToUnit("1050000000000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus NFT (Tron)", @@ -1189,6 +1252,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("4000000000", 18), // NFT has 18 decimals on testnet borrowCap: convertToUnit("2800000000", 18), // NFT has 18 decimals on testnet vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus WIN (Tron)", @@ -1206,6 +1272,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("3000000000", 18), borrowCap: convertToUnit("2100000000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus TRX (Tron)", @@ -1223,6 +1292,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("11000000", 6), // Note 6 decimals borrowCap: convertToUnit("7700000", 6), // Note 6 decimals vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (Tron)", @@ -1240,6 +1312,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 6), // USDT has 6 decimals on testnet borrowCap: convertToUnit("14880000", 6), // USDT has 6 decimals on testnet vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (Tron)", @@ -1257,6 +1332,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1455,6 +1533,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(500_000, 18), borrowCap: convertToUnit(200_000, 18), vTokenReceiver: "0x09702Ea135d9D707DD51f530864f2B9220aAD87B", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (Stablecoins)", @@ -1472,6 +1553,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(1_000_000, 18), borrowCap: convertToUnit(400_000, 18), vTokenReceiver: "0xF322942f644A996A617BD29c16bd7d231d9F35E9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (Stablecoins)", @@ -1489,6 +1573,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(1_000_000, 18), borrowCap: convertToUnit(400_000, 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1529,6 +1616,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("15000000", 18), borrowCap: convertToUnit("10500000", 18), vTokenReceiver: "0x109E8083a64c7DedE513e8b580c5b08B96f9cE73", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus ALPACA (DeFi)", @@ -1546,6 +1636,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2500000", 18), borrowCap: convertToUnit("1750000", 18), vTokenReceiver: "0xAD9CADe20100B8b945da48e1bCbd805C38d8bE77", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (DeFi)", @@ -1563,6 +1656,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 18), borrowCap: convertToUnit("14880000", 18), vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (DeFi)", @@ -1580,6 +1676,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus ANKR (DeFi)", @@ -1597,6 +1696,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("9508802", 18), borrowCap: convertToUnit("6656161", 18), vTokenReceiver: "0xAE1c38847Fb90A13a2a1D7E5552cCD80c62C6508", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { @@ -1615,6 +1717,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("5000", 18), borrowCap: convertToUnit("4000", 18), vTokenReceiver: "0xAE1c38847Fb90A13a2a1D7E5552cCD80c62C6508", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1656,6 +1761,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("4000000000", 18), borrowCap: convertToUnit("2800000000", 18), vTokenReceiver: "0x6Ee74536B3Ff10Ff639aa781B7220121287F6Fa5", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus FLOKI (GameFi)", @@ -1673,6 +1781,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("40000000000", 9), // Note 9 decimals borrowCap: convertToUnit("28000000000", 9), // Note 9 decimals vTokenReceiver: "0x17e98a24f992BB7bcd62d6722d714A3C74814B94", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (GameFi)", @@ -1690,6 +1801,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 18), borrowCap: convertToUnit("14880000", 18), vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (GameFi)", @@ -1707,6 +1821,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1747,6 +1864,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("8000", 18), borrowCap: convertToUnit("5600", 18), vTokenReceiver: "0xAE1c38847Fb90A13a2a1D7E5552cCD80c62C6508", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus BNBx (Liquid Staked BNB)", @@ -1764,6 +1884,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1818", 18), borrowCap: convertToUnit("1272", 18), vTokenReceiver: "0xF0348E1748FCD45020151C097D234DbbD5730BE7", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus stkBNB (Liquid Staked BNB)", @@ -1781,6 +1904,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("540", 18), borrowCap: convertToUnit("378", 18), vTokenReceiver: "0xccc022502d6c65e1166fd34147040f05880f7972", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus WBNB (Liquid Staked BNB)", @@ -1798,6 +1924,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("80000", 18), borrowCap: convertToUnit("56000", 18), vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (Liquid Staked BNB)", @@ -1815,6 +1944,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 18), borrowCap: convertToUnit("14880000", 18), vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (Liquid Staked BNB)", @@ -1832,6 +1964,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus SnBNB (Liquid Staked BNB)", @@ -1849,6 +1984,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1000", 18), borrowCap: convertToUnit("100", 18), vTokenReceiver: "0xDC2D855A95Ee70d7282BebD35c96f905CDE31f55", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1907,6 +2045,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1500000000000", 18), borrowCap: convertToUnit("1050000000000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus NFT (Tron)", @@ -1924,6 +2065,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("4000000000", 6), // Note 6 decimals borrowCap: convertToUnit("2800000000", 6), // Note 6 decimals vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus WIN (Tron)", @@ -1941,6 +2085,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("3000000000", 18), borrowCap: convertToUnit("2100000000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus TRX (Tron)", @@ -1958,6 +2105,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("11000000", 6), // Note 6 decimals borrowCap: convertToUnit("7700000", 6), // Note 6 decimals vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDT (Tron)", @@ -1975,6 +2125,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("18600000", 18), borrowCap: convertToUnit("14880000", 18), vTokenReceiver: "0xf322942f644a996a617bd29c16bd7d231d9f35e9", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, { name: "Venus USDD (Tron)", @@ -1992,6 +2145,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("2000000", 18), borrowCap: convertToUnit("1600000", 18), vTokenReceiver: "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ From 6e20a8e1aacb2d899c3a3ad8e7e477b4629c449f Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 18 Oct 2023 17:39:18 +0530 Subject: [PATCH 22/23] tests: added mock contract for access control manager --- .../test/Mocks/MockAccessControlManager.sol | 97 +++++++++++++++++++ tests/hardhat/Tokens/mintAndRedeemTest.ts | 4 +- tests/hardhat/util/TokenTestHelpers.ts | 2 +- 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 contracts/test/Mocks/MockAccessControlManager.sol diff --git a/contracts/test/Mocks/MockAccessControlManager.sol b/contracts/test/Mocks/MockAccessControlManager.sol new file mode 100644 index 000000000..a7f7fd016 --- /dev/null +++ b/contracts/test/Mocks/MockAccessControlManager.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.13; +import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; +import { IAccessControlManagerV8 } from "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol"; + +/** + * @title Venus Access Control Contract + * @author venus + * @dev This contract is a wrapper of OpenZeppelin AccessControl + * extending it in a way to standartize access control + * within Venus Smart Contract Ecosystem + */ +contract AccessControlManager is AccessControl, IAccessControlManagerV8 { + /// @notice Emitted when an account is given a permission to a certain contract function + /// @dev If contract address is 0x000..0 this means that the account is a default admin of this function and + /// can call any contract function with this signature + event PermissionGranted(address account, address contractAddress, string functionSig); + + /// @notice Emitted when an account is revoked a permission to a certain contract function + event PermissionRevoked(address account, address contractAddress, string functionSig); + + constructor() { + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + /** + * @notice Gives a function call permission to one single account + * @dev this function can be called only from Role Admin or DEFAULT_ADMIN_ROLE + * @param contractAddress address of contract for which call permissions will be granted + * @dev if contractAddress is zero address, the account can access the specified function + * on **any** contract managed by this ACL + * @param functionSig signature e.g. "functionName(uint256,bool)" + * @param accountToPermit account that will be given access to the contract function + * @custom:event Emits a {RoleGranted} and {PermissionGranted} events. + */ + function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) public { + bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); + grantRole(role, accountToPermit); + emit PermissionGranted(accountToPermit, contractAddress, functionSig); + } + + /** + * @notice Revokes an account's permission to a particular function call + * @dev this function can be called only from Role Admin or DEFAULT_ADMIN_ROLE + * May emit a {RoleRevoked} event. + * @param contractAddress address of contract for which call permissions will be revoked + * @param functionSig signature e.g. "functionName(uint256,bool)" + * @custom:event Emits {RoleRevoked} and {PermissionRevoked} events. + */ + function revokeCallPermission( + address contractAddress, + string calldata functionSig, + address accountToRevoke + ) public { + bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); + revokeRole(role, accountToRevoke); + emit PermissionRevoked(accountToRevoke, contractAddress, functionSig); + } + + /** + * @notice Verifies if the given account can call a contract's guarded function + * @dev Since restricted contracts using this function as a permission hook, we can get contracts address with msg.sender + * @param account for which call permissions will be checked + * @param functionSig restricted function signature e.g. "functionName(uint256,bool)" + * @return false if the user account cannot call the particular contract function + * + */ + function isAllowedToCall(address account, string calldata functionSig) public view returns (bool) { + bytes32 role = keccak256(abi.encodePacked(msg.sender, functionSig)); + + if (hasRole(role, account)) { + return true; + } else { + role = keccak256(abi.encodePacked(address(0), functionSig)); + return hasRole(role, account); + } + } + + /** + * @notice Verifies if the given account can call a contract's guarded function + * @dev This function is used as a view function to check permissions rather than contract hook for access restriction check. + * @param account for which call permissions will be checked against + * @param contractAddress address of the restricted contract + * @param functionSig signature of the restricted function e.g. "functionName(uint256,bool)" + * @return false if the user account cannot call the particular contract function + */ + function hasPermission( + address account, + address contractAddress, + string calldata functionSig + ) public view returns (bool) { + bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); + return hasRole(role, account); + } +} diff --git a/tests/hardhat/Tokens/mintAndRedeemTest.ts b/tests/hardhat/Tokens/mintAndRedeemTest.ts index 3dc2e805b..990b0e5ff 100644 --- a/tests/hardhat/Tokens/mintAndRedeemTest.ts +++ b/tests/hardhat/Tokens/mintAndRedeemTest.ts @@ -260,11 +260,11 @@ describe("VToken", function () { it("emits an AccrueInterest event", async () => { await expect(await quickMint(underlying, vToken, minter, mintAmount)) .to.emit(vToken, "AccrueInterest") - .withArgs("0", "0", "1000000000000000000", "0", "1000000000001000000"); + .withArgs("0", "0", "1000000000000000000", "0", "1000000000000000000"); await expect(await quickMint(underlying, vToken, minter, mintAmount)) .to.emit(vToken, "AccrueInterest") - .withArgs("10000000000000000000000", "0", "1000000000000000000", "0", "1000000000001000000"); + .withArgs("10000000000000000000000", "0", "1000000000000000000", "0", "1000000000000000000"); }); }); diff --git a/tests/hardhat/util/TokenTestHelpers.ts b/tests/hardhat/util/TokenTestHelpers.ts index 75d74843f..ca24127e0 100644 --- a/tests/hardhat/util/TokenTestHelpers.ts +++ b/tests/hardhat/util/TokenTestHelpers.ts @@ -73,7 +73,7 @@ export const fakeInterestRateModel = async (): Promise> => { const stableRateModel = await smock.fake("StableRateModel"); - stableRateModel.isInterestRateModel.returns(true); + stableRateModel.isStableRateModel.returns(true); return stableRateModel; }; From 6123b4eee971e0ead78a35e6b7b180342eb4e9eb Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 18 Oct 2023 18:07:46 +0530 Subject: [PATCH 23/23] fix: deployment config file --- helpers/deploymentConfig.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/helpers/deploymentConfig.ts b/helpers/deploymentConfig.ts index 8622c0a23..1996951db 100644 --- a/helpers/deploymentConfig.ts +++ b/helpers/deploymentConfig.ts @@ -800,6 +800,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(100000, 18), borrowCap: convertToUnit(50000, 18), vTokenReceiver: "0xc444949e0054a23c44fc45789738bdf64aed2391", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1232,6 +1235,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1000", 18), borrowCap: convertToUnit("100", 18), vTokenReceiver: "0xDC2D855A95Ee70d7282BebD35c96f905CDE31f55", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -1665,6 +1671,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit(100000, 18), borrowCap: convertToUnit(50000, 18), vTokenReceiver: "0xc444949e0054a23c44fc45789738bdf64aed2391", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [ @@ -2099,6 +2108,9 @@ export const globalConfig: NetworkConfig = { supplyCap: convertToUnit("1000", 18), borrowCap: convertToUnit("100", 18), vTokenReceiver: "0xDC2D855A95Ee70d7282BebD35c96f905CDE31f55", + baseRatePerYearForStable: "", + stableRatePremium: "", + optimalStableLoanRatio: "", }, ], rewards: [