diff --git a/buidler.config.ts b/buidler.config.ts index 47eba1e2..03f42645 100644 --- a/buidler.config.ts +++ b/buidler.config.ts @@ -10,6 +10,7 @@ usePlugin('solidity-coverage'); usePlugin('@nomiclabs/buidler-waffle'); usePlugin('@nomiclabs/buidler-etherscan'); //usePlugin('buidler-gas-reporter'); + const DEFAULT_BLOCK_GAS_LIMIT = 10000000; const DEFAULT_GAS_PRICE = 10; const HARDFORK = 'istanbul'; diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index d19f7ba1..66d4542a 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -327,6 +327,7 @@ interface ILendingPool { uint256 ltv, uint256 liquidationThreshold, uint256 liquidationBonus, + uint256 reserveFactor, address interestRateStrategyAddress, bool usageAsCollateralEnabled, bool borrowingEnabled, @@ -349,8 +350,8 @@ interface ILendingPool { view returns ( uint256 availableLiquidity, - uint256 totalBorrowsStable, - uint256 totalBorrowsVariable, + uint256 totalStableDebt, + uint256 totalVariableDebt, uint256 liquidityRate, uint256 variableBorrowRate, uint256 stableBorrowRate, @@ -380,10 +381,9 @@ interface ILendingPool { uint256 currentStableDebt, uint256 currentVariableDebt, uint256 principalStableDebt, - uint256 principalVariableDebt, + uint256 scaledVariableDebt, uint256 stableBorrowRate, uint256 liquidityRate, - uint256 variableBorrowIndex, uint40 stableRateLastUpdated, bool usageAsCollateralEnabled ); diff --git a/contracts/interfaces/IReserveInterestRateStrategy.sol b/contracts/interfaces/IReserveInterestRateStrategy.sol index 48311e70..99f30b78 100644 --- a/contracts/interfaces/IReserveInterestRateStrategy.sol +++ b/contracts/interfaces/IReserveInterestRateStrategy.sol @@ -21,9 +21,10 @@ interface IReserveInterestRateStrategy { function calculateInterestRates( address reserve, uint256 utilizationRate, - uint256 totalBorrowsStable, - uint256 totalBorrowsVariable, - uint256 averageStableBorrowRate + uint256 totalStableDebt, + uint256 totalVariableDebt, + uint256 averageStableBorrowRate, + uint256 reserveFactor ) external view diff --git a/contracts/lendingpool/DefaultReserveInterestRateStrategy.sol b/contracts/lendingpool/DefaultReserveInterestRateStrategy.sol index c2303254..045e9358 100644 --- a/contracts/lendingpool/DefaultReserveInterestRateStrategy.sol +++ b/contracts/lendingpool/DefaultReserveInterestRateStrategy.sol @@ -4,6 +4,7 @@ pragma solidity ^0.6.8; import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol'; import {IReserveInterestRateStrategy} from '../interfaces/IReserveInterestRateStrategy.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; +import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {LendingPoolAddressesProvider} from '../configuration/LendingPoolAddressesProvider.sol'; import {ILendingRateOracle} from '../interfaces/ILendingRateOracle.sol'; @@ -17,7 +18,7 @@ import {ILendingRateOracle} from '../interfaces/ILendingRateOracle.sol'; contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { using WadRayMath for uint256; using SafeMath for uint256; - + using PercentageMath for uint256; /** * @dev this constant represents the utilization rate at which the pool aims to obtain most competitive borrow rates * expressed in ray @@ -49,6 +50,7 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { //slope of the stable interest curve when utilization rate > OPTIMAL_UTILIZATION_RATE. Expressed in ray uint256 internal immutable _stableRateSlope2; + constructor( LendingPoolAddressesProvider provider, uint256 baseVariableBorrowRate, @@ -89,13 +91,23 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { return _baseVariableBorrowRate; } + struct CalcInterestRatesLocalVars { + + uint256 totalBorrows; + uint256 currentVariableBorrowRate; + uint256 currentStableBorrowRate; + uint256 currentLiquidityRate; + uint256 utilizationRate; + } + /** * @dev calculates the interest rates depending on the available liquidity and the total borrowed. * @param reserve the address of the reserve * @param availableLiquidity the liquidity available in the reserve - * @param totalBorrowsStable the total borrowed from the reserve a stable rate - * @param totalBorrowsVariable the total borrowed from the reserve at a variable rate + * @param totalStableDebt the total borrowed from the reserve a stable rate + * @param totalVariableDebt the total borrowed from the reserve at a variable rate * @param averageStableBorrowRate the weighted average of all the stable rate borrows + * @param reserveFactor the reserve portion of the interest to redirect to the reserve treasury * @return currentLiquidityRate the liquidity rate * @return currentStableBorrowRate stable borrow rate * @return currentVariableBorrowRate variable borrow rate @@ -103,9 +115,10 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { function calculateInterestRates( address reserve, uint256 availableLiquidity, - uint256 totalBorrowsStable, - uint256 totalBorrowsVariable, - uint256 averageStableBorrowRate + uint256 totalStableDebt, + uint256 totalVariableDebt, + uint256 averageStableBorrowRate, + uint256 reserveFactor ) external override @@ -116,16 +129,19 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { uint256 ) { - uint256 totalBorrows = totalBorrowsStable.add(totalBorrowsVariable); - uint256 currentVariableBorrowRate = 0; - uint256 currentStableBorrowRate = 0; - uint256 currentLiquidityRate = 0; - uint256 utilizationRate = totalBorrows == 0 + CalcInterestRatesLocalVars memory vars; + + vars.totalBorrows = totalStableDebt.add(totalVariableDebt); + vars.currentVariableBorrowRate = 0; + vars.currentStableBorrowRate = 0; + vars.currentLiquidityRate = 0; + + uint256 utilizationRate = vars.totalBorrows == 0 ? 0 - : totalBorrows.rayDiv(availableLiquidity.add(totalBorrows)); + : vars.totalBorrows.rayDiv(availableLiquidity.add(vars.totalBorrows)); - currentStableBorrowRate = ILendingRateOracle(addressesProvider.getLendingRateOracle()) + vars.currentStableBorrowRate = ILendingRateOracle(addressesProvider.getLendingRateOracle()) .getMarketBorrowRate(reserve); if (utilizationRate > OPTIMAL_UTILIZATION_RATE) { @@ -133,56 +149,57 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { EXCESS_UTILIZATION_RATE ); - currentStableBorrowRate = currentStableBorrowRate.add(_stableRateSlope1).add( + vars.currentStableBorrowRate = vars.currentStableBorrowRate.add(_stableRateSlope1).add( _stableRateSlope2.rayMul(excessUtilizationRateRatio) ); - currentVariableBorrowRate = _baseVariableBorrowRate.add(_variableRateSlope1).add( + vars.currentVariableBorrowRate = _baseVariableBorrowRate.add(_variableRateSlope1).add( _variableRateSlope2.rayMul(excessUtilizationRateRatio) ); } else { - currentStableBorrowRate = currentStableBorrowRate.add( + vars.currentStableBorrowRate = vars.currentStableBorrowRate.add( _stableRateSlope1.rayMul(utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE)) ); - currentVariableBorrowRate = _baseVariableBorrowRate.add( + vars.currentVariableBorrowRate = _baseVariableBorrowRate.add( utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE).rayMul(_variableRateSlope1) ); } - currentLiquidityRate = _getOverallBorrowRate( - totalBorrowsStable, - totalBorrowsVariable, - currentVariableBorrowRate, + vars.currentLiquidityRate = _getOverallBorrowRate( + totalStableDebt, + totalVariableDebt, + vars.currentVariableBorrowRate, averageStableBorrowRate ) - .rayMul(utilizationRate); + .rayMul(utilizationRate) + .percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(reserveFactor)); - return (currentLiquidityRate, currentStableBorrowRate, currentVariableBorrowRate); + return (vars.currentLiquidityRate, vars.currentStableBorrowRate, vars.currentVariableBorrowRate); } /** * @dev calculates the overall borrow rate as the weighted average between the total variable borrows and total stable borrows. - * @param totalBorrowsStable the total borrowed from the reserve a stable rate - * @param totalBorrowsVariable the total borrowed from the reserve at a variable rate + * @param totalStableDebt the total borrowed from the reserve a stable rate + * @param totalVariableDebt the total borrowed from the reserve at a variable rate * @param currentVariableBorrowRate the current variable borrow rate * @param currentAverageStableBorrowRate the weighted average of all the stable rate borrows * @return the weighted averaged borrow rate **/ function _getOverallBorrowRate( - uint256 totalBorrowsStable, - uint256 totalBorrowsVariable, + uint256 totalStableDebt, + uint256 totalVariableDebt, uint256 currentVariableBorrowRate, uint256 currentAverageStableBorrowRate ) internal pure returns (uint256) { - uint256 totalBorrows = totalBorrowsStable.add(totalBorrowsVariable); + uint256 totalBorrows = totalStableDebt.add(totalVariableDebt); if (totalBorrows == 0) return 0; - uint256 weightedVariableRate = totalBorrowsVariable.wadToRay().rayMul( + uint256 weightedVariableRate = totalVariableDebt.wadToRay().rayMul( currentVariableBorrowRate ); - uint256 weightedStableRate = totalBorrowsStable.wadToRay().rayMul( + uint256 weightedStableRate = totalStableDebt.wadToRay().rayMul( currentAverageStableBorrowRate ); diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 86c47374..53721f53 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -12,6 +12,7 @@ import {IAToken} from '../tokenization/interfaces/IAToken.sol'; import {Helpers} from '../libraries/helpers/Helpers.sol'; import {Errors} from '../libraries/helpers/Errors.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; +import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol'; import {GenericLogic} from '../libraries/logic/GenericLogic.sol'; import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol'; @@ -19,6 +20,7 @@ import {ReserveConfiguration} from '../libraries/configuration/ReserveConfigurat import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol'; import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol'; import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol'; +import {DebtTokenBase} from '../tokenization/base/DebtTokenBase.sol'; import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol'; import {LendingPoolCollateralManager} from './LendingPoolCollateralManager.sol'; @@ -42,6 +44,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 public constant REBALANCE_DOWN_RATE_DELTA = (1e27) / 5; uint256 public constant MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 25; uint256 public constant FLASHLOAN_PREMIUM_TOTAL = 9; + uint256 public constant UINT_MAX_VALUE = uint256(-1); + uint256 public constant LENDINGPOOL_REVISION = 0x2; /** * @dev only lending pools configurator can use functions affected by this modifier @@ -64,8 +68,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage require(!_paused, Errors.IS_PAUSED); } - uint256 public constant LENDINGPOOL_REVISION = 0x2; - function getRevision() internal override pure returns (uint256) { return LENDINGPOOL_REVISION; } @@ -99,7 +101,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address aToken = reserve.aTokenAddress; - reserve.updateCumulativeIndexesAndTimestamp(); + reserve.updateState(); reserve.updateInterestRates(asset, aToken, amount, 0); bool isFirstDeposit = IAToken(aToken).balanceOf(onBehalfOf) == 0; @@ -146,7 +148,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage _addressesProvider.getPriceOracle() ); - reserve.updateCumulativeIndexesAndTimestamp(); + reserve.updateState(); reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw); @@ -246,16 +248,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address onBehalfOf ) external override { whenNotPaused(); - _executeRepay(asset, msg.sender, amount, rateMode, onBehalfOf); - } - function _executeRepay( - address asset, - address user, - uint256 amount, - uint256 rateMode, - address onBehalfOf - ) internal { ReserveLogic.ReserveData storage reserve = _reserves[asset]; (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve); @@ -280,13 +273,17 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage variableDebt ); - reserve.updateCumulativeIndexesAndTimestamp(); + reserve.updateState(); //burns an equivalent amount of debt tokens if (interestRateMode == ReserveLogic.InterestRateMode.STABLE) { IStableDebtToken(reserve.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).burn(onBehalfOf, paybackAmount); + IVariableDebtToken(reserve.variableDebtTokenAddress).burn( + onBehalfOf, + paybackAmount, + reserve.variableBorrowIndex + ); } address aToken = reserve.aTokenAddress; @@ -296,9 +293,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage _usersConfig[onBehalfOf].setBorrowing(reserve.id, false); } - IERC20(asset).safeTransferFrom(user, aToken, paybackAmount); + IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount); - emit Repay(asset, onBehalfOf, user, paybackAmount); + emit Repay(asset, onBehalfOf, msg.sender, paybackAmount); } /** @@ -322,15 +319,23 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage interestRateMode ); - reserve.updateCumulativeIndexesAndTimestamp(); + reserve.updateState(); if (interestRateMode == ReserveLogic.InterestRateMode.STABLE) { //burn stable rate tokens, mint variable rate tokens IStableDebtToken(reserve.stableDebtTokenAddress).burn(msg.sender, stableDebt); - IVariableDebtToken(reserve.variableDebtTokenAddress).mint(msg.sender, stableDebt); + IVariableDebtToken(reserve.variableDebtTokenAddress).mint( + msg.sender, + stableDebt, + reserve.variableBorrowIndex + ); } else { //do the opposite - IVariableDebtToken(reserve.variableDebtTokenAddress).burn(msg.sender, variableDebt); + IVariableDebtToken(reserve.variableDebtTokenAddress).burn( + msg.sender, + variableDebt, + reserve.variableBorrowIndex + ); IStableDebtToken(reserve.stableDebtTokenAddress).mint( msg.sender, variableDebt, @@ -377,10 +382,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage Errors.INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET ); + reserve.updateState(); + //burn old debt tokens, mint new ones - - reserve.updateCumulativeIndexesAndTimestamp(); - stableDebtToken.burn(user, stableBorrowBalance); stableDebtToken.mint(user, stableBorrowBalance, reserve.currentStableBorrowRate); @@ -507,7 +511,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage _flashLiquidationLocked = false; } - struct FlashLoanLocalVars { + struct FlashLoanLocalVars { uint256 premium; uint256 amountPlusPremium; IFlashLoanReceiver receiver; @@ -559,7 +563,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (debtMode == ReserveLogic.InterestRateMode.NONE) { IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium); - reserve.updateCumulativeIndexesAndTimestamp(); + reserve.updateState(); reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium); reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0); @@ -632,6 +636,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 ltv, uint256 liquidationThreshold, uint256 liquidationBonus, + uint256 reserveFactor, address interestRateStrategyAddress, bool usageAsCollateralEnabled, bool borrowingEnabled, @@ -647,6 +652,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserve.configuration.getLtv(), reserve.configuration.getLiquidationThreshold(), reserve.configuration.getLiquidationBonus(), + reserve.configuration.getReserveFactor(), reserve.interestRateStrategyAddress, reserve.configuration.getLtv() != 0, reserve.configuration.getBorrowingEnabled(), @@ -681,8 +687,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage view returns ( uint256 availableLiquidity, - uint256 totalBorrowsStable, - uint256 totalBorrowsVariable, + uint256 totalStableDebt, + uint256 totalVariableDebt, uint256 liquidityRate, uint256 variableBorrowRate, uint256 stableBorrowRate, @@ -693,6 +699,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ) { ReserveLogic.ReserveData memory reserve = _reserves[asset]; + return ( IERC20(asset).balanceOf(reserve.aTokenAddress), IERC20(reserve.stableDebtTokenAddress).totalSupply(), @@ -750,10 +757,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 currentStableDebt, uint256 currentVariableDebt, uint256 principalStableDebt, - uint256 principalVariableDebt, + uint256 scaledVariableDebt, uint256 stableBorrowRate, uint256 liquidityRate, - uint256 variableBorrowIndex, uint40 stableRateLastUpdated, bool usageAsCollateralEnabled ) @@ -762,14 +768,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage currentATokenBalance = IERC20(reserve.aTokenAddress).balanceOf(user); (currentStableDebt, currentVariableDebt) = Helpers.getUserCurrentDebt(user, reserve); - (principalStableDebt, principalVariableDebt) = Helpers.getUserPrincipalDebt(user, reserve); + principalStableDebt = IStableDebtToken(reserve.stableDebtTokenAddress).principalBalanceOf(user); + scaledVariableDebt = IVariableDebtToken(reserve.variableDebtTokenAddress).scaledBalanceOf(user); liquidityRate = reserve.currentLiquidityRate; stableBorrowRate = IStableDebtToken(reserve.stableDebtTokenAddress).getUserStableRate(user); stableRateLastUpdated = IStableDebtToken(reserve.stableDebtTokenAddress).getUserLastUpdated( user ); usageAsCollateralEnabled = _usersConfig[user].isUsingAsCollateral(reserve.id); - variableBorrowIndex = IVariableDebtToken(reserve.variableDebtTokenAddress).getUserIndex(user); } function getReserves() external override view returns (address[] memory) { @@ -871,12 +877,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage oracle ); - uint256 reserveIndex = reserve.id; - if (!userConfig.isBorrowing(reserveIndex)) { - userConfig.setBorrowing(reserveIndex, true); + uint256 reserveId = reserve.id; + if (!userConfig.isBorrowing(reserveId)) { + userConfig.setBorrowing(reserveId, true); } - reserve.updateCumulativeIndexesAndTimestamp(); + reserve.updateState(); //caching the current stable borrow rate uint256 currentStableRate = 0; @@ -892,7 +898,11 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage currentStableRate ); } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.onBehalfOf, vars.amount); + IVariableDebtToken(reserve.variableDebtTokenAddress).mint( + vars.onBehalfOf, + vars.amount, + reserve.variableBorrowIndex + ); } reserve.updateInterestRates( diff --git a/contracts/lendingpool/LendingPoolCollateralManager.sol b/contracts/lendingpool/LendingPoolCollateralManager.sol index 9ffd149f..fab8b71f 100644 --- a/contracts/lendingpool/LendingPoolCollateralManager.sol +++ b/contracts/lendingpool/LendingPoolCollateralManager.sol @@ -9,6 +9,7 @@ import { import {IAToken} from '../tokenization/interfaces/IAToken.sol'; import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol'; import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol'; +import {DebtTokenBase} from '../tokenization/base/DebtTokenBase.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; import {GenericLogic} from '../libraries/logic/GenericLogic.sol'; import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol'; @@ -221,7 +222,8 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor } //update the principal reserve - principalReserve.updateCumulativeIndexesAndTimestamp(); + principalReserve.updateState(); + principalReserve.updateInterestRates( principal, principalReserve.aTokenAddress, @@ -232,13 +234,16 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn( user, - vars.actualAmountToLiquidate + vars.actualAmountToLiquidate, + principalReserve.variableBorrowIndex ); } else { IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn( user, - vars.userVariableDebt + vars.userVariableDebt, + principalReserve.variableBorrowIndex ); + IStableDebtToken(principalReserve.stableDebtTokenAddress).burn( user, vars.actualAmountToLiquidate.sub(vars.userVariableDebt) @@ -252,7 +257,7 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor //otherwise receives the underlying asset //updating collateral reserve - collateralReserve.updateCumulativeIndexesAndTimestamp(); + collateralReserve.updateState(); collateralReserve.updateInterestRates( collateral, address(vars.collateralAtoken), @@ -367,7 +372,7 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor vars.actualAmountToLiquidate = vars.principalAmountNeeded; } //updating collateral reserve indexes - collateralReserve.updateCumulativeIndexesAndTimestamp(); + collateralReserve.updateState(); vars.collateralAtoken.burn( user, @@ -392,7 +397,7 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor ); //updating debt reserve - debtReserve.updateCumulativeIndexesAndTimestamp(); + debtReserve.updateState(); debtReserve.updateInterestRates( principal, vars.principalAToken, @@ -404,10 +409,15 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( user, - vars.actualAmountToLiquidate + vars.actualAmountToLiquidate, + debtReserve.variableBorrowIndex ); } else { - IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(user, vars.userVariableDebt); + IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( + user, + vars.userVariableDebt, + debtReserve.variableBorrowIndex + ); IStableDebtToken(debtReserve.stableDebtTokenAddress).burn( user, vars.actualAmountToLiquidate.sub(vars.userVariableDebt) @@ -468,8 +478,8 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor vars.fromReserveAToken = IAToken(fromReserve.aTokenAddress); vars.toReserveAToken = IAToken(toReserve.aTokenAddress); - fromReserve.updateCumulativeIndexesAndTimestamp(); - toReserve.updateCumulativeIndexesAndTimestamp(); + fromReserve.updateState(); + toReserve.updateState(); if (vars.fromReserveAToken.balanceOf(msg.sender) == amountToSwap) { _usersConfig[msg.sender].setUsingAsCollateral(fromReserve.id, false); diff --git a/contracts/lendingpool/LendingPoolConfigurator.sol b/contracts/lendingpool/LendingPoolConfigurator.sol index 801523de..6bedff76 100644 --- a/contracts/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/lendingpool/LendingPoolConfigurator.sol @@ -118,6 +118,13 @@ contract LendingPoolConfigurator is VersionedInitializable { **/ event ReserveBaseLtvChanged(address asset, uint256 ltv); + /** + * @dev emitted when a reserve factor is updated + * @param asset the address of the reserve + * @param factor the new reserve factor + **/ + event ReserveFactorChanged(address asset, uint256 factor); + /** * @dev emitted when a reserve liquidation threshold is updated * @param asset the address of the reserve @@ -405,8 +412,8 @@ contract LendingPoolConfigurator is VersionedInitializable { function deactivateReserve(address asset) external onlyAaveAdmin { ( uint256 availableLiquidity, - uint256 totalBorrowsStable, - uint256 totalBorrowsVariable, + uint256 totalStableDebt, + uint256 totalVariableDebt, , , , @@ -416,7 +423,7 @@ contract LendingPoolConfigurator is VersionedInitializable { ) = pool.getReserveData(asset); require( - availableLiquidity == 0 && totalBorrowsStable == 0 && totalBorrowsVariable == 0, + availableLiquidity == 0 && totalStableDebt == 0 && totalVariableDebt == 0, Errors.RESERVE_LIQUIDITY_NOT_0 ); @@ -458,7 +465,7 @@ contract LendingPoolConfigurator is VersionedInitializable { } /** - * @dev emitted when a reserve loan to value is updated + * @dev updates the ltv of a reserve * @param asset the address of the reserve * @param ltv the new value for the loan to value **/ @@ -472,6 +479,22 @@ contract LendingPoolConfigurator is VersionedInitializable { emit ReserveBaseLtvChanged(asset, ltv); } + /** + * @dev updates the reserve factor of a reserve + * @param asset the address of the reserve + * @param reserveFactor the new reserve factor of the reserve + **/ + function setReserveFactor(address asset, uint256 reserveFactor) external onlyAaveAdmin { + ReserveConfiguration.Map memory currentConfig = pool.getConfiguration(asset); + + currentConfig.setReserveFactor(reserveFactor); + + pool.setConfiguration(asset, currentConfig.data); + + emit ReserveFactorChanged(asset, reserveFactor); + } + + /** * @dev updates the liquidation threshold of a reserve. * @param asset the address of the reserve @@ -559,7 +582,7 @@ contract LendingPoolConfigurator is VersionedInitializable { payable(proxyAddress) ); - (uint256 decimals, , , , , , , , , ) = pool.getReserveConfigurationData(asset); + (uint256 decimals, , , , , , , , , , ) = pool.getReserveConfigurationData(asset); bytes memory params = abi.encodeWithSignature( 'initialize(uint8,string,string)', diff --git a/contracts/libraries/configuration/ReserveConfiguration.sol b/contracts/libraries/configuration/ReserveConfiguration.sol index 46ad9ba9..ed4df31e 100644 --- a/contracts/libraries/configuration/ReserveConfiguration.sol +++ b/contracts/libraries/configuration/ReserveConfiguration.sol @@ -6,6 +6,7 @@ import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import {ReserveLogic} from '../logic/ReserveLogic.sol'; import {WadRayMath} from '../math/WadRayMath.sol'; import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol'; +import "@nomiclabs/buidler/console.sol"; /** * @title ReserveConfiguration library @@ -13,14 +14,15 @@ import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol'; * @notice Implements the bitmap logic to handle the reserve configuration */ library ReserveConfiguration { - uint256 constant LTV_MASK = 0xFFFFFFFFFFF0000; - uint256 constant LIQUIDATION_THRESHOLD_MASK = 0xFFFFFFF0000FFFF; - uint256 constant LIQUIDATION_BONUS_MASK = 0xFFF0000FFFFFFFF; - uint256 constant DECIMALS_MASK = 0xF00FFFFFFFFFFFF; - uint256 constant ACTIVE_MASK = 0xEFFFFFFFFFFFFFF; - uint256 constant FROZEN_MASK = 0xDFFFFFFFFFFFFFF; - uint256 constant BORROWING_MASK = 0xBFFFFFFFFFFFFFF; - uint256 constant STABLE_BORROWING_MASK = 0x7FFFFFFFFFFFFFF; + uint256 constant LTV_MASK = 0xFFFFFFFFFFFFFFFF0000; + uint256 constant LIQUIDATION_THRESHOLD_MASK = 0xFFFFFFFFFFFF0000FFFF; + uint256 constant LIQUIDATION_BONUS_MASK = 0xFFFFFFF0000FFFFFFFF; + uint256 constant DECIMALS_MASK = 0xFFFFFF00FFFFFFFFFFFF; + uint256 constant ACTIVE_MASK = 0xFFFFFEFFFFFFFFFFFFFF; + uint256 constant FROZEN_MASK = 0xFFFFFDFFFFFFFFFFFFFF; + uint256 constant BORROWING_MASK = 0xFFFFFBFFFFFFFFFFFFFF; + uint256 constant STABLE_BORROWING_MASK = 0xFFFF07FFFFFFFFFFFFFF; + uint256 constant RESERVE_FACTOR_MASK = 0xFFFFFFFFFFFFFFFF; struct Map { //bit 0-15: LTV @@ -31,9 +33,28 @@ library ReserveConfiguration { //bit 57: reserve is freezed //bit 58: borrowing is enabled //bit 59: stable rate borrowing enabled + //bit 64-79: reserve factor uint256 data; } + /** + * @dev sets the reserve factor of the reserve + * @param self the reserve configuration + * @param reserveFactor the reserve factor + **/ + function setReserveFactor(ReserveConfiguration.Map memory self, uint256 reserveFactor) internal pure { + + self.data = (self.data & RESERVE_FACTOR_MASK) | reserveFactor << 64; + } + + /** + * @dev gets the reserve factor of the reserve + * @param self the reserve configuration + * @return the reserve factor + **/ + function getReserveFactor(ReserveConfiguration.Map storage self) internal view returns (uint256) { + return (self.data & ~RESERVE_FACTOR_MASK) >> 64; + } /** * @dev sets the Loan to Value of the reserve * @param self the reserve configuration diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index d25b041b..4f3f6daa 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -48,7 +48,6 @@ library Errors { string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool' string public constant CANNOT_GIVE_ALLOWANCE_TO_HIMSELF = '30'; // 'User cannot give allowance to himself' string public constant TRANSFER_AMOUNT_NOT_GT_0 = '31'; // 'Transferred amount needs to be greater than zero' - string public constant INVALID_ATOKEN_BALANCE = '52'; // balance on burning is invalid // require error messages - ReserveLogic string public constant RESERVE_ALREADY_INITIALIZED = '34'; // 'Reserve has already been initialized' diff --git a/contracts/libraries/helpers/Helpers.sol b/contracts/libraries/helpers/Helpers.sol index dda22eb4..e1d65502 100644 --- a/contracts/libraries/helpers/Helpers.sol +++ b/contracts/libraries/helpers/Helpers.sol @@ -26,21 +26,4 @@ library Helpers { DebtTokenBase(reserve.variableDebtTokenAddress).balanceOf(user) ); } - - /** - * @dev fetches the user principal stable and variable debt balances - * @param user the user - * @param reserve the reserve object - * @return the stable and variable debt balance - **/ - function getUserPrincipalDebt(address user, ReserveLogic.ReserveData storage reserve) - internal - view - returns (uint256, uint256) - { - return ( - DebtTokenBase(reserve.stableDebtTokenAddress).principalBalanceOf(user), - DebtTokenBase(reserve.variableDebtTokenAddress).principalBalanceOf(user) - ); - } } diff --git a/contracts/libraries/logic/ReserveLogic.sol b/contracts/libraries/logic/ReserveLogic.sol index f8ce2220..8f6069ab 100644 --- a/contracts/libraries/logic/ReserveLogic.sol +++ b/contracts/libraries/logic/ReserveLogic.sol @@ -6,10 +6,13 @@ import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import {MathUtils} from '../math/MathUtils.sol'; import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol'; import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol'; +import {IAToken} from '../../tokenization/interfaces/IAToken.sol'; import {IStableDebtToken} from '../../tokenization/interfaces/IStableDebtToken.sol'; +import {IVariableDebtToken} from '../../tokenization/interfaces/IVariableDebtToken.sol'; import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol'; import {IReserveInterestRateStrategy} from '../../interfaces/IReserveInterestRateStrategy.sol'; import {WadRayMath} from '../math/WadRayMath.sol'; +import {PercentageMath} from '../math/PercentageMath.sol'; import {Errors} from '../helpers/Errors.sol'; /** @@ -20,6 +23,7 @@ import {Errors} from '../helpers/Errors.sol'; library ReserveLogic { using SafeMath for uint256; using WadRayMath for uint256; + using PercentageMath for uint256; using SafeERC20 for IERC20; /** @@ -27,7 +31,6 @@ library ReserveLogic { * @param reserve the address of the reserve * @param liquidityRate the new liquidity rate * @param stableBorrowRate the new stable borrow rate - * @param averageStableBorrowRate the new average stable borrow rate * @param variableBorrowRate the new variable borrow rate * @param liquidityIndex the new liquidity index * @param variableBorrowIndex the new variable borrow index @@ -36,7 +39,6 @@ library ReserveLogic { address indexed reserve, uint256 liquidityRate, uint256 stableBorrowRate, - uint256 averageStableBorrowRate, uint256 variableBorrowRate, uint256 liquidityIndex, uint256 variableBorrowIndex @@ -66,6 +68,7 @@ library ReserveLogic { address aTokenAddress; address stableDebtTokenAddress; address variableDebtTokenAddress; + //address of the interest rate strategy address interestRateStrategyAddress; //the id of the reserve. Represents the position in the list of the active reserves uint8 id; @@ -144,36 +147,25 @@ library ReserveLogic { * a formal specification. * @param reserve the reserve object **/ - function updateCumulativeIndexesAndTimestamp(ReserveData storage reserve) internal { - uint256 currentLiquidityRate = reserve.currentLiquidityRate; + function updateState(ReserveData storage reserve) external { + address variableDebtToken = reserve.variableDebtTokenAddress; + uint256 previousVariableBorrowIndex = reserve.variableBorrowIndex; + uint256 previousLiquidityIndex = reserve.liquidityIndex; - //only cumulating if there is any income being produced - if (currentLiquidityRate > 0) { - uint40 lastUpdateTimestamp = reserve.lastUpdateTimestamp; - uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest( - currentLiquidityRate, - lastUpdateTimestamp - ); - uint256 index = cumulatedLiquidityInterest.rayMul(reserve.liquidityIndex); - require(index < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW); + (uint256 newLiquidityIndex, uint256 newVariableBorrowIndex) = _updateIndexes( + reserve, + variableDebtToken, + previousLiquidityIndex, + previousVariableBorrowIndex + ); - reserve.liquidityIndex = uint128(index); - - //as the liquidity rate might come only from stable rate loans, we need to ensure - //that there is actual variable debt before accumulating - if (IERC20(reserve.variableDebtTokenAddress).totalSupply() > 0) { - uint256 cumulatedVariableBorrowInterest = MathUtils.calculateCompoundedInterest( - reserve.currentVariableBorrowRate, - lastUpdateTimestamp - ); - index = cumulatedVariableBorrowInterest.rayMul(reserve.variableBorrowIndex); - require(index < (1 << 128), Errors.VARIABLE_BORROW_INDEX_OVERFLOW); - reserve.variableBorrowIndex = uint128(index); - } - } - - //solium-disable-next-line - reserve.lastUpdateTimestamp = uint40(block.timestamp); + _mintToTreasury( + reserve, + variableDebtToken, + previousVariableBorrowIndex, + newLiquidityIndex, + newVariableBorrowIndex + ); } /** @@ -228,12 +220,13 @@ library ReserveLogic { } struct UpdateInterestRatesLocalVars { - uint256 currentAvgStableRate; - uint256 availableLiquidity; address stableDebtTokenAddress; + uint256 availableLiquidity; + uint256 totalStableDebt; uint256 newLiquidityRate; uint256 newStableRate; uint256 newVariableRate; + uint256 avgStableRate; } /** @@ -253,8 +246,10 @@ library ReserveLogic { UpdateInterestRatesLocalVars memory vars; vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress; - vars.currentAvgStableRate = IStableDebtToken(vars.stableDebtTokenAddress) - .getAverageStableRate(); + + (vars.totalStableDebt, vars.avgStableRate) = IStableDebtToken(vars.stableDebtTokenAddress) + .getTotalSupplyAndAvgRate(); + vars.availableLiquidity = IERC20(reserveAddress).balanceOf(aTokenAddress); ( @@ -264,9 +259,10 @@ library ReserveLogic { ) = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).calculateInterestRates( reserveAddress, vars.availableLiquidity.add(liquidityAdded).sub(liquidityTaken), - IERC20(vars.stableDebtTokenAddress).totalSupply(), + vars.totalStableDebt, IERC20(reserve.variableDebtTokenAddress).totalSupply(), - vars.currentAvgStableRate + vars.avgStableRate, + reserve.configuration.getReserveFactor() ); require(vars.newLiquidityRate < (1 << 128), 'ReserveLogic: Liquidity rate overflow'); require(vars.newStableRate < (1 << 128), 'ReserveLogic: Stable borrow rate overflow'); @@ -280,10 +276,135 @@ library ReserveLogic { reserveAddress, vars.newLiquidityRate, vars.newStableRate, - vars.currentAvgStableRate, vars.newVariableRate, reserve.liquidityIndex, reserve.variableBorrowIndex ); } + + struct MintToTreasuryLocalVars { + uint256 currentStableDebt; + uint256 principalStableDebt; + uint256 previousStableDebt; + uint256 currentVariableDebt; + uint256 scaledVariableDebt; + uint256 previousVariableDebt; + uint256 avgStableRate; + uint256 cumulatedStableInterest; + uint256 totalDebtAccrued; + uint256 amountToMint; + uint256 reserveFactor; + uint40 stableSupplyUpdatedTimestamp; + } + + /** + * @dev mints part of the repaid interest to the reserve treasury, depending on the reserveFactor for the + * specific asset. + * @param reserve the reserve reserve to be updated + * @param variableDebtToken the debt token address + * @param previousVariableBorrowIndex the variable borrow index before the last accumulation of the interest + * @param newLiquidityIndex the new liquidity index + * @param newVariableBorrowIndex the variable borrow index after the last accumulation of the interest + **/ + function _mintToTreasury( + ReserveData storage reserve, + address variableDebtToken, + uint256 previousVariableBorrowIndex, + uint256 newLiquidityIndex, + uint256 newVariableBorrowIndex + ) internal { + MintToTreasuryLocalVars memory vars; + + vars.reserveFactor = reserve.configuration.getReserveFactor(); + + if (vars.reserveFactor == 0) { + return; + } + + //fetching the last scaled total variable debt + vars.scaledVariableDebt = IVariableDebtToken(variableDebtToken).scaledTotalSupply(); + + //fetching the principal, total stable debt and the avg stable rate + ( + vars.principalStableDebt, + vars.currentStableDebt, + vars.avgStableRate, + vars.stableSupplyUpdatedTimestamp + ) = IStableDebtToken(reserve.stableDebtTokenAddress).getSupplyData(); + + //calculate the last principal variable debt + vars.previousVariableDebt = vars.scaledVariableDebt.rayMul(previousVariableBorrowIndex); + + //calculate the new total supply after accumulation of the index + vars.currentVariableDebt = vars.scaledVariableDebt.rayMul(newVariableBorrowIndex); + + //calculate the stable debt until the last timestamp update + vars.cumulatedStableInterest = MathUtils.calculateCompoundedInterest( + vars.avgStableRate, + vars.stableSupplyUpdatedTimestamp + ); + + vars.previousStableDebt = vars.principalStableDebt.rayMul(vars.cumulatedStableInterest); + + //debt accrued is the sum of the current debt minus the sum of the debt at the last update + vars.totalDebtAccrued = vars + .currentVariableDebt + .add(vars.currentStableDebt) + .sub(vars.previousVariableDebt) + .sub(vars.previousStableDebt); + + vars.amountToMint = vars.totalDebtAccrued.percentMul(vars.reserveFactor); + + IAToken(reserve.aTokenAddress).mintToTreasury(vars.amountToMint, newLiquidityIndex); + } + + /** + * @dev updates the reserve indexes and the timestamp of the update + * @param reserve the reserve reserve to be updated + * @param variableDebtToken the debt token address + * @param liquidityIndex the last stored liquidity index + * @param variableBorrowIndex the last stored variable borrow index + **/ + function _updateIndexes( + ReserveData storage reserve, + address variableDebtToken, + uint256 liquidityIndex, + uint256 variableBorrowIndex + ) internal returns (uint256, uint256) { + + uint40 timestamp = reserve.lastUpdateTimestamp; + + uint256 currentLiquidityRate = reserve.currentLiquidityRate; + + uint256 newLiquidityIndex = liquidityIndex; + uint256 newVariableBorrowIndex = variableBorrowIndex; + + //only cumulating if there is any income being produced + if (currentLiquidityRate > 0) { + uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest( + currentLiquidityRate, + timestamp + ); + newLiquidityIndex = cumulatedLiquidityInterest.rayMul(liquidityIndex); + require(newLiquidityIndex < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW); + + reserve.liquidityIndex = uint128(newLiquidityIndex); + + //as the liquidity rate might come only from stable rate loans, we need to ensure + //that there is actual variable debt before accumulating + if (IERC20(variableDebtToken).totalSupply() > 0) { + uint256 cumulatedVariableBorrowInterest = MathUtils.calculateCompoundedInterest( + reserve.currentVariableBorrowRate, + timestamp + ); + newVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(variableBorrowIndex); + require(newVariableBorrowIndex < (1 << 128), Errors.VARIABLE_BORROW_INDEX_OVERFLOW); + reserve.variableBorrowIndex = uint128(newVariableBorrowIndex); + } + } + + //solium-disable-next-line + reserve.lastUpdateTimestamp = uint40(block.timestamp); + return (newLiquidityIndex, newVariableBorrowIndex); + } } diff --git a/contracts/misc/WalletBalanceProvider.sol b/contracts/misc/WalletBalanceProvider.sol index d4497178..c52b5f21 100644 --- a/contracts/misc/WalletBalanceProvider.sol +++ b/contracts/misc/WalletBalanceProvider.sol @@ -91,7 +91,7 @@ contract WalletBalanceProvider { uint256[] memory balances = new uint256[](reserves.length); for (uint256 j = 0; j < reserves.length; j++) { - (, , , , , , , , bool isActive, ) = pool.getReserveConfigurationData(reserves[j]); + (, , , , , , , , , bool isActive, ) = pool.getReserveConfigurationData(reserves[j]); if (!isActive) { balances[j] = 0; diff --git a/contracts/mocks/upgradeability/MockAToken.sol b/contracts/mocks/upgradeability/MockAToken.sol index f84543d9..837e103a 100644 --- a/contracts/mocks/upgradeability/MockAToken.sol +++ b/contracts/mocks/upgradeability/MockAToken.sol @@ -6,12 +6,13 @@ import {LendingPool} from '../../lendingpool/LendingPool.sol'; contract MockAToken is AToken { constructor( - LendingPool _pool, - address _underlyingAssetAddress, - string memory _tokenName, - string memory _tokenSymbol, + LendingPool pool, + address underlyingAssetAddress, + address reserveTreasury, + string memory tokenName, + string memory tokenSymbol, address incentivesController - ) public AToken(_pool, _underlyingAssetAddress, _tokenName, _tokenSymbol, incentivesController) {} + ) public AToken(pool, underlyingAssetAddress, reserveTreasury, tokenName, tokenSymbol, incentivesController) {} function getRevision() internal override pure returns (uint256) { return 0x2; diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 3bb54b49..98c54bcd 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -20,18 +20,8 @@ import {SafeERC20} from '../misc/SafeERC20.sol'; */ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { using WadRayMath for uint256; - using SafeERC20 for IncentivizedERC20; + using SafeERC20 for IERC20; - uint256 public constant UINT_MAX_VALUE = uint256(-1); - address public immutable UNDERLYING_ASSET_ADDRESS; - LendingPool public immutable POOL; - - /// @dev owner => next valid nonce to submit with permit() - mapping(address => uint256) public _nonces; - - uint256 public constant ATOKEN_REVISION = 0x1; - - bytes32 public DOMAIN_SEPARATOR; bytes public constant EIP712_REVISION = bytes('1'); bytes32 internal constant EIP712_DOMAIN = keccak256( 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' @@ -40,6 +30,17 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)' ); + uint256 public constant UINT_MAX_VALUE = uint256(-1); + uint256 public constant ATOKEN_REVISION = 0x1; + address public immutable UNDERLYING_ASSET_ADDRESS; + address public immutable RESERVE_TREASURY_ADDRESS; + LendingPool public immutable POOL; + + /// @dev owner => next valid nonce to submit with permit() + mapping(address => uint256) public _nonces; + + bytes32 public DOMAIN_SEPARATOR; + modifier onlyLendingPool { require(msg.sender == address(POOL), Errors.CALLER_MUST_BE_LENDING_POOL); _; @@ -48,12 +49,14 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { constructor( LendingPool pool, address underlyingAssetAddress, + address reserveTreasuryAddress, string memory tokenName, string memory tokenSymbol, address incentivesController ) public IncentivizedERC20(tokenName, tokenSymbol, 18, incentivesController) { POOL = pool; UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress; + RESERVE_TREASURY_ADDRESS = reserveTreasuryAddress; } function getRevision() internal virtual override pure returns (uint256) { @@ -98,20 +101,13 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { uint256 amount, uint256 index ) external override onlyLendingPool { - uint256 currentBalance = balanceOf(user); - - require(amount <= currentBalance, Errors.INVALID_ATOKEN_BALANCE); - - uint256 scaledAmount = amount.rayDiv(index); - - _burn(user, scaledAmount); + _burn(user, amount.rayDiv(index)); //transfers the underlying to the target - IncentivizedERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount); + IERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount); //transfer event to track balances emit Transfer(user, address(0), amount); - emit Burn(msg.sender, receiverOfUnderlying, amount, index); } @@ -126,16 +122,22 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { uint256 amount, uint256 index ) external override onlyLendingPool { - uint256 scaledAmount = amount.rayDiv(index); - //mint an equivalent amount of tokens to cover the new deposit - _mint(user, scaledAmount); + _mint(user, amount.rayDiv(index)); //transfer event to track balances emit Transfer(address(0), user, amount); emit Mint(user, amount, index); } + function mintToTreasury(uint256 amount, uint256 index) external override onlyLendingPool { + _mint(RESERVE_TREASURY_ADDRESS, amount.div(index)); + + //transfer event to track balances + emit Transfer(address(0), RESERVE_TREASURY_ADDRESS, amount); + emit Mint(RESERVE_TREASURY_ADDRESS, amount, index); + } + /** * @dev transfers tokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken * only lending pools can call this function @@ -209,6 +211,14 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { return currentSupplyScaled.rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)); } + /** + * @dev Returns the scaled total supply of the variable debt token. Represents sum(borrows/index) + * @return the scaled total supply + **/ + function scaledTotalSupply() public virtual override view returns (uint256) { + return super.totalSupply(); + } + /** * @dev Used to validate transfers before actually executing them. * @param user address of the user to check @@ -232,7 +242,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { onlyLendingPool returns (uint256) { - IncentivizedERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(target, amount); + IERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(target, amount); return amount; } @@ -271,6 +281,14 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { _approve(owner, spender, value); } + /** + * @dev transfers the aTokens between two users. Validates the transfer + * (ie checks for valid HF after the transfer) if required + * @param from the source address + * @param to the destination address + * @param amount the amount to transfer + * @param validate true if the transfer needs to be validated + **/ function _transfer( address from, address to, @@ -283,13 +301,17 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { uint256 index = POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - uint256 scaledAmount = amount.rayDiv(index); - - super._transfer(from, to, scaledAmount); + super._transfer(from, to, amount.rayDiv(index)); emit BalanceTransfer(from, to, amount, index); } + /** + * @dev overrides the parent _transfer to force validated transfer() and transferFrom() + * @param from the source address + * @param to the destination address + * @param amount the amount to transfer + **/ function _transfer( address from, address to, diff --git a/contracts/tokenization/StableDebtToken.sol b/contracts/tokenization/StableDebtToken.sol index 547052e4..1974d51e 100644 --- a/contracts/tokenization/StableDebtToken.sol +++ b/contracts/tokenization/StableDebtToken.sol @@ -8,6 +8,7 @@ import {DebtTokenBase} from './base/DebtTokenBase.sol'; import {MathUtils} from '../libraries/math/MathUtils.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {IStableDebtToken} from './interfaces/IStableDebtToken.sol'; +import "@nomiclabs/buidler/console.sol"; /** * @title contract StableDebtToken @@ -21,6 +22,7 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { uint256 private _avgStableRate; mapping(address => uint40) _timestamps; + uint40 _totalSupplyTimestamp; constructor( address pool, @@ -68,7 +70,7 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { * @return the accumulated debt of the user **/ function balanceOf(address account) public virtual override view returns (uint256) { - uint256 accountBalance = principalBalanceOf(account); + uint256 accountBalance = super.balanceOf(account); uint256 stableRate = _usersData[account]; if (accountBalance == 0) { return 0; @@ -77,14 +79,15 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { stableRate, _timestamps[account] ); - return accountBalance.wadToRay().rayMul(cumulatedInterest).rayToWad(); + return accountBalance.rayMul(cumulatedInterest); } struct MintLocalVars { - uint256 supplyAfterMint; - uint256 supplyBeforeMint; + uint256 previousSupply; + uint256 nextSupply; uint256 amountInRay; uint256 newStableRate; + uint256 currentAvgStableRate; } /** @@ -108,8 +111,10 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { uint256 balanceIncrease ) = _calculateBalanceIncrease(user); - vars.supplyBeforeMint = totalSupply().add(balanceIncrease); - vars.supplyAfterMint = vars.supplyBeforeMint.add(amount); + //accrueing the interest accumulation to the stored total supply and caching it + vars.previousSupply = totalSupply(); + vars.currentAvgStableRate = _avgStableRate; + vars.nextSupply = _totalSupply = vars.previousSupply.add(amount); vars.amountInRay = amount.wadToRay(); @@ -122,16 +127,17 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { require(vars.newStableRate < (1 << 128), 'Debt token: stable rate overflow'); _usersData[user] = vars.newStableRate; + //updating the user and supply timestamp //solium-disable-next-line - _timestamps[user] = uint40(block.timestamp); + _totalSupplyTimestamp = _timestamps[user] = uint40(block.timestamp); //calculates the updated average stable rate - _avgStableRate = _avgStableRate - .rayMul(vars.supplyBeforeMint.wadToRay()) + _avgStableRate = vars.currentAvgStableRate + .rayMul(vars.previousSupply.wadToRay()) .add(rate.rayMul(vars.amountInRay)) - .rayDiv(vars.supplyAfterMint.wadToRay()); + .rayDiv(vars.nextSupply.wadToRay()); - _mint(user, amount.add(balanceIncrease)); + _mint(user, amount.add(balanceIncrease), vars.previousSupply); // transfer event to track balances emit Transfer(address(0), user, amount); @@ -158,30 +164,39 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { uint256 balanceIncrease ) = _calculateBalanceIncrease(user); - uint256 supplyBeforeBurn = totalSupply().add(balanceIncrease); - uint256 supplyAfterBurn = supplyBeforeBurn.sub(amount); + + uint256 previousSupply = totalSupply(); - if (supplyAfterBurn == 0) { + //since the total supply and each single user debt accrue separately, + //there might be accumulation errors so that the last borrower repaying + //might actually try to repay more than the available debt supply. + //in this case we simply set the total supply and the avg stable rate to 0 + if (previousSupply <= amount) { _avgStableRate = 0; + _totalSupply = 0; } else { + uint256 nextSupply = _totalSupply = previousSupply.sub(amount); _avgStableRate = _avgStableRate - .rayMul(supplyBeforeBurn.wadToRay()) + .rayMul(previousSupply.wadToRay()) .sub(_usersData[user].rayMul(amount.wadToRay())) - .rayDiv(supplyAfterBurn.wadToRay()); + .rayDiv(nextSupply.wadToRay()); } if (amount == currentBalance) { _usersData[user] = 0; _timestamps[user] = 0; + } else { //solium-disable-next-line _timestamps[user] = uint40(block.timestamp); } + //solium-disable-next-line + _totalSupplyTimestamp = uint40(block.timestamp); if (balanceIncrease > amount) { - _mint(user, balanceIncrease.sub(amount)); + _mint(user, balanceIncrease.sub(amount), previousSupply); } else { - _burn(user, amount.sub(balanceIncrease)); + _burn(user, amount.sub(balanceIncrease), previousSupply); } // transfer event to track balances @@ -189,4 +204,127 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease); } + + /** + * @dev Calculates the increase in balance since the last user interaction + * @param user The address of the user for which the interest is being accumulated + * @return The previous principal balance, the new principal balance, the balance increase + * and the new user index + **/ + function _calculateBalanceIncrease(address user) + internal + view + returns ( + uint256, + uint256, + uint256 + ) + { + uint256 previousPrincipalBalance = super.balanceOf(user); + + if (previousPrincipalBalance == 0) { + return (0, 0, 0); + } + + // Calculation of the accrued interest since the last accumulation + uint256 balanceIncrease = balanceOf(user).sub(previousPrincipalBalance); + + return ( + previousPrincipalBalance, + previousPrincipalBalance.add(balanceIncrease), + balanceIncrease + ); + } + + /** + * @dev returns the principal and total supply, the average borrow rate and the last supply update timestamp + **/ + function getSupplyData() public override view returns (uint256, uint256, uint256,uint40) { + uint256 avgRate = _avgStableRate; + return (super.totalSupply(), _calcTotalSupply(avgRate), avgRate, _totalSupplyTimestamp); + } + + /** + * @dev returns the the total supply and the average stable rate + **/ + function getTotalSupplyAndAvgRate() public override view returns (uint256, uint256) { + uint256 avgRate = _avgStableRate; + return (_calcTotalSupply(avgRate), avgRate); + } + + /** + * @dev returns the total supply + **/ + function totalSupply() public override view returns (uint256) { + return _calcTotalSupply(_avgStableRate); + } + + /** + * @dev returns the timestamp at which the total supply was updated + **/ + function getTotalSupplyLastUpdated() public override view returns(uint40) { + return _totalSupplyTimestamp; + } + + /** + * @dev Returns the principal debt balance of the user from + * @param user the user + * @return The debt balance of the user since the last burn/mint action + **/ + function principalBalanceOf(address user) external virtual override view returns (uint256) { + return super.balanceOf(user); + } + + + /** + * @dev calculates the total supply + * @param avgRate the average rate at which calculate the total supply + * @return The debt balance of the user since the last burn/mint action + **/ + function _calcTotalSupply(uint256 avgRate) internal view returns(uint256) { + uint256 principalSupply = super.totalSupply(); + + if (principalSupply == 0) { + return 0; + } + + uint256 cumulatedInterest = MathUtils.calculateCompoundedInterest( + avgRate, + _totalSupplyTimestamp + ); + + return principalSupply.rayMul(cumulatedInterest); + } + + /** + * @dev mints stable debt tokens to an user + * @param account the account receiving the debt tokens + * @param amount the amount being minted + * @param oldTotalSupply the total supply before the minting event + **/ + function _mint(address account, uint256 amount, uint256 oldTotalSupply) internal { + + uint256 oldAccountBalance = _balances[account]; + _balances[account] = oldAccountBalance.add(amount); + + if (address(_incentivesController) != address(0)) { + _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); + } + } + + /** + * @dev burns stable debt tokens of an user + * @param account the user getting his debt burned + * @param amount the amount being burned + * @param oldTotalSupply the total supply before the burning event + **/ + function _burn(address account, uint256 amount, uint256 oldTotalSupply) internal { + + uint256 oldAccountBalance = _balances[account]; + _balances[account] = oldAccountBalance.sub(amount, 'ERC20: burn amount exceeds balance'); + + if (address(_incentivesController) != address(0)) { + _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); + } + } } diff --git a/contracts/tokenization/VariableDebtToken.sol b/contracts/tokenization/VariableDebtToken.sol index 7ec1d8e3..5e0f2dff 100644 --- a/contracts/tokenization/VariableDebtToken.sol +++ b/contracts/tokenization/VariableDebtToken.sol @@ -39,79 +39,81 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken { * @return the debt balance of the user **/ function balanceOf(address user) public virtual override view returns (uint256) { - uint256 userBalance = principalBalanceOf(user); - uint256 index = _usersData[user]; - if (userBalance == 0) { + uint256 scaledBalance = super.balanceOf(user); + + if (scaledBalance == 0) { return 0; } - return - userBalance - .wadToRay() - .rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET)) - .rayDiv(index) - .rayToWad(); - } - - /** - * @dev returns the index of the last user action - * @return the user index - **/ - - function getUserIndex(address user) external virtual override view returns (uint256) { - return _usersData[user]; + return scaledBalance.rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET)); } /** * @dev mints new variable debt * @param user the user receiving the debt * @param amount the amount of debt being minted + * @param index the variable debt index of the reserve **/ - function mint(address user, uint256 amount) external override onlyLendingPool { - ( - uint256 previousBalance, - uint256 currentBalance, - uint256 balanceIncrease - ) = _calculateBalanceIncrease(user); - - _mint(user, amount.add(balanceIncrease)); - - uint256 newUserIndex = POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET); - require(newUserIndex < (1 << 128), 'Debt token: Index overflow'); - _usersData[user] = newUserIndex; + function mint( + address user, + uint256 amount, + uint256 index + ) external override onlyLendingPool { + + _mint(user, amount.rayDiv(index)); emit Transfer(address(0), user, amount); - emit MintDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex); + emit Mint(user, amount, index); } /** * @dev burns user variable debt * @param user the user which debt is burnt - * @param amount the amount of debt being burned + * @param index the variable debt index of the reserve **/ - function burn(address user, uint256 amount) external override onlyLendingPool { - ( - uint256 previousBalance, - uint256 currentBalance, - uint256 balanceIncrease - ) = _calculateBalanceIncrease(user); + function burn( + address user, + uint256 amount, + uint256 index + ) external override onlyLendingPool { + _burn(user, amount.rayDiv(index)); - if (balanceIncrease > amount) { - _mint(user, balanceIncrease.sub(amount)); - } else { - _burn(user, amount.sub(balanceIncrease)); - } - - uint256 newUserIndex = 0; - //if user not repaid everything - if (currentBalance != amount) { - newUserIndex = POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET); - require(newUserIndex < (1 << 128), 'Debt token: Index overflow'); - } - _usersData[user] = newUserIndex; - - // transfer event to track the balances emit Transfer(user, address(0), amount); - emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex); + emit Burn(user, amount, index); } + + /** + * @dev Returns the principal debt balance of the user from + * @return The debt balance of the user since the last burn/mint action + **/ + function scaledBalanceOf(address user) public virtual override view returns (uint256) { + return super.balanceOf(user); + } + + /** + * @dev Returns the total supply of the variable debt token. Represents the total debt accrued by the users + * @return the total supply + **/ + function totalSupply() public virtual override view returns (uint256) { + return super.totalSupply().rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET)); + } + + /** + * @dev Returns the scaled total supply of the variable debt token. Represents sum(borrows/index) + * @return the scaled total supply + **/ + function scaledTotalSupply() public virtual override view returns (uint256) { + return super.totalSupply(); + } + + /** + * @dev returns the principal balance of the user and principal total supply. + * @param user the address of the user + * @return the principal balance of the user + * @return the principal total supply + **/ + function getScaledUserBalanceAndSupply(address user) external override view returns (uint256, uint256){ + return (super.balanceOf(user), super.totalSupply()); + } + } diff --git a/contracts/tokenization/base/DebtTokenBase.sol b/contracts/tokenization/base/DebtTokenBase.sol index ceb51688..36d99d8e 100644 --- a/contracts/tokenization/base/DebtTokenBase.sol +++ b/contracts/tokenization/base/DebtTokenBase.sol @@ -65,14 +65,6 @@ abstract contract DebtTokenBase is IncentivizedERC20, VersionedInitializable { return UNDERLYING_ASSET; } - /** - * @dev Returns the principal debt balance of the user from - * @return The debt balance of the user since the last burn/mint action - **/ - function principalBalanceOf(address user) public view returns (uint256) { - return super.balanceOf(user); - } - /** * @dev Being non transferrable, the debt token does not implement any of the * standard ERC20 functions for transfer and allowance. @@ -133,35 +125,4 @@ abstract contract DebtTokenBase is IncentivizedERC20, VersionedInitializable { subtractedValue; revert('ALLOWANCE_NOT_SUPPORTED'); } - - /** - * @dev Calculates the increase in balance since the last user interaction - * @param user The address of the user for which the interest is being accumulated - * @return The previous principal balance, the new principal balance, the balance increase - * and the new user index - **/ - function _calculateBalanceIncrease(address user) - internal - view - returns ( - uint256, - uint256, - uint256 - ) - { - uint256 previousPrincipalBalance = principalBalanceOf(user); - - if (previousPrincipalBalance == 0) { - return (0, 0, 0); - } - - // Calculation of the accrued interest since the last accumulation - uint256 balanceIncrease = balanceOf(user).sub(previousPrincipalBalance); - - return ( - previousPrincipalBalance, - previousPrincipalBalance.add(balanceIncrease), - balanceIncrease - ); - } } diff --git a/contracts/tokenization/interfaces/IAToken.sol b/contracts/tokenization/interfaces/IAToken.sol index e1975074..b5e182bc 100644 --- a/contracts/tokenization/interfaces/IAToken.sol +++ b/contracts/tokenization/interfaces/IAToken.sol @@ -2,8 +2,9 @@ pragma solidity ^0.6.8; import {IERC20} from '../../interfaces/IERC20.sol'; +import {IScaledBalanceToken} from './IScaledBalanceToken.sol'; -interface IAToken is IERC20 { +interface IAToken is IERC20, IScaledBalanceToken { /** * @dev emitted after aTokens are burned * @param from the address performing the redeem @@ -16,15 +17,6 @@ interface IAToken is IERC20 { uint256 value, uint256 index ); - - /** - * @dev emitted after the mint action - * @param from the address performing the mint - * @param value the amount to be minted - * @param index the last index of the reserve - **/ - event Mint(address indexed from, uint256 value, uint256 index); - /** * @dev emitted during the transfer action * @param from the address from which the tokens are being transferred @@ -38,7 +30,6 @@ interface IAToken is IERC20 { uint256 value, uint256 index ); - /** * @dev burns the aTokens and sends the equivalent amount of underlying to the target. * only lending pools can call this function @@ -53,13 +44,11 @@ interface IAToken is IERC20 { ) external; /** - * @dev mints aTokens to user - * only lending pools can call this function - * @param user the address receiving the minted tokens - * @param amount the amount of tokens to mint - * @param index the liquidity index - */ - function mint(address user, uint256 amount, uint256 index) external; + * @dev mints aTokens to the reserve treasury + * @param amount the amount to mint + * @param index the liquidity index of the reserve + **/ + function mintToTreasury(uint256 amount, uint256 index) external; /** * @dev transfers tokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken @@ -74,28 +63,7 @@ interface IAToken is IERC20 { uint256 value ) external; - /** - * @dev returns the principal balance of the user. The principal balance is the last - * updated stored balance, which does not consider the perpetually accruing interest. - * @param user the address of the user - * @return the principal balance of the user - **/ - function scaledBalanceOf(address user) external view returns (uint256); - - /** - * @dev returns the principal balance of the user and principal total supply. - * @param user the address of the user - * @return the principal balance of the user - * @return the principal total supply - **/ - function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256); - - /** - * @dev Used to validate transfers before actually executing them. - * @param user address of the user to check - * @param amount the amount to check - * @return true if the user can transfer amount, false otherwise - **/ + function isTransferAllowed(address user, uint256 amount) external view returns (bool); /** @@ -104,6 +72,7 @@ interface IAToken is IERC20 { * @param amount the amount to transfer * @return the amount transferred **/ - function transferUnderlyingTo(address user, uint256 amount) external returns (uint256); + + } diff --git a/contracts/tokenization/interfaces/IScaledBalanceToken.sol b/contracts/tokenization/interfaces/IScaledBalanceToken.sol new file mode 100644 index 00000000..9bcd2427 --- /dev/null +++ b/contracts/tokenization/interfaces/IScaledBalanceToken.sol @@ -0,0 +1,49 @@ + +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface IScaledBalanceToken { + + /** + * @dev emitted after the mint action + * @param from the address performing the mint + * @param value the amount to be minted + * @param index the last index of the reserve + **/ + event Mint(address indexed from, uint256 value, uint256 index); + + /** + * @dev mints aTokens to user + * only lending pools can call this function + * @param user the address receiving the minted tokens + * @param amount the amount of tokens to mint + * @param index the liquidity index + */ + function mint( + address user, + uint256 amount, + uint256 index + ) external; + + /** + * @dev returns the principal balance of the user. The principal balance is the last + * updated stored balance, which does not consider the perpetually accruing interest. + * @param user the address of the user + * @return the principal balance of the user + **/ + function scaledBalanceOf(address user) external view returns (uint256); + + /** + * @dev returns the principal balance of the user and principal total supply. + * @param user the address of the user + * @return the principal balance of the user + * @return the principal total supply + **/ + function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256); + + /** + * @dev Returns the scaled total supply of the variable debt token. Represents sum(borrows/index) + * @return the scaled total supply + **/ + function scaledTotalSupply() external view returns (uint256); +} diff --git a/contracts/tokenization/interfaces/IStableDebtToken.sol b/contracts/tokenization/interfaces/IStableDebtToken.sol index 0aaccd26..901a1178 100644 --- a/contracts/tokenization/interfaces/IStableDebtToken.sol +++ b/contracts/tokenization/interfaces/IStableDebtToken.sol @@ -84,4 +84,26 @@ interface IStableDebtToken { * @return the timestamp **/ function getUserLastUpdated(address user) external view returns (uint40); + + /** + * @dev returns the principal, the total supply and the average stable rate + **/ + function getSupplyData() external view returns (uint256, uint256, uint256, uint40); + + /** + * @dev returns the timestamp of the last update of the total supply + * @return the timestamp + **/ + function getTotalSupplyLastUpdated() external view returns (uint40); + + /** + * @dev returns the total supply and the average stable rate + **/ + function getTotalSupplyAndAvgRate() external view returns (uint256, uint256); + + /** + * @dev Returns the principal debt balance of the user + * @return The debt balance of the user since the last burn/mint action + **/ + function principalBalanceOf(address user) external view returns (uint256); } diff --git a/contracts/tokenization/interfaces/IVariableDebtToken.sol b/contracts/tokenization/interfaces/IVariableDebtToken.sol index fdd86a0d..2e6bae93 100644 --- a/contracts/tokenization/interfaces/IVariableDebtToken.sol +++ b/contracts/tokenization/interfaces/IVariableDebtToken.sol @@ -1,66 +1,36 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.6.8; +import {IScaledBalanceToken} from './IScaledBalanceToken.sol'; + /** * @title interface IVariableDebtToken * @author Aave * @notice defines the basic interface for a variable debt token. - * @dev does not inherit from IERC20 to save in contract size **/ -interface IVariableDebtToken { - /** - * @dev emitted when new variable debt is minted - * @param user the user receiving the debt - * @param amount the amount of debt being minted - * @param previousBalance the previous balance of the user - * @param currentBalance the current balance of the user - * @param balanceIncrease the debt accumulated since the last action - * @param index the index of the user - **/ - event MintDebt( - address user, - uint256 amount, - uint256 previousBalance, - uint256 currentBalance, - uint256 balanceIncrease, - uint256 index - ); - +interface IVariableDebtToken is IScaledBalanceToken { + /** * @dev emitted when variable debt is burnt * @param user the user which debt has been burned * @param amount the amount of debt being burned - * @param previousBalance the previous balance of the user - * @param currentBalance the current balance of the user - * @param balanceIncrease the debt accumulated since the last action * @param index the index of the user **/ - event BurnDebt( - address user, + event Burn( + address indexed user, uint256 amount, - uint256 previousBalance, - uint256 currentBalance, - uint256 balanceIncrease, uint256 index ); - /** - * @dev mints new variable debt - * @param user the user receiving the debt - * @param amount the amount of debt being minted - **/ - function mint(address user, uint256 amount) external; - - /** + /** * @dev burns user variable debt * @param user the user which debt is burnt - * @param amount the amount of debt being burned + * @param index the variable debt index of the reserve **/ - function burn(address user, uint256 amount) external; - - /** - * @dev returns the last index of the user - * @return the index of the user - **/ - function getUserIndex(address user) external view returns (uint256); + function burn( + address user, + uint256 amount, + uint256 index + ) external; + } diff --git a/deployed-contracts.json b/deployed-contracts.json index 60c63dd8..dd305039 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -5,7 +5,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x9Dc554694756dC303a087e04bA6918C333Bc26a7", + "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -19,7 +19,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xAfC307938C1c0035942c141c31524504c89Aaa8B", + "address": "0xa4bcDF64Cdd5451b6ac3743B414124A6299B65FF", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -33,7 +33,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x73DE1e0ab6A5C221258703bc546E0CAAcCc6EC87", + "address": "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -65,7 +65,7 @@ "address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8" }, "localhost": { - "address": "0x65e0Cd5B8904A02f2e00BC6f58bf881998D54BDe" + "address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8" }, "coverage": { "address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8" @@ -81,7 +81,7 @@ "address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e" }, "localhost": { - "address": "0x5d12dDe3286D94E0d85F9D3B01B7099cfA0aBCf1" + "address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e" }, "coverage": { "address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e" @@ -93,7 +93,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xbeA90474c2F3C7c43bC7c36CaAf5272c927Af5a1", + "address": "0x1750499D05Ed1674d822430FB960d5F6731fDf64", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -107,7 +107,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x19E42cA990cF697D3dda0e59131215C43bB6989F", + "address": "0xEC1C93A9f6a9e18E97784c76aC52053587FcDB89", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -121,7 +121,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xE30c3983E51bC9d6baE3E9437710a1459e21e81F", + "address": "0x7B6C3e5486D9e6959441ab554A889099eed76290", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -135,7 +135,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xDf69898e844197a24C658CcF9fD53dF15948dc8b", + "address": "0xD83D2773a7873ae2b5f8Fb92097e20a8C64F691E", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -149,7 +149,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xBe6d8642382C241c9B4B50c89574DbF3f4181E7D", + "address": "0x626FdE749F9d499d3777320CAf29484B624ab84a", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -207,7 +207,7 @@ "address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA" }, "localhost": { - "address": "0xAd49512dFBaD6fc13D67d3935283c0606812E962" + "address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA" }, "coverage": { "address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA" @@ -219,7 +219,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xA29C2A7e59aa49C71aF084695337E3AA5e820758", + "address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -233,7 +233,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xbe66dC9DFEe580ED968403e35dF7b5159f873df8", + "address": "0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -247,7 +247,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x93AfC6Df4bB8F62F2493B19e577f8382c0BA9EBC", + "address": "0x8858eeB3DfffA017D4BCE9801D340D36Cf895CCf", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -261,7 +261,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x75Ded61646B5945BdDd0CD9a9Db7c8288DA6F810", + "address": "0x0078371BDeDE8aAc7DeBfFf451B74c5EDB385Af7", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -275,7 +275,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xdE7c40e675bF1aA45c18cCbaEb9662B16b0Ddf7E", + "address": "0xf4e77E5Da47AC3125140c470c71cBca77B5c638c", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -289,7 +289,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xDFbeeed692AA81E7f86E72F7ACbEA2A1C4d63544", + "address": "0x3619DbE27d7c1e7E91aA738697Ae7Bc5FC3eACA5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -303,7 +303,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5191aA68c7dB195181Dd2441dBE23A48EA24b040", + "address": "0x038B86d9d8FAFdd0a02ebd1A476432877b0107C8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -317,7 +317,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8F9422aa37215c8b3D1Ea1674138107F84D68F26", + "address": "0x1A1FEe7EeD918BD762173e4dc5EfDB8a78C924A8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -331,7 +331,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xa89E20284Bd638F31b0011D0fC754Fc9d2fa73e3", + "address": "0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -345,7 +345,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xaA935993065F2dDB1d13623B1941C7AEE3A60F23", + "address": "0xc4905364b78a742ccce7B890A89514061E47068D", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -359,7 +359,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x35A2624888e207e4B3434E9a9E250bF6Ee68FeA3", + "address": "0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -373,7 +373,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1f569c307949a908A4b8Ff7453a88Ca0b8D8df13", + "address": "0x8B5B7a6055E54a36fF574bbE40cf2eA68d5554b3", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -387,7 +387,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x4301cb254CCc126B9eb9cbBE030C6FDA2FA16D4a", + "address": "0xEcc0a6dbC0bb4D51E4F84A315a9e5B0438cAD4f0", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -401,7 +401,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x0766c9592a8686CAB0081b4f35449462c6e82F11", + "address": "0x20Ce94F404343aD2752A2D01b43fa407db9E0D00", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -415,7 +415,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xaF6D34adD35E1A565be4539E4d1069c48A49C953", + "address": "0x1d80315fac6aBd3EfeEbE97dEc44461ba7556160", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -429,7 +429,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x48bb3E35D2D6994374db457a6Bf61de2d9cC8E49", + "address": "0x2D8553F9ddA85A9B3259F6Bf26911364B85556F5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -443,7 +443,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1E59BA56B1F61c3Ee946D8c7e2994B4A9b0cA45C", + "address": "0x52d3b94181f8654db2530b0fEe1B19173f519C52", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -457,7 +457,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x53813198c75959DDB604462831d8989C29152164", + "address": "0xd15468525c35BDBC1eD8F2e09A00F8a173437f2f", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -471,7 +471,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x0eD6115873ce6B807a03FE0df1f940387779b729", + "address": "0x7e35Eaf7e8FBd7887ad538D4A38Df5BbD073814a", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -485,7 +485,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xFFfDa24e7E3d5F89a24278f53d6f0F81B3bE0d6B", + "address": "0x5bcb88A0d20426e451332eE6C4324b0e663c50E0", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -499,7 +499,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5889354f21A1C8D8D2f82669d778f6Dab778B519", + "address": "0x3521eF8AaB0323004A6dD8b03CE890F4Ea3A13f5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -513,7 +513,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x09F7bF33B3F8922268B34103af3a8AF83148C9B1", + "address": "0x53369fd4680FfE3DfF39Fc6DDa9CfbfD43daeA2E", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -527,7 +527,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8f3966F7d53Fd5f12b701C8835e1e32541613869", + "address": "0xB00cC45B4a7d3e1FEE684cFc4417998A1c183e6d", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -541,7 +541,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x9Dc554694756dC303a087e04bA6918C333Bc26a7", + "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -554,7 +554,7 @@ "address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4" }, "localhost": { - "address": "0x9305d862ee95a899b83906Cd9CB666aC269E5f66" + "address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4" }, "coverage": { "address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4" @@ -566,7 +566,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x02BB514187B830d6A2111197cd7D8cb60650B970", + "address": "0xB660Fdd109a95718cB9d20E3A89EE6cE342aDcB6", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -580,7 +580,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x6774Ce86Abf5EBB22E9F45b5f55daCbB4170aD7f", + "address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -590,7 +590,7 @@ }, "AToken": { "localhost": { - "address": "0x007C1a44e85bDa8F562F916685A9DC8BdC6542bF", + "address": "0xA0AB1cB92A4AF81f84dCd258155B5c25D247b54E", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "buidlerevm": { @@ -608,7 +608,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xFBdF1E93D0D88145e3CcA63bf8d513F83FB0903b", + "address": "0xEcb928A3c079a1696Aa5244779eEc3dE1717fACd", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -622,7 +622,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xEcb928A3c079a1696Aa5244779eEc3dE1717fACd", + "address": "0xf784709d2317D872237C4bC22f867d1BAe2913AB", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -636,7 +636,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xE45fF4A0A8D0E9734C73874c034E03594E15ba28", + "address": "0xDFbeeed692AA81E7f86E72F7ACbEA2A1C4d63544", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -650,7 +650,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5cCC6Abc4c9F7262B9485797a848Ec6CC28A11dF", + "address": "0x5191aA68c7dB195181Dd2441dBE23A48EA24b040", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "coverage": { @@ -664,6 +664,9 @@ }, "coverage": { "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2" + }, + "localhost": { + "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2" } } } \ No newline at end of file diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 49f0f401..147135a2 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -31,6 +31,7 @@ import BigNumber from 'bignumber.js'; import {Ierc20Detailed} from '../types/Ierc20Detailed'; import {StableDebtToken} from '../types/StableDebtToken'; import {VariableDebtToken} from '../types/VariableDebtToken'; +import { ZERO_ADDRESS } from './constants'; import {MockSwapAdapter} from '../types/MockSwapAdapter'; import {signTypedData_v4, TypedData} from 'eth-sig-util'; import {fromRpcSig, ECDSASignature} from 'ethereumjs-util'; @@ -290,13 +291,15 @@ export const deployVariableDebtToken = async ([ export const deployGenericAToken = async ([ poolAddress, underlyingAssetAddress, + reserveTreasuryAddress, name, symbol, incentivesController, -]: [tEthereumAddress, tEthereumAddress, string, string, tEthereumAddress]) => { +]: [tEthereumAddress, tEthereumAddress, tEthereumAddress, string, string, tEthereumAddress]) => { const token = await deployContract(eContractid.AToken, [ poolAddress, underlyingAssetAddress, + reserveTreasuryAddress, name, symbol, incentivesController, @@ -353,6 +356,21 @@ export const getAToken = async (address?: tEthereumAddress) => { ); }; +export const getStableDebtToken = async (address?: tEthereumAddress) => { + return await getContract( + eContractid.StableDebtToken, + address || (await getDb().get(`${eContractid.StableDebtToken}.${BRE.network.name}`).value()).address + ); +}; + +export const getVariableDebtToken = async (address?: tEthereumAddress) => { + return await getContract( + eContractid.VariableDebtToken, + address || (await getDb().get(`${eContractid.VariableDebtToken}.${BRE.network.name}`).value()).address + ); +}; + + export const getMintableErc20 = async (address: tEthereumAddress) => { return await getContract( eContractid.MintableERC20, diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index 43ac2fd6..d1613e22 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -245,6 +245,7 @@ const initReserves = async ( const aToken = await deployGenericAToken([ lendingPool.address, tokenAddress, + ZERO_ADDRESS, `Aave interest bearing ${assetSymbol === 'WETH' ? 'ETH' : assetSymbol}`, `a${assetSymbol === 'WETH' ? 'ETH' : assetSymbol}`, incentivesController, diff --git a/test/configurator.spec.ts b/test/configurator.spec.ts index 89260ff3..0fa3289c 100644 --- a/test/configurator.spec.ts +++ b/test/configurator.spec.ts @@ -180,6 +180,23 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { ).to.be.revertedWith(CALLER_NOT_AAVE_ADMIN); }); + + it('Changes the reserve factor of the reserve', async () => { + const {configurator, pool, weth} = testEnv; + await configurator.setReserveFactor(weth.address, '1000'); + const {reserveFactor} = await pool.getReserveConfigurationData(weth.address); + expect(reserveFactor.toString()).to.be.bignumber.equal('1000', 'Invalid reserve factor'); + }); + + it('Check the onlyLendingPoolManager on setReserveFactor', async () => { + const {configurator, users, weth} = testEnv; + await expect( + configurator.connect(users[2].signer).setReserveFactor(weth.address, '2000'), + CALLER_NOT_AAVE_ADMIN + ).to.be.revertedWith(CALLER_NOT_AAVE_ADMIN); + }); + + it('Changes liquidation threshold of the reserve', async () => { const {configurator, pool, weth} = testEnv; await configurator.setLiquidationThreshold(weth.address, '75'); diff --git a/test/flash-liquidation-with-collateral.spec.ts b/test/flash-liquidation-with-collateral.spec.ts index d30787c9..3b20f79c 100644 --- a/test/flash-liquidation-with-collateral.spec.ts +++ b/test/flash-liquidation-with-collateral.spec.ts @@ -125,7 +125,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn ).minus(usdcUserDataBefore.currentVariableDebt); const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance( - usdcUserDataBefore, + usdcUserDataBefore.principalStableDebt, + usdcUserDataBefore.stableBorrowRate, + usdcUserDataBefore.stableRateLastUpdated, new BigNumber(repayWithCollateralTimestamp) ).minus(usdcUserDataBefore.currentStableDebt); @@ -233,7 +235,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn userData: usdcUserDataBefore, } = await getContractsData(usdc.address, user.address, testEnv); - const amountToRepay = usdcReserveDataBefore.totalBorrowsVariable.dividedBy(2).toFixed(0); + const amountToRepay = usdcReserveDataBefore.totalVariableDebt.dividedBy(2).toFixed(0); await mockSwapAdapter.setAmountToReturn(amountToRepay); await waitForTx( @@ -295,7 +297,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn 'INVALID_DEBT_POSITION' ); - expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.almostEqual( new BigNumber(wethUserDataBefore.currentATokenBalance).minus( expectedCollateralLiquidated.toString() ), @@ -368,7 +370,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn userData: usdcUserDataBefore, } = await getContractsData(usdc.address, user.address, testEnv); - const amountToRepay = usdcReserveDataBefore.totalBorrowsVariable.toFixed(0); + const amountToRepay = usdcReserveDataBefore.totalVariableDebt.toFixed(0); await mockSwapAdapter.setAmountToReturn(amountToRepay); await waitForTx( @@ -486,7 +488,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn testEnv ); - const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.toString(); + const amountToRepay = daiReserveDataBefore.totalVariableDebt.toString(); await waitForTx(await mockSwapAdapter.setTryReentrancy(true)); @@ -522,7 +524,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn ); // First half - const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.dividedBy(2).toString(); + const amountToRepay = daiReserveDataBefore.totalVariableDebt.dividedBy(2).toString(); await mockSwapAdapter.setAmountToReturn(amountToRepay); await expect( @@ -568,7 +570,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn ); // First half - const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.multipliedBy(0.6).toString(); + const amountToRepay = daiReserveDataBefore.totalVariableDebt.multipliedBy(0.6).toFixed(0).toString(); await mockSwapAdapter.setAmountToReturn(amountToRepay); await waitForTx( @@ -654,7 +656,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn await increaseTime(1000); // Repay the remaining DAI - const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.toString(); + const amountToRepay = daiReserveDataBefore.totalVariableDebt.toString(); await mockSwapAdapter.setAmountToReturn(amountToRepay); const receipt = await waitForTx( @@ -816,7 +818,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn new BigNumber(repayWithCollateralTimestamp) ).minus(usdcUserDataBefore.currentVariableDebt); - expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( new BigNumber(usdcUserDataBefore.currentVariableDebt) .minus(expectedDebtCovered.toString()) .plus(expectedVariableDebtIncrease), diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 036a30fd..c2be90ed 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -62,8 +62,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const currentLiquidityIndex = reserveData.liquidityIndex; const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString()) - .plus(reserveData.totalBorrowsStable.toString()) - .plus(reserveData.totalBorrowsVariable.toString()); + .plus(reserveData.totalStableDebt.toString()) + .plus(reserveData.totalVariableDebt.toString()); expect(totalLiquidity.toString()).to.be.equal('1000720000000000000'); expect(currentLiquidityRate.toString()).to.be.equal('0'); @@ -89,8 +89,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const currentLiquidityIndex = reserveData.liquidityIndex; const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString()) - .plus(reserveData.totalBorrowsStable.toString()) - .plus(reserveData.totalBorrowsVariable.toString()); + .plus(reserveData.totalStableDebt.toString()) + .plus(reserveData.totalVariableDebt.toString()); expect(totalLiquidity.toString()).to.be.equal('1001620648000000000'); expect(currentLiqudityRate.toString()).to.be.equal('0'); @@ -244,8 +244,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const userData = await pool.getUserReserveData(usdc.address, depositor.address); const totalLiquidity = reserveData.availableLiquidity - .add(reserveData.totalBorrowsStable) - .add(reserveData.totalBorrowsVariable) + .add(reserveData.totalStableDebt) + .add(reserveData.totalVariableDebt) .toString(); const currentLiqudityRate = reserveData.liquidityRate.toString(); const currentLiquidityIndex = reserveData.liquidityIndex.toString(); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 3e60955d..69c9c6ce 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -32,6 +32,7 @@ import {waitForTx} from '../__setup.spec'; import {ContractReceipt} from 'ethers'; import {AToken} from '../../types/AToken'; import {RateMode, tEthereumAddress} from '../../helpers/types'; +import { time } from 'console'; const {expect} = chai; @@ -48,7 +49,8 @@ const almostEqualOrEqual = function ( key === 'marketStableRate' || key === 'symbol' || key === 'aTokenAddress' || - key === 'decimals' + key === 'decimals' || + key === 'totalStableDebtLastUpdated' ) { // skipping consistency check on accessory data return; @@ -262,10 +264,6 @@ export const withdraw = async ( txCost ); - const actualAmountWithdrawn = userDataBefore.currentATokenBalance.minus( - expectedUserData.currentATokenBalance - ); - expectEqual(reserveDataAfter, expectedReserveData); expectEqual(userDataAfter, expectedUserData); @@ -371,8 +369,7 @@ export const borrow = async ( expectedReserveData, userDataBefore, txTimestamp, - timestamp, - txCost + timestamp ); expectEqual(reserveDataAfter, expectedReserveData); @@ -475,8 +472,7 @@ export const repay = async ( user.address, onBehalfOf.address, txTimestamp, - timestamp, - txCost + timestamp ); expectEqual(reserveDataAfter, expectedReserveData); @@ -741,9 +737,12 @@ export const getContractsData = async ( sender?: string ) => { const {pool} = testEnv; - const reserveData = await getReserveData(pool, reserve); - const userData = await getUserData(pool, reserve, user, sender || user); - const timestamp = await timeLatest(); + + const [userData, reserveData, timestamp] = await Promise.all([ + getUserData(pool, reserve, user, sender || user), + getReserveData(pool, reserve), + timeLatest(), + ]); return { reserveData, diff --git a/test/helpers/scenarios/borrow-repay-stable.json b/test/helpers/scenarios/borrow-repay-stable.json index 9d2348e9..87e7de0b 100644 --- a/test/helpers/scenarios/borrow-repay-stable.json +++ b/test/helpers/scenarios/borrow-repay-stable.json @@ -626,6 +626,17 @@ }, "expected": "success" }, + { + "name": "repay", + "args": { + "reserve": "DAI", + "amount": "-1", + "user": "1", + "onBehalfOf": "1", + "borrowRateMode": "variable" + }, + "expected": "success" + }, { "name": "withdraw", "args": { diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index 98431d36..7ef73c48 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -7,7 +7,7 @@ import { EXCESS_UTILIZATION_RATE, ZERO_ADDRESS, } from '../../../helpers/constants'; -import {IReserveParams, iAavePoolAssets, RateMode} from '../../../helpers/types'; +import {IReserveParams, iAavePoolAssets, RateMode, tEthereumAddress} from '../../../helpers/types'; import './math'; import {ReserveData, UserReserveData} from './interfaces'; @@ -30,19 +30,21 @@ export const calcExpectedUserDataAfterDeposit = ( ): UserReserveData => { const expectedUserData = {}; - expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( - userDataBeforeAction, + expectedUserData.currentStableDebt = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, txTimestamp ); - expectedUserData.currentVariableDebt = expectedUserData.principalStableDebt = calcExpectedVariableDebtTokenBalance( + expectedUserData.currentVariableDebt = calcExpectedVariableDebtTokenBalance( reserveDataBeforeAction, userDataBeforeAction, txTimestamp ); expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; - expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; + expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt; expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate; expectedUserData.stableRateLastUpdated = userDataBeforeAction.stableRateLastUpdated; @@ -71,7 +73,9 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited); expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( - userDataBeforeAction, + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, txTimestamp ); @@ -115,10 +119,12 @@ export const calcExpectedUserDataAfterWithdraw = ( expectedUserData.currentATokenBalance = aTokenBalance.minus(amountWithdrawn); expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; - expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; + expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt; expectedUserData.currentStableDebt = calcExpectedStableDebtTokenBalance( - userDataBeforeAction, + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, txTimestamp ); @@ -166,27 +172,6 @@ export const calcExpectedReserveDataAfterDeposit = ( reserveDataBeforeAction.availableLiquidity ).plus(amountDeposited); - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable; - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable; - expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; - - expectedReserveData.utilizationRate = calcExpectedUtilizationRate( - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.totalLiquidity - ); - const rates = calcExpectedInterestRates( - reserveDataBeforeAction.symbol, - reserveDataBeforeAction.marketStableRate, - expectedReserveData.utilizationRate, - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.averageStableBorrowRate - ); - expectedReserveData.liquidityRate = rates[0]; - expectedReserveData.stableBorrowRate = rates[1]; - expectedReserveData.variableBorrowRate = rates[2]; - expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( reserveDataBeforeAction, @@ -197,6 +182,37 @@ export const calcExpectedReserveDataAfterDeposit = ( txTimestamp ); + expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + txTimestamp + ); + expectedReserveData.totalVariableDebt = calcExpectedTotalVariableDebt( + reserveDataBeforeAction, + expectedReserveData.variableBorrowIndex + ); + + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt; + expectedReserveData.principalStableDebt = reserveDataBeforeAction.principalStableDebt; + + expectedReserveData.utilizationRate = calcExpectedUtilizationRate( + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.totalLiquidity + ); + const rates = calcExpectedInterestRates( + reserveDataBeforeAction.symbol, + reserveDataBeforeAction.marketStableRate, + expectedReserveData.utilizationRate, + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.averageStableBorrowRate + ); + expectedReserveData.liquidityRate = rates[0]; + expectedReserveData.stableBorrowRate = rates[1]; + expectedReserveData.variableBorrowRate = rates[2]; + return expectedReserveData; }; @@ -218,35 +234,13 @@ export const calcExpectedReserveDataAfterWithdraw = ( ).toFixed(); } - expectedReserveData.totalLiquidity = new BigNumber(reserveDataBeforeAction.totalLiquidity).minus( - amountWithdrawn - ); expectedReserveData.availableLiquidity = new BigNumber( reserveDataBeforeAction.availableLiquidity ).minus(amountWithdrawn); - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable; - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable; - expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; + expectedReserveData.principalStableDebt = reserveDataBeforeAction.principalStableDebt; + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt; - expectedReserveData.utilizationRate = calcExpectedUtilizationRate( - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.totalLiquidity - ); - const rates = calcExpectedInterestRates( - reserveDataBeforeAction.symbol, - reserveDataBeforeAction.marketStableRate, - expectedReserveData.utilizationRate, - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.averageStableBorrowRate - ); - expectedReserveData.liquidityRate = rates[0]; - expectedReserveData.stableBorrowRate = rates[1]; - expectedReserveData.variableBorrowRate = rates[2]; - - expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( reserveDataBeforeAction, txTimestamp @@ -256,6 +250,40 @@ export const calcExpectedReserveDataAfterWithdraw = ( txTimestamp ); + expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + txTimestamp + ); + expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + + expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; + + expectedReserveData.totalLiquidity = new BigNumber(reserveDataBeforeAction.availableLiquidity) + .minus(amountWithdrawn) + .plus(expectedReserveData.totalVariableDebt) + .plus(expectedReserveData.totalStableDebt); + + expectedReserveData.utilizationRate = calcExpectedUtilizationRate( + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.totalLiquidity + ); + const rates = calcExpectedInterestRates( + reserveDataBeforeAction.symbol, + reserveDataBeforeAction.marketStableRate, + expectedReserveData.utilizationRate, + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.averageStableBorrowRate + ); + expectedReserveData.liquidityRate = rates[0]; + expectedReserveData.stableBorrowRate = rates[1]; + expectedReserveData.variableBorrowRate = rates[2]; + return expectedReserveData; }; @@ -273,78 +301,167 @@ export const calcExpectedReserveDataAfterBorrow = ( const amountBorrowedBN = new BigNumber(amountBorrowed); - const userStableBorrowBalance = calcExpectedStableDebtTokenBalance( - userDataBeforeAction, - txTimestamp - ); - - const userVariableBorrowBalance = calcExpectedVariableDebtTokenBalance( - reserveDataBeforeAction, - userDataBeforeAction, - txTimestamp - ); - - if (borrowRateMode == RateMode.Stable) { - const debtAccrued = userStableBorrowBalance.minus(userDataBeforeAction.principalStableDebt); - - expectedReserveData.totalLiquidity = reserveDataBeforeAction.totalLiquidity.plus(debtAccrued); - - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable - .plus(amountBorrowedBN) - .plus(debtAccrued); - - expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( - reserveDataBeforeAction.averageStableBorrowRate, - reserveDataBeforeAction.totalBorrowsStable.plus(debtAccrued), - amountBorrowedBN, - reserveDataBeforeAction.stableBorrowRate - ); - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable; - } else { - const debtAccrued = userVariableBorrowBalance.minus(userDataBeforeAction.principalVariableDebt); - expectedReserveData.totalLiquidity = reserveDataBeforeAction.totalLiquidity.plus(debtAccrued); - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable - .plus(amountBorrowedBN) - .plus(debtAccrued); - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable; - expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; - } - - expectedReserveData.availableLiquidity = reserveDataBeforeAction.availableLiquidity.minus( - amountBorrowedBN - ); - - expectedReserveData.utilizationRate = calcExpectedUtilizationRate( - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.totalLiquidity - ); - - const rates = calcExpectedInterestRates( - reserveDataBeforeAction.symbol, - reserveDataBeforeAction.marketStableRate, - expectedReserveData.utilizationRate, - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.averageStableBorrowRate - ); - expectedReserveData.liquidityRate = rates[0]; - - expectedReserveData.stableBorrowRate = rates[1]; - - expectedReserveData.variableBorrowRate = rates[2]; - expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( reserveDataBeforeAction, txTimestamp ); + expectedReserveData.variableBorrowIndex = calcExpectedVariableBorrowIndex( reserveDataBeforeAction, txTimestamp ); + expectedReserveData.availableLiquidity = reserveDataBeforeAction.availableLiquidity.minus( + amountBorrowedBN + ); + expectedReserveData.lastUpdateTimestamp = txTimestamp; + if (borrowRateMode == RateMode.Stable) { + + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt; + + const expectedVariableDebtAfterTx = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + + const expectedStableDebtUntilTx = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + txTimestamp + ); + + expectedReserveData.principalStableDebt = expectedStableDebtUntilTx.plus(amountBorrowedBN); + + expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( + reserveDataBeforeAction.averageStableBorrowRate, + expectedStableDebtUntilTx, + amountBorrowedBN, + reserveDataBeforeAction.stableBorrowRate + ); + + const totalLiquidityAfterTx = expectedReserveData.availableLiquidity + .plus(expectedReserveData.principalStableDebt) + .plus(expectedVariableDebtAfterTx); + + const utilizationRateAfterTx = calcExpectedUtilizationRate( + expectedReserveData.principalStableDebt, //the expected principal debt is the total debt immediately after the tx + expectedVariableDebtAfterTx, + totalLiquidityAfterTx + ); + + const ratesAfterTx = calcExpectedInterestRates( + reserveDataBeforeAction.symbol, + reserveDataBeforeAction.marketStableRate, + utilizationRateAfterTx, + expectedReserveData.principalStableDebt, + expectedVariableDebtAfterTx, + expectedReserveData.averageStableBorrowRate + ); + + expectedReserveData.liquidityRate = ratesAfterTx[0]; + + expectedReserveData.stableBorrowRate = ratesAfterTx[1]; + + expectedReserveData.variableBorrowRate = ratesAfterTx[2]; + + expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( + expectedReserveData.principalStableDebt, + expectedReserveData.averageStableBorrowRate, + txTimestamp, + currentTimestamp + ); + + expectedReserveData.totalVariableDebt = reserveDataBeforeAction.scaledVariableDebt.rayMul( + calcExpectedReserveNormalizedDebt( + expectedReserveData.variableBorrowRate, + expectedReserveData.variableBorrowIndex, + txTimestamp, + currentTimestamp + ) + ); + + expectedReserveData.totalLiquidity = expectedReserveData.availableLiquidity + .plus(expectedReserveData.totalVariableDebt) + .plus(expectedReserveData.totalStableDebt); + + expectedReserveData.utilizationRate = calcExpectedUtilizationRate( + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.totalLiquidity + ); + } else { + + expectedReserveData.principalStableDebt = reserveDataBeforeAction.principalStableDebt; + + const totalStableDebtAfterTx = calcExpectedStableDebtTokenBalance( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + txTimestamp + ); + + expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + currentTimestamp + ); + + expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; + + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt.plus( + amountBorrowedBN.rayDiv(expectedReserveData.variableBorrowIndex) + ); + + const totalVariableDebtAfterTx = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + + const utilizationRateAfterTx = calcExpectedUtilizationRate( + totalStableDebtAfterTx, + totalVariableDebtAfterTx, + expectedReserveData.availableLiquidity + .plus(totalStableDebtAfterTx) + .plus(totalVariableDebtAfterTx) + ); + + const rates = calcExpectedInterestRates( + reserveDataBeforeAction.symbol, + reserveDataBeforeAction.marketStableRate, + utilizationRateAfterTx, + totalStableDebtAfterTx, + totalVariableDebtAfterTx, + expectedReserveData.averageStableBorrowRate + ); + + expectedReserveData.liquidityRate = rates[0]; + + expectedReserveData.stableBorrowRate = rates[1]; + + expectedReserveData.variableBorrowRate = rates[2]; + + expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( + calcExpectedReserveNormalizedDebt( + expectedReserveData.variableBorrowRate, + expectedReserveData.variableBorrowIndex, + txTimestamp, + currentTimestamp + ) + ); + + expectedReserveData.totalLiquidity = expectedReserveData.availableLiquidity + .plus(expectedReserveData.totalStableDebt) + .plus(expectedReserveData.totalVariableDebt); + + expectedReserveData.utilizationRate = calcExpectedUtilizationRate( + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.totalLiquidity + ); + } + return expectedReserveData; }; @@ -362,83 +479,28 @@ export const calcExpectedReserveDataAfterRepay = ( let amountRepaidBN = new BigNumber(amountRepaid); - const userStableBorrowBalance = calcExpectedStableDebtTokenBalance( - userDataBeforeAction, + const userStableDebt = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, txTimestamp ); - const userVariableBorrowBalance = calcExpectedVariableDebtTokenBalance( + const userVariableDebt = calcExpectedVariableDebtTokenBalance( reserveDataBeforeAction, userDataBeforeAction, txTimestamp ); - //if amount repaid = MAX_UINT_AMOUNT, user is repaying everything + //if amount repaid == MAX_UINT_AMOUNT, user is repaying everything if (amountRepaidBN.abs().eq(MAX_UINT_AMOUNT)) { if (borrowRateMode == RateMode.Stable) { - amountRepaidBN = userStableBorrowBalance; + amountRepaidBN = userStableDebt; } else { - amountRepaidBN = userVariableBorrowBalance; + amountRepaidBN = userVariableDebt; } } - if (borrowRateMode == RateMode.Stable) { - const debtAccrued = userStableBorrowBalance.minus(userDataBeforeAction.principalStableDebt); - - expectedReserveData.totalLiquidity = reserveDataBeforeAction.totalLiquidity.plus(debtAccrued); - - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable - .minus(amountRepaidBN) - .plus(debtAccrued); - - expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( - reserveDataBeforeAction.averageStableBorrowRate, - reserveDataBeforeAction.totalBorrowsStable.plus(debtAccrued), - amountRepaidBN.negated(), - userDataBeforeAction.stableBorrowRate - ); - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable; - } else { - const debtAccrued = userVariableBorrowBalance.minus(userDataBeforeAction.principalVariableDebt); - - expectedReserveData.totalLiquidity = reserveDataBeforeAction.totalLiquidity.plus(debtAccrued); - - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable - .plus(debtAccrued) - .minus(amountRepaidBN); - - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable; - expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; - } - - expectedReserveData.availableLiquidity = reserveDataBeforeAction.availableLiquidity.plus( - amountRepaidBN - ); - - expectedReserveData.availableLiquidity = reserveDataBeforeAction.availableLiquidity.plus( - amountRepaidBN - ); - - expectedReserveData.utilizationRate = calcExpectedUtilizationRate( - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.totalLiquidity - ); - - const rates = calcExpectedInterestRates( - reserveDataBeforeAction.symbol, - reserveDataBeforeAction.marketStableRate, - expectedReserveData.utilizationRate, - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, - expectedReserveData.averageStableBorrowRate - ); - expectedReserveData.liquidityRate = rates[0]; - - expectedReserveData.stableBorrowRate = rates[1]; - - expectedReserveData.variableBorrowRate = rates[2]; - expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( reserveDataBeforeAction, txTimestamp @@ -448,6 +510,82 @@ export const calcExpectedReserveDataAfterRepay = ( txTimestamp ); + if (borrowRateMode == RateMode.Stable) { + const expectedDebt = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + txTimestamp + ); + + expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = expectedDebt.minus( + amountRepaidBN + ); + + //due to accumulation errors, the total stable debt might be smaller than the last user debt. + //in this case we simply set the total supply and avg stable rate to 0. + if (expectedReserveData.principalStableDebt.lt(0)) { + expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = new BigNumber( + 0 + ); + expectedReserveData.averageStableBorrowRate = new BigNumber(0); + } else { + expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( + reserveDataBeforeAction.averageStableBorrowRate, + expectedDebt, + amountRepaidBN.negated(), + userDataBeforeAction.stableBorrowRate + ); + } + + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt; + + expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + } else { + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt.minus( + amountRepaidBN.rayDiv(expectedReserveData.variableBorrowIndex) + ); + + expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + + expectedReserveData.principalStableDebt = reserveDataBeforeAction.principalStableDebt; + expectedReserveData.totalStableDebt = reserveDataBeforeAction.totalStableDebt; + + expectedReserveData.averageStableBorrowRate = reserveDataBeforeAction.averageStableBorrowRate; + } + + expectedReserveData.availableLiquidity = reserveDataBeforeAction.availableLiquidity.plus( + amountRepaidBN + ); + + expectedReserveData.totalLiquidity = expectedReserveData.availableLiquidity + .plus(expectedReserveData.totalStableDebt) + .plus(expectedReserveData.totalVariableDebt); + + expectedReserveData.utilizationRate = calcExpectedUtilizationRate( + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.totalLiquidity + ); + + const rates = calcExpectedInterestRates( + reserveDataBeforeAction.symbol, + reserveDataBeforeAction.marketStableRate, + expectedReserveData.utilizationRate, + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, + expectedReserveData.averageStableBorrowRate + ); + expectedReserveData.liquidityRate = rates[0]; + + expectedReserveData.stableBorrowRate = rates[1]; + + expectedReserveData.variableBorrowRate = rates[2]; + expectedReserveData.lastUpdateTimestamp = txTimestamp; return expectedReserveData; @@ -460,75 +598,66 @@ export const calcExpectedUserDataAfterBorrow = ( expectedDataAfterAction: ReserveData, userDataBeforeAction: UserReserveData, txTimestamp: BigNumber, - currentTimestamp: BigNumber, - txCost: BigNumber + currentTimestamp: BigNumber ): UserReserveData => { const expectedUserData = {}; - const currentStableDebt = calcExpectedStableDebtTokenBalance(userDataBeforeAction, txTimestamp); - - const currentVariableDebt = calcExpectedVariableDebtTokenBalance( - reserveDataBeforeAction, - userDataBeforeAction, - txTimestamp - ); + const amountBorrowedBN = new BigNumber(amountBorrowed); if (interestRateMode == RateMode.Stable) { - const debtAccrued = currentStableDebt.minus(userDataBeforeAction.principalStableDebt); + + const stableDebtUntilTx = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, + txTimestamp + ); - expectedUserData.principalStableDebt = currentStableDebt.plus(amountBorrowed); - expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; + expectedUserData.principalStableDebt = stableDebtUntilTx.plus(amountBorrowed); + expectedUserData.stableRateLastUpdated = txTimestamp; expectedUserData.stableBorrowRate = calcExpectedUserStableRate( - userDataBeforeAction.principalStableDebt.plus(debtAccrued), + stableDebtUntilTx, userDataBeforeAction.stableBorrowRate, - new BigNumber(amountBorrowed), + amountBorrowedBN, reserveDataBeforeAction.stableBorrowRate ); - expectedUserData.stableRateLastUpdated = txTimestamp; + + expectedUserData.currentStableDebt = calcExpectedStableDebtTokenBalance( + expectedUserData.principalStableDebt, + expectedUserData.stableBorrowRate, + txTimestamp, + currentTimestamp + ); + + expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt; + } else { - expectedUserData.principalVariableDebt = currentVariableDebt.plus(amountBorrowed); + + expectedUserData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt.plus( + amountBorrowedBN.rayDiv(expectedDataAfterAction.variableBorrowIndex) + ); + expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate; expectedUserData.stableRateLastUpdated = userDataBeforeAction.stableRateLastUpdated; - } - expectedUserData.currentStableDebt = calcExpectedStableDebtTokenBalance( - { - ...userDataBeforeAction, - currentStableDebt: expectedUserData.principalStableDebt, - principalStableDebt: expectedUserData.principalStableDebt, - stableBorrowRate: expectedUserData.stableBorrowRate, - stableRateLastUpdated: expectedUserData.stableRateLastUpdated, - }, - currentTimestamp - ); + expectedUserData.currentStableDebt = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, + currentTimestamp + ); + } expectedUserData.currentVariableDebt = calcExpectedVariableDebtTokenBalance( expectedDataAfterAction, - { - ...userDataBeforeAction, - currentVariableDebt: expectedUserData.principalVariableDebt, - principalVariableDebt: expectedUserData.principalVariableDebt, - variableBorrowIndex: - interestRateMode == RateMode.Variable - ? expectedDataAfterAction.variableBorrowIndex - : userDataBeforeAction.variableBorrowIndex, - }, + expectedUserData, currentTimestamp ); - if (expectedUserData.principalVariableDebt.eq(0)) { - expectedUserData.variableBorrowIndex = new BigNumber(0); - } else { - expectedUserData.variableBorrowIndex = - interestRateMode == RateMode.Variable - ? expectedDataAfterAction.variableBorrowIndex - : userDataBeforeAction.variableBorrowIndex; - } - expectedUserData.liquidityRate = expectedDataAfterAction.liquidityRate; expectedUserData.usageAsCollateralEnabled = userDataBeforeAction.usageAsCollateralEnabled; @@ -538,6 +667,7 @@ export const calcExpectedUserDataAfterBorrow = ( userDataBeforeAction, currentTimestamp ); + expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed); @@ -554,36 +684,34 @@ export const calcExpectedUserDataAfterRepay = ( user: string, onBehalfOf: string, txTimestamp: BigNumber, - currentTimestamp: BigNumber, - txCost: BigNumber + currentTimestamp: BigNumber ): UserReserveData => { const expectedUserData = {}; - const variableBorrowBalance = calcExpectedVariableDebtTokenBalance( + const variableDebt = calcExpectedVariableDebtTokenBalance( reserveDataBeforeAction, userDataBeforeAction, currentTimestamp ); - const stableBorrowBalance = calcExpectedStableDebtTokenBalance( - userDataBeforeAction, + const stableDebt = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, currentTimestamp ); - if (new BigNumber(totalRepaid).abs().eq(MAX_UINT_AMOUNT)) { - totalRepaid = - rateMode == RateMode.Stable - ? stableBorrowBalance.toFixed(0) - : variableBorrowBalance.toFixed(); + let totalRepaidBN = new BigNumber(totalRepaid); + if (totalRepaidBN.abs().eq(MAX_UINT_AMOUNT)) { + totalRepaidBN = rateMode == RateMode.Stable ? stableDebt : variableDebt; } if (rateMode == RateMode.Stable) { - expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; - expectedUserData.currentVariableDebt = variableBorrowBalance; - expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; + expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt; + expectedUserData.currentVariableDebt = variableDebt; - expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = stableBorrowBalance.minus( - totalRepaid + expectedUserData.principalStableDebt = expectedUserData.currentStableDebt = stableDebt.minus( + totalRepaidBN ); if (expectedUserData.currentStableDebt.eq('0')) { @@ -596,21 +724,17 @@ export const calcExpectedUserDataAfterRepay = ( expectedUserData.stableRateLastUpdated = txTimestamp; } } else { - expectedUserData.currentStableDebt = stableBorrowBalance; - expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; + expectedUserData.currentStableDebt = userDataBeforeAction.principalStableDebt; + expectedUserData.principalStableDebt = stableDebt; expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate; expectedUserData.stableRateLastUpdated = userDataBeforeAction.stableRateLastUpdated; - expectedUserData.currentVariableDebt = expectedUserData.principalVariableDebt = variableBorrowBalance.minus( - totalRepaid + expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt.minus( + totalRepaidBN.rayDiv(expectedDataAfterAction.variableBorrowIndex) + ); + expectedUserData.currentVariableDebt = expectedUserData.scaledVariableDebt.rayMul( + expectedDataAfterAction.variableBorrowIndex ); - - if (expectedUserData.currentVariableDebt.eq('0')) { - //user repaid everything - expectedUserData.variableBorrowIndex = new BigNumber('0'); - } else { - expectedUserData.variableBorrowIndex = expectedDataAfterAction.variableBorrowIndex; - } } expectedUserData.liquidityRate = expectedDataAfterAction.liquidityRate; @@ -625,7 +749,7 @@ export const calcExpectedUserDataAfterRepay = ( expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; if (user === onBehalfOf) { - expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(totalRepaid); + expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(totalRepaidBN); } else { //wallet balance didn't change expectedUserData.walletBalance = userDataBeforeAction.walletBalance; @@ -657,58 +781,88 @@ export const calcExpectedReserveDataAfterSwapRateMode = ( expectedReserveData.address = reserveDataBeforeAction.address; - const variableBorrowBalance = calcExpectedVariableDebtTokenBalance( + const variableDebt = calcExpectedVariableDebtTokenBalance( reserveDataBeforeAction, userDataBeforeAction, txTimestamp ); - const stableBorrowBalance = calcExpectedStableDebtTokenBalance(userDataBeforeAction, txTimestamp); + const stableDebt = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, + txTimestamp + ); + + expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( + reserveDataBeforeAction, + txTimestamp + ); + + expectedReserveData.variableBorrowIndex = calcExpectedVariableBorrowIndex( + reserveDataBeforeAction, + txTimestamp + ); expectedReserveData.availableLiquidity = reserveDataBeforeAction.availableLiquidity; + const totalStableDebtUntilTx = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + txTimestamp + ); + if (rateMode === RateMode.Stable) { //swap user stable debt to variable - const debtAccrued = stableBorrowBalance.minus(userDataBeforeAction.principalStableDebt); + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt.plus( + stableDebt.rayDiv(expectedReserveData.variableBorrowIndex) + ); - expectedReserveData.totalLiquidity = reserveDataBeforeAction.totalLiquidity.plus(debtAccrued); + expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + + expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = totalStableDebtUntilTx.minus( + stableDebt + ); expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( reserveDataBeforeAction.averageStableBorrowRate, - reserveDataBeforeAction.totalBorrowsStable.plus(debtAccrued), - stableBorrowBalance.negated(), + expectedReserveData.principalStableDebt.plus(stableDebt), + stableDebt.negated(), userDataBeforeAction.stableBorrowRate ); - - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable.plus( - stableBorrowBalance - ); - - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable.minus( - userDataBeforeAction.principalStableDebt - ); } else { - const debtAccrued = variableBorrowBalance.minus(userDataBeforeAction.principalVariableDebt); + //swap variable to stable - expectedReserveData.totalLiquidity = reserveDataBeforeAction.totalLiquidity.plus(debtAccrued); - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable.minus( - userDataBeforeAction.principalVariableDebt + expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = totalStableDebtUntilTx.plus( + variableDebt ); - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable.plus( - variableBorrowBalance + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt.minus( + variableDebt.rayDiv(expectedReserveData.variableBorrowIndex) ); + + expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( reserveDataBeforeAction.averageStableBorrowRate, - reserveDataBeforeAction.totalBorrowsStable, - variableBorrowBalance, + reserveDataBeforeAction.totalStableDebt, + variableDebt, reserveDataBeforeAction.stableBorrowRate ); } + expectedReserveData.totalLiquidity = reserveDataBeforeAction.availableLiquidity + .plus(expectedReserveData.totalStableDebt) + .plus(expectedReserveData.totalVariableDebt); + expectedReserveData.utilizationRate = calcExpectedUtilizationRate( - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, expectedReserveData.totalLiquidity ); @@ -716,8 +870,8 @@ export const calcExpectedReserveDataAfterSwapRateMode = ( reserveDataBeforeAction.symbol, reserveDataBeforeAction.marketStableRate, expectedReserveData.utilizationRate, - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, expectedReserveData.averageStableBorrowRate ); expectedReserveData.liquidityRate = rates[0]; @@ -726,15 +880,6 @@ export const calcExpectedReserveDataAfterSwapRateMode = ( expectedReserveData.variableBorrowRate = rates[2]; - expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( - reserveDataBeforeAction, - txTimestamp - ); - expectedReserveData.variableBorrowIndex = calcExpectedVariableBorrowIndex( - reserveDataBeforeAction, - txTimestamp - ); - return expectedReserveData; }; @@ -748,14 +893,19 @@ export const calcExpectedUserDataAfterSwapRateMode = ( ): UserReserveData => { const expectedUserData = {...userDataBeforeAction}; - const variableBorrowBalance = calcExpectedVariableDebtTokenBalance( + const stableDebtBalance = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, + txTimestamp + ); + + const variableDebtBalance = calcExpectedVariableDebtTokenBalance( reserveDataBeforeAction, userDataBeforeAction, txTimestamp ); - const stableBorrowBalance = calcExpectedStableDebtTokenBalance(userDataBeforeAction, txTimestamp); - expectedUserData.currentATokenBalance = calcExpectedATokenBalance( reserveDataBeforeAction, userDataBeforeAction, @@ -768,31 +918,30 @@ export const calcExpectedUserDataAfterSwapRateMode = ( expectedUserData.stableBorrowRate = new BigNumber(0); - expectedUserData.principalVariableDebt = expectedUserData.currentVariableDebt = userDataBeforeAction.currentVariableDebt.plus( - stableBorrowBalance + expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt.plus( + stableDebtBalance.rayDiv(expectedDataAfterAction.variableBorrowIndex) ); - expectedUserData.variableBorrowIndex = expectedDataAfterAction.variableBorrowIndex; + expectedUserData.currentVariableDebt = expectedUserData.scaledVariableDebt.rayMul( + expectedDataAfterAction.variableBorrowIndex + ); + expectedUserData.stableRateLastUpdated = new BigNumber(0); } else { expectedUserData.principalStableDebt = expectedUserData.currentStableDebt = userDataBeforeAction.currentStableDebt.plus( - variableBorrowBalance + variableDebtBalance ); //weighted average of the previous and the current expectedUserData.stableBorrowRate = calcExpectedUserStableRate( - userDataBeforeAction.principalStableDebt, + stableDebtBalance, userDataBeforeAction.stableBorrowRate, - variableBorrowBalance, + variableDebtBalance, reserveDataBeforeAction.stableBorrowRate ); expectedUserData.stableRateLastUpdated = txTimestamp; - expectedUserData.currentVariableDebt = expectedUserData.principalVariableDebt = new BigNumber( - 0 - ); - - expectedUserData.variableBorrowIndex = new BigNumber(0); + expectedUserData.currentVariableDebt = expectedUserData.scaledVariableDebt = new BigNumber(0); } expectedUserData.liquidityRate = expectedDataAfterAction.liquidityRate; @@ -809,39 +958,61 @@ export const calcExpectedReserveDataAfterStableRateRebalance = ( expectedReserveData.address = reserveDataBeforeAction.address; - const stableBorrowBalance = calcExpectedStableDebtTokenBalance(userDataBeforeAction, txTimestamp); + const userStableDebt = calcExpectedStableDebtTokenBalance( + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, + txTimestamp + ); - const debtAccrued = stableBorrowBalance.minus(userDataBeforeAction.principalStableDebt); + expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( + reserveDataBeforeAction, + txTimestamp + ); - expectedReserveData.totalLiquidity = reserveDataBeforeAction.totalLiquidity.plus(debtAccrued); + expectedReserveData.variableBorrowIndex = calcExpectedVariableBorrowIndex( + reserveDataBeforeAction, + txTimestamp + ); + + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt; + expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( + expectedReserveData.variableBorrowIndex + ); + + expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.totalStableDebtLastUpdated, + txTimestamp + ); expectedReserveData.availableLiquidity = reserveDataBeforeAction.availableLiquidity; + expectedReserveData.totalLiquidity = expectedReserveData.availableLiquidity + .plus(expectedReserveData.totalStableDebt) + .plus(expectedReserveData.totalVariableDebt); + //removing the stable liquidity at the old rate const avgRateBefore = calcExpectedAverageStableBorrowRate( reserveDataBeforeAction.averageStableBorrowRate, - reserveDataBeforeAction.totalBorrowsStable.plus(debtAccrued), - stableBorrowBalance.negated(), + expectedReserveData.totalStableDebt, + userStableDebt.negated(), userDataBeforeAction.stableBorrowRate ); // adding it again at the new rate expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( avgRateBefore, - reserveDataBeforeAction.totalBorrowsStable.minus(userDataBeforeAction.principalStableDebt), - stableBorrowBalance, + expectedReserveData.totalStableDebt.minus(userStableDebt), + userStableDebt, reserveDataBeforeAction.stableBorrowRate ); - expectedReserveData.totalBorrowsVariable = reserveDataBeforeAction.totalBorrowsVariable; - expectedReserveData.totalBorrowsStable = reserveDataBeforeAction.totalBorrowsStable.plus( - debtAccrued - ); - expectedReserveData.utilizationRate = calcExpectedUtilizationRate( - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, expectedReserveData.totalLiquidity ); @@ -849,8 +1020,8 @@ export const calcExpectedReserveDataAfterStableRateRebalance = ( reserveDataBeforeAction.symbol, reserveDataBeforeAction.marketStableRate, expectedReserveData.utilizationRate, - expectedReserveData.totalBorrowsStable, - expectedReserveData.totalBorrowsVariable, + expectedReserveData.totalStableDebt, + expectedReserveData.totalVariableDebt, expectedReserveData.averageStableBorrowRate ); @@ -860,15 +1031,6 @@ export const calcExpectedReserveDataAfterStableRateRebalance = ( expectedReserveData.variableBorrowRate = rates[2]; - expectedReserveData.liquidityIndex = calcExpectedLiquidityIndex( - reserveDataBeforeAction, - txTimestamp - ); - expectedReserveData.variableBorrowIndex = calcExpectedVariableBorrowIndex( - reserveDataBeforeAction, - txTimestamp - ); - return expectedReserveData; }; @@ -887,7 +1049,9 @@ export const calcExpectedUserDataAfterStableRateRebalance = ( txTimestamp ); expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( - userDataBeforeAction, + userDataBeforeAction.principalStableDebt, + userDataBeforeAction.stableBorrowRate, + userDataBeforeAction.stableRateLastUpdated, txTimestamp ); @@ -920,26 +1084,26 @@ const calcExpectedScaledATokenBalance = ( }; export const calcExpectedATokenBalance = ( - reserveDataBeforeAction: ReserveData, - userDataBeforeAction: UserReserveData, + reserveData: ReserveData, + userData: UserReserveData, currentTimestamp: BigNumber ) => { - const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp); + const index = calcExpectedReserveNormalizedIncome(reserveData, currentTimestamp); - const {scaledATokenBalance: scaledBalanceBeforeAction} = userDataBeforeAction; + const {scaledATokenBalance: scaledBalanceBeforeAction} = userData; return scaledBalanceBeforeAction.rayMul(index); }; const calcExpectedAverageStableBorrowRate = ( avgStableRateBefore: BigNumber, - totalBorrowsStableBefore: BigNumber, + totalStableDebtBefore: BigNumber, amountChanged: string | BigNumber, rate: BigNumber ) => { - const weightedTotalBorrows = avgStableRateBefore.multipliedBy(totalBorrowsStableBefore); + const weightedTotalBorrows = avgStableRateBefore.multipliedBy(totalStableDebtBefore); const weightedAmountBorrowed = rate.multipliedBy(amountChanged); - const totalBorrowedStable = totalBorrowsStableBefore.plus(new BigNumber(amountChanged)); + const totalBorrowedStable = totalStableDebtBefore.plus(new BigNumber(amountChanged)); if (totalBorrowedStable.eq(0)) return new BigNumber('0'); @@ -949,39 +1113,29 @@ const calcExpectedAverageStableBorrowRate = ( .decimalPlaces(0, BigNumber.ROUND_DOWN); }; -const calcExpectedVariableDebtUserIndex = ( - reserveDataBeforeAction: ReserveData, - expectedUserBalanceAfterAction: BigNumber, - currentTimestamp: BigNumber -) => { - if (expectedUserBalanceAfterAction.eq(0)) { - return new BigNumber(0); - } - return calcExpectedReserveNormalizedDebt(reserveDataBeforeAction, currentTimestamp); -}; - export const calcExpectedVariableDebtTokenBalance = ( - reserveDataBeforeAction: ReserveData, - userDataBeforeAction: UserReserveData, + reserveData: ReserveData, + userData: UserReserveData, currentTimestamp: BigNumber ) => { - const debt = calcExpectedReserveNormalizedDebt(reserveDataBeforeAction, currentTimestamp); + const normalizedDebt = calcExpectedReserveNormalizedDebt( + reserveData.variableBorrowRate, + reserveData.variableBorrowIndex, + reserveData.lastUpdateTimestamp, + currentTimestamp + ); - const {principalVariableDebt, variableBorrowIndex} = userDataBeforeAction; + const {scaledVariableDebt} = userData; - if (variableBorrowIndex.eq(0)) { - return principalVariableDebt; - } - - return principalVariableDebt.wadToRay().rayMul(debt).rayDiv(variableBorrowIndex).rayToWad(); + return scaledVariableDebt.rayMul(normalizedDebt); }; export const calcExpectedStableDebtTokenBalance = ( - userDataBeforeAction: UserReserveData, + principalStableDebt: BigNumber, + stableBorrowRate: BigNumber, + stableRateLastUpdated: BigNumber, currentTimestamp: BigNumber ) => { - const {principalStableDebt, stableBorrowRate, stableRateLastUpdated} = userDataBeforeAction; - if ( stableBorrowRate.eq(0) || currentTimestamp.eq(stableRateLastUpdated) || @@ -996,7 +1150,7 @@ export const calcExpectedStableDebtTokenBalance = ( stableRateLastUpdated ); - return principalStableDebt.wadToRay().rayMul(cumulatedInterest).rayToWad(); + return principalStableDebt.rayMul(cumulatedInterest); }; const calcLinearInterest = ( @@ -1049,8 +1203,8 @@ const calcExpectedInterestRates = ( reserveSymbol: string, marketStableRate: BigNumber, utilizationRate: BigNumber, - totalBorrowsStable: BigNumber, - totalBorrowsVariable: BigNumber, + totalStableDebt: BigNumber, + totalVariableDebt: BigNumber, averageStableBorrowRate: BigNumber ): BigNumber[] => { const {reservesParams} = configuration; @@ -1094,8 +1248,8 @@ const calcExpectedInterestRates = ( } const expectedOverallRate = calcExpectedOverallBorrowRate( - totalBorrowsStable, - totalBorrowsVariable, + totalStableDebt, + totalVariableDebt, variableBorrowRate, averageStableBorrowRate ); @@ -1105,18 +1259,18 @@ const calcExpectedInterestRates = ( }; const calcExpectedOverallBorrowRate = ( - totalBorrowsStable: BigNumber, - totalBorrowsVariable: BigNumber, + totalStableDebt: BigNumber, + totalVariableDebt: BigNumber, currentVariableBorrowRate: BigNumber, currentAverageStableBorrowRate: BigNumber ): BigNumber => { - const totalBorrows = totalBorrowsStable.plus(totalBorrowsVariable); + const totalBorrows = totalStableDebt.plus(totalVariableDebt); if (totalBorrows.eq(0)) return strToBN('0'); - const weightedVariableRate = totalBorrowsVariable.wadToRay().rayMul(currentVariableBorrowRate); + const weightedVariableRate = totalVariableDebt.wadToRay().rayMul(currentVariableBorrowRate); - const weightedStableRate = totalBorrowsStable.wadToRay().rayMul(currentAverageStableBorrowRate); + const weightedStableRate = totalStableDebt.wadToRay().rayMul(currentAverageStableBorrowRate); const overallBorrowRate = weightedVariableRate .plus(weightedStableRate) @@ -1126,15 +1280,15 @@ const calcExpectedOverallBorrowRate = ( }; const calcExpectedUtilizationRate = ( - totalBorrowsStable: BigNumber, - totalBorrowsVariable: BigNumber, + totalStableDebt: BigNumber, + totalVariableDebt: BigNumber, totalLiquidity: BigNumber ): BigNumber => { - if (totalBorrowsStable.eq('0') && totalBorrowsVariable.eq('0')) { + if (totalStableDebt.eq('0') && totalVariableDebt.eq('0')) { return strToBN('0'); } - const utilization = totalBorrowsStable.plus(totalBorrowsVariable).rayDiv(totalLiquidity); + const utilization = totalStableDebt.plus(totalVariableDebt).rayDiv(totalLiquidity); return utilization; }; @@ -1162,11 +1316,11 @@ const calcExpectedReserveNormalizedIncome = ( }; const calcExpectedReserveNormalizedDebt = ( - reserveData: ReserveData, + variableBorrowRate: BigNumber, + variableBorrowIndex: BigNumber, + lastUpdateTimestamp: BigNumber, currentTimestamp: BigNumber ) => { - const {variableBorrowRate, variableBorrowIndex, lastUpdateTimestamp} = reserveData; - //if utilization rate is 0, nothing to compound if (variableBorrowRate.eq('0')) { return variableBorrowIndex; @@ -1211,8 +1365,8 @@ const calcExpectedLiquidityIndex = (reserveData: ReserveData, timestamp: BigNumb }; const calcExpectedVariableBorrowIndex = (reserveData: ReserveData, timestamp: BigNumber) => { - //if totalBorrowsVariable is 0, nothing to compound - if (reserveData.totalBorrowsVariable.eq('0')) { + //if totalVariableDebt is 0, nothing to compound + if (reserveData.totalVariableDebt.eq('0')) { return reserveData.variableBorrowIndex; } @@ -1224,3 +1378,25 @@ const calcExpectedVariableBorrowIndex = (reserveData: ReserveData, timestamp: Bi return cumulatedInterest.rayMul(reserveData.variableBorrowIndex); }; + +const calcExpectedTotalStableDebt = ( + principalStableDebt: BigNumber, + averageStableBorrowRate: BigNumber, + lastUpdateTimestamp: BigNumber, + currentTimestamp: BigNumber +) => { + const cumulatedInterest = calcCompoundedInterest( + averageStableBorrowRate, + currentTimestamp, + lastUpdateTimestamp + ); + + return cumulatedInterest.rayMul(principalStableDebt); +}; + +const calcExpectedTotalVariableDebt = ( + reserveData: ReserveData, + expectedVariableDebtIndex: BigNumber +) => { + return reserveData.scaledVariableDebt.rayMul(expectedVariableDebtIndex); +}; diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index 7d3ccee5..7c3dbd2a 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -4,9 +4,8 @@ import { getLendingRateOracle, getIErc20Detailed, getMintableErc20, - getAToken, + getAToken, getStableDebtToken, getVariableDebtToken } from '../../../helpers/contracts-helpers'; -import {ZERO_ADDRESS} from '../../../helpers/constants'; import {tEthereumAddress} from '../../../helpers/types'; import BigNumber from 'bignumber.js'; import {getDb, BRE} from '../../../helpers/misc-utils'; @@ -15,41 +14,54 @@ export const getReserveData = async ( pool: LendingPool, reserve: tEthereumAddress ): Promise => { - const data = await pool.getReserveData(reserve); - const tokenAddresses = await pool.getReserveTokensAddresses(reserve); - const rateOracle = await getLendingRateOracle(); + const [reserveData, tokenAddresses, rateOracle, token] = await Promise.all([ + pool.getReserveData(reserve), + pool.getReserveTokensAddresses(reserve), + getLendingRateOracle(), + getIErc20Detailed(reserve), + ]); + + const stableDebtToken = await getStableDebtToken(tokenAddresses.stableDebtTokenAddress); + const variableDebtToken = await getVariableDebtToken(tokenAddresses.variableDebtTokenAddress); + + const [principalStableDebt] = await stableDebtToken.getSupplyData(); + const totalStableDebtLastUpdated = await stableDebtToken.getTotalSupplyLastUpdated(); + + + const scaledVariableDebt = await variableDebtToken.scaledTotalSupply(); const rate = (await rateOracle.getMarketBorrowRate(reserve)).toString(); - - const token = await getIErc20Detailed(reserve); const symbol = await token.symbol(); const decimals = new BigNumber(await token.decimals()); - const totalLiquidity = new BigNumber(data.availableLiquidity.toString()) - .plus(data.totalBorrowsStable.toString()) - .plus(data.totalBorrowsVariable.toString()); + const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString()) + .plus(reserveData.totalStableDebt.toString()) + .plus(reserveData.totalVariableDebt.toString()); const utilizationRate = new BigNumber( totalLiquidity.eq(0) ? 0 - : new BigNumber(data.totalBorrowsStable.toString()) - .plus(data.totalBorrowsVariable.toString()) + : new BigNumber(reserveData.totalStableDebt.toString()) + .plus(reserveData.totalVariableDebt.toString()) .rayDiv(totalLiquidity) ); return { totalLiquidity, utilizationRate, - availableLiquidity: new BigNumber(data.availableLiquidity.toString()), - totalBorrowsStable: new BigNumber(data.totalBorrowsStable.toString()), - totalBorrowsVariable: new BigNumber(data.totalBorrowsVariable.toString()), - liquidityRate: new BigNumber(data.liquidityRate.toString()), - variableBorrowRate: new BigNumber(data.variableBorrowRate.toString()), - stableBorrowRate: new BigNumber(data.stableBorrowRate.toString()), - averageStableBorrowRate: new BigNumber(data.averageStableBorrowRate.toString()), - liquidityIndex: new BigNumber(data.liquidityIndex.toString()), - variableBorrowIndex: new BigNumber(data.variableBorrowIndex.toString()), - lastUpdateTimestamp: new BigNumber(data.lastUpdateTimestamp), + availableLiquidity: new BigNumber(reserveData.availableLiquidity.toString()), + totalStableDebt: new BigNumber(reserveData.totalStableDebt.toString()), + totalVariableDebt: new BigNumber(reserveData.totalVariableDebt.toString()), + liquidityRate: new BigNumber(reserveData.liquidityRate.toString()), + variableBorrowRate: new BigNumber(reserveData.variableBorrowRate.toString()), + stableBorrowRate: new BigNumber(reserveData.stableBorrowRate.toString()), + averageStableBorrowRate: new BigNumber(reserveData.averageStableBorrowRate.toString()), + liquidityIndex: new BigNumber(reserveData.liquidityIndex.toString()), + variableBorrowIndex: new BigNumber(reserveData.variableBorrowIndex.toString()), + lastUpdateTimestamp: new BigNumber(reserveData.lastUpdateTimestamp), + totalStableDebtLastUpdated: new BigNumber(totalStableDebtLastUpdated), + principalStableDebt: new BigNumber(principalStableDebt.toString()), + scaledVariableDebt: new BigNumber(scaledVariableDebt.toString()), address: reserve, aTokenAddress: tokenAddresses.aTokenAddress, symbol, @@ -69,7 +81,6 @@ export const getUserData = async ( getATokenUserData(reserve, user, pool), ]); - const token = await getMintableErc20(reserve); const walletBalance = new BigNumber((await token.balanceOf(sender || user)).toString()); @@ -79,8 +90,7 @@ export const getUserData = async ( currentStableDebt: new BigNumber(userData.currentStableDebt.toString()), currentVariableDebt: new BigNumber(userData.currentVariableDebt.toString()), principalStableDebt: new BigNumber(userData.principalStableDebt.toString()), - principalVariableDebt: new BigNumber(userData.principalVariableDebt.toString()), - variableBorrowIndex: new BigNumber(userData.variableBorrowIndex.toString()), + scaledVariableDebt: new BigNumber(userData.scaledVariableDebt.toString()), stableBorrowRate: new BigNumber(userData.stableBorrowRate.toString()), liquidityRate: new BigNumber(userData.liquidityRate.toString()), usageAsCollateralEnabled: userData.usageAsCollateralEnabled, @@ -107,5 +117,4 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo const scaledBalance = await aToken.scaledBalanceOf(user); return scaledBalance.toString(); - }; diff --git a/test/helpers/utils/interfaces/index.ts b/test/helpers/utils/interfaces/index.ts index 7042bbea..72b30708 100644 --- a/test/helpers/utils/interfaces/index.ts +++ b/test/helpers/utils/interfaces/index.ts @@ -6,8 +6,7 @@ export interface UserReserveData { currentStableDebt: BigNumber; currentVariableDebt: BigNumber; principalStableDebt: BigNumber; - principalVariableDebt: BigNumber; - variableBorrowIndex: BigNumber; + scaledVariableDebt: BigNumber; liquidityRate: BigNumber; stableBorrowRate: BigNumber; stableRateLastUpdated: BigNumber; @@ -22,8 +21,10 @@ export interface ReserveData { decimals: BigNumber; totalLiquidity: BigNumber; availableLiquidity: BigNumber; - totalBorrowsStable: BigNumber; - totalBorrowsVariable: BigNumber; + totalStableDebt: BigNumber; + totalVariableDebt: BigNumber; + principalStableDebt: BigNumber, + scaledVariableDebt: BigNumber, averageStableBorrowRate: BigNumber; variableBorrowRate: BigNumber; stableBorrowRate: BigNumber; @@ -33,6 +34,7 @@ export interface ReserveData { aTokenAddress: string; marketStableRate: BigNumber; lastUpdateTimestamp: BigNumber; + totalStableDebtLastUpdated: BigNumber; liquidityRate: BigNumber; [key: string]: BigNumber | string; } diff --git a/test/liquidation-underlying.spec.ts b/test/liquidation-underlying.spec.ts index 26f10ced..df1e9404 100644 --- a/test/liquidation-underlying.spec.ts +++ b/test/liquidation-underlying.spec.ts @@ -175,7 +175,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', ); const stableDebtBeforeTx = calcExpectedStableDebtTokenBalance( - userReserveDataBefore, + userReserveDataBefore.principalStableDebt, + userReserveDataBefore.stableBorrowRate, + userReserveDataBefore.stableRateLastUpdated, txTimestamp ); diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts index 41e9060f..7fe7d714 100644 --- a/test/repay-with-collateral.spec.ts +++ b/test/repay-with-collateral.spec.ts @@ -420,7 +420,9 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { ).minus(usdcUserDataBefore.currentVariableDebt); const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance( - usdcUserDataBefore, + usdcUserDataBefore.principalStableDebt, + usdcUserDataBefore.stableBorrowRate, + usdcUserDataBefore.stableRateLastUpdated, new BigNumber(repayWithCollateralTimestamp) ).minus(usdcUserDataBefore.currentStableDebt); @@ -556,14 +558,14 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { new BigNumber(repayWithCollateralTimestamp) ).minus(daiUserDataBefore.currentVariableDebt); - expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( new BigNumber(daiUserDataBefore.currentVariableDebt) .minus(expectedDebtCovered.toString()) .plus(expectedVariableDebtIncrease), 'INVALID_VARIABLE_DEBT_POSITION' ); - expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0); + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.almostEqual(0); expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false; }); diff --git a/test/upgradeability.spec.ts b/test/upgradeability.spec.ts index 47c452bf..29f2da40 100644 --- a/test/upgradeability.spec.ts +++ b/test/upgradeability.spec.ts @@ -23,9 +23,10 @@ makeSuite('Upgradeability', (testEnv: TestEnv) => { const aTokenInstance = await deployContract(eContractid.MockAToken, [ pool.address, dai.address, + ZERO_ADDRESS, 'Aave Interest bearing DAI updated', 'aDAI', - ZERO_ADDRESS, + ZERO_ADDRESS ]); const stableDebtTokenInstance = await deployContract( diff --git a/test/variable-debt-token.spec.ts b/test/variable-debt-token.spec.ts index 89bb1acc..a79bdde2 100644 --- a/test/variable-debt-token.spec.ts +++ b/test/variable-debt-token.spec.ts @@ -18,7 +18,7 @@ makeSuite('Variable debt token tests', (testEnv: TestEnv) => { daiVariableDebtTokenAddress ); - await expect(variableDebtContract.mint(deployer.address, '1')).to.be.revertedWith( + await expect(variableDebtContract.mint(deployer.address, '1', '1')).to.be.revertedWith( CALLER_MUST_BE_LENDING_POOL ); }); @@ -34,7 +34,7 @@ makeSuite('Variable debt token tests', (testEnv: TestEnv) => { daiVariableDebtTokenAddress ); - await expect(variableDebtContract.burn(deployer.address, '1')).to.be.revertedWith( + await expect(variableDebtContract.burn(deployer.address, '1', '1')).to.be.revertedWith( CALLER_MUST_BE_LENDING_POOL ); });