From e4485f12fe5df1a3d271fdd18fa50056903465a6 Mon Sep 17 00:00:00 2001 From: eboado Date: Wed, 26 Aug 2020 16:02:22 +0200 Subject: [PATCH 01/31] - Refactored logic of repay() to an internal _executeRepay(). - Initial implementation of flashCollateral() for flash liquidations, repayment with collateral and movement of position. --- contracts/lendingpool/LendingPool.sol | 123 +++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index ec587920..b1b20a5a 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -238,6 +238,16 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { uint256 rateMode, address onBehalfOf ) external override nonReentrant { + _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); @@ -278,9 +288,9 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { _usersConfig[onBehalfOf].setBorrowing(reserve.index, false); } - IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount); + IERC20(asset).safeTransferFrom(user, aToken, paybackAmount); - emit Repay(asset, onBehalfOf, msg.sender, paybackAmount); + emit Repay(asset, onBehalfOf, user, paybackAmount); } /** @@ -509,6 +519,115 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { emit FlashLoan(receiverAddress, asset, amount, amountFee); } + /** + * @dev flashes collateral, by both a flash liquidator or the user owning it. + * @param collateralAsset The address of the collateral asset. + * @param debtAsset The address of the debt asset. + * @param collateralAmount Collateral amount to flash. + * @param user Address of the user owning the collateral. + * @param receiverAddress Address of the contract receiving the collateral. + * @param debtMode Numeric variable, managing how to operate with the debt side. + * 1 -> With final repayment, to do it on the stable debt. + * 2 -> With final repayment, to do it on the variable debt. + * 3 -> On movement of the debt to the liquidator, to move the stable debt + * 4 -> On movement of the debt to the liquidator, to move the variable debt + * @param receiveAToken "true" to send aToken to the receiver contract, "false" to send underlying tokens. + * @param referralCode Integrators are assigned a referral code and can potentially receive rewards. + **/ + function flashCollateral( + address collateralAsset, + address debtAsset, + uint256 collateralAmount, + address user, + address receiverAddress, + uint256 debtMode, + bool receiveAToken, + uint16 referralCode + ) external override { + require(debtMode > 0, 'INVALID_DEBT_FLAG'); + + ReserveLogic.ReserveData storage collateralReserve = _reserves[collateralAsset]; + ReserveLogic.ReserveData storage debtReserve = _reserves[debtAsset]; + + address collateralAToken = collateralReserve.aTokenAddress; + uint256 availableCollateral = IERC20(collateralAToken).balanceOf(user); + + require(collateralAmount <= availableCollateral, 'NOT_ENOUGH_BALANCE'); + + address oracle = addressesProvider.getPriceOracle(); + (, , , , healthFactor) = GenericLogic.calculateUserAccountData( + user, + _reserves, + _usersConfig[user], + _reservesList, + oracle + ); + + if (healthFactor > GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD && msg.sender != user) { + revert('INVALID_FLASH_COLLATERAL_BY_NON_OWNER'); + } + + uint256 amountToFlash = (msg.sender == user || healthFactor < 0.98 ether) // TODO: better constant + ? collateralAmount + : collateralAmount.div(2); // TODO: better constant + + // If liquidator reclaims the aToken, he receives the equivalent atoken amount, + // otherwise receives the underlying asset + if (receiveAToken) { + IAToken(collateralAToken).transferOnLiquidation(user, receiverAddress, amountToFlash); + } else { + collateralReserve.updateCumulativeIndexesAndTimestamp(); + collateralReserve.updateInterestRates(collateral, aTokenAddress, 0, collateralAmount); + + // Burn of aToken and send the underlying to the receiver + IAToken(aTokenAddress).burn(user, receiver, collateralAmount); + } + + // Notifies the receiver to proceed, sending the underlying or the aToken amount already transferred + IFlashLoanReceiver(receiverAddress).executeOperation( + collateralAsset, + aTokenAddress, + (!receiveAToken) ? collateralAmount : 0, + receiverAToken ? aTokenAmount : 0, + params + ); + + // Calculation of the minimum amount of the debt asset to be received + uint256 debtAmountNeeded = oracle + .getAssetPrice(collateralAsset) + .mul(collateralAmount) + .mul(10**debtReserve.configuration.getDecimals()) + .div(oracle.getAssetPrice(debtAsset).mul(10**collateralReserve.configuration.getDecimals())) + .percentDiv(collateralReserve.configuration.getLiquidationBonus()); + + // Or the debt is transferred to the msg.sender, or funds are transferred from the receiver to repay the debt + if (debtMode > 2) { + (uint256 userStableDebt, uint256 userVariableDebt) = Helpers.getUserCurrentDebt( + user, + debtReserve + ); + + uint256 debtToTransfer; + if (debtMode.div(3) == 1) { + // stable + debtToTransfer = (userStableDebt > debtAmountNeeded) ? debtAmountNeeded : userStableDebt; + IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(user, debtToTransfer); + } else if (debtMode.div(3) == 2) { + // variable + debtToTransfer = (userVariableDebt > debtAmountNeeded) + ? debtAmountNeeded + : userVariableDebt; + IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(user, debtToTransfer); + } + _executeBorrow( + BorrowLocalVars(debtAsset, msg.sender, debtToTransfer, debt, false, referralCode) + ); + } else { + IERC20(debtAsset).transferFrom(receiverAddress, address(this), debtAmountNeeded); + _executeRepay(asset, msg.sender, amount, debtMode, user); + } + } + /** * @dev accessory functions to fetch data from the core contract **/ From 2cbb1f57148004eedbd75345f621307b18e00832 Mon Sep 17 00:00:00 2001 From: eboado Date: Thu, 3 Sep 2020 15:46:45 +0200 Subject: [PATCH 02/31] - Implemented repayWithCollateral() on LendingPoolLiquidationManager. --- contracts/interfaces/ILendingPool.sol | 21 ++ contracts/lendingpool/LendingPool.sol | 152 +++++---------- .../LendingPoolLiquidationManager.sol | 179 ++++++++++++++++++ contracts/libraries/logic/GenericLogic.sol | 3 +- 4 files changed, 245 insertions(+), 110 deletions(-) diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 43bfb554..b773a638 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -231,6 +231,27 @@ interface ILendingPool { bool receiveAToken ) external; + /** + * @dev flashes the underlying collateral on an user to swap for the owed asset and repay + * - Both the owner of the position and other liquidators can execute it + * - The owner can repay with his collateral at any point, no matter the health factor + * - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below + * @param collateral The address of the collateral asset + * @param principal The address of the owed asset + * @param user Address of the borrower + * @param principalAmount Amount of the debt to repay. type(uint256).max to repay the maximum possible + * @param receiver Address of the contract receiving the collateral to swap + * @param params Variadic bytes param to pass with extra information to the receiver + **/ + function repayWithCollateral( + address collateral, + address principal, + address user, + uint256 principalAmount, + address receiver, + bytes calldata params + ) external; + /** * @dev allows smartcontracts to access the liquidity of the pool within one transaction, * as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index b1b20a5a..1d266080 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -455,6 +455,49 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { } } + /** + * @dev flashes the underlying collateral on an user to swap for the owed asset and repay + * - Both the owner of the position and other liquidators can execute it + * - The owner can repay with his collateral at any point, no matter the health factor + * - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below + * @param collateral The address of the collateral asset + * @param principal The address of the owed asset + * @param user Address of the borrower + * @param principalAmount Amount of the debt to repay. type(uint256).max to repay the maximum possible + * @param receiver Address of the contract receiving the collateral to swap + * @param params Variadic bytes param to pass with extra information to the receiver + **/ + function repayWithCollateral( + address collateral, + address principal, + address user, + uint256 principalAmount, + address receiver, + bytes calldata params + ) external override nonReentrant { + address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager(); + + //solium-disable-next-line + (bool success, bytes memory result) = liquidationManager.delegatecall( + abi.encodeWithSignature( + 'repayWithCollateral(address,address,address,uint256,address,bytes)', + collateral, + principal, + user, + principalAmount, + receiver, + params + ) + ); + require(success, 'FAILED_REPAY_WITH_COLLATERAL'); + + (uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string)); + + if (returnCode != 0) { + revert(string(abi.encodePacked(returnMessage))); + } + } + /** * @dev allows smartcontracts to access the liquidity of the pool within one transaction, * as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts @@ -519,115 +562,6 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { emit FlashLoan(receiverAddress, asset, amount, amountFee); } - /** - * @dev flashes collateral, by both a flash liquidator or the user owning it. - * @param collateralAsset The address of the collateral asset. - * @param debtAsset The address of the debt asset. - * @param collateralAmount Collateral amount to flash. - * @param user Address of the user owning the collateral. - * @param receiverAddress Address of the contract receiving the collateral. - * @param debtMode Numeric variable, managing how to operate with the debt side. - * 1 -> With final repayment, to do it on the stable debt. - * 2 -> With final repayment, to do it on the variable debt. - * 3 -> On movement of the debt to the liquidator, to move the stable debt - * 4 -> On movement of the debt to the liquidator, to move the variable debt - * @param receiveAToken "true" to send aToken to the receiver contract, "false" to send underlying tokens. - * @param referralCode Integrators are assigned a referral code and can potentially receive rewards. - **/ - function flashCollateral( - address collateralAsset, - address debtAsset, - uint256 collateralAmount, - address user, - address receiverAddress, - uint256 debtMode, - bool receiveAToken, - uint16 referralCode - ) external override { - require(debtMode > 0, 'INVALID_DEBT_FLAG'); - - ReserveLogic.ReserveData storage collateralReserve = _reserves[collateralAsset]; - ReserveLogic.ReserveData storage debtReserve = _reserves[debtAsset]; - - address collateralAToken = collateralReserve.aTokenAddress; - uint256 availableCollateral = IERC20(collateralAToken).balanceOf(user); - - require(collateralAmount <= availableCollateral, 'NOT_ENOUGH_BALANCE'); - - address oracle = addressesProvider.getPriceOracle(); - (, , , , healthFactor) = GenericLogic.calculateUserAccountData( - user, - _reserves, - _usersConfig[user], - _reservesList, - oracle - ); - - if (healthFactor > GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD && msg.sender != user) { - revert('INVALID_FLASH_COLLATERAL_BY_NON_OWNER'); - } - - uint256 amountToFlash = (msg.sender == user || healthFactor < 0.98 ether) // TODO: better constant - ? collateralAmount - : collateralAmount.div(2); // TODO: better constant - - // If liquidator reclaims the aToken, he receives the equivalent atoken amount, - // otherwise receives the underlying asset - if (receiveAToken) { - IAToken(collateralAToken).transferOnLiquidation(user, receiverAddress, amountToFlash); - } else { - collateralReserve.updateCumulativeIndexesAndTimestamp(); - collateralReserve.updateInterestRates(collateral, aTokenAddress, 0, collateralAmount); - - // Burn of aToken and send the underlying to the receiver - IAToken(aTokenAddress).burn(user, receiver, collateralAmount); - } - - // Notifies the receiver to proceed, sending the underlying or the aToken amount already transferred - IFlashLoanReceiver(receiverAddress).executeOperation( - collateralAsset, - aTokenAddress, - (!receiveAToken) ? collateralAmount : 0, - receiverAToken ? aTokenAmount : 0, - params - ); - - // Calculation of the minimum amount of the debt asset to be received - uint256 debtAmountNeeded = oracle - .getAssetPrice(collateralAsset) - .mul(collateralAmount) - .mul(10**debtReserve.configuration.getDecimals()) - .div(oracle.getAssetPrice(debtAsset).mul(10**collateralReserve.configuration.getDecimals())) - .percentDiv(collateralReserve.configuration.getLiquidationBonus()); - - // Or the debt is transferred to the msg.sender, or funds are transferred from the receiver to repay the debt - if (debtMode > 2) { - (uint256 userStableDebt, uint256 userVariableDebt) = Helpers.getUserCurrentDebt( - user, - debtReserve - ); - - uint256 debtToTransfer; - if (debtMode.div(3) == 1) { - // stable - debtToTransfer = (userStableDebt > debtAmountNeeded) ? debtAmountNeeded : userStableDebt; - IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(user, debtToTransfer); - } else if (debtMode.div(3) == 2) { - // variable - debtToTransfer = (userVariableDebt > debtAmountNeeded) - ? debtAmountNeeded - : userVariableDebt; - IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(user, debtToTransfer); - } - _executeBorrow( - BorrowLocalVars(debtAsset, msg.sender, debtToTransfer, debt, false, referralCode) - ); - } else { - IERC20(debtAsset).transferFrom(receiverAddress, address(this), debtAmountNeeded); - _executeRepay(asset, msg.sender, amount, debtMode, user); - } - } - /** * @dev accessory functions to fetch data from the core contract **/ diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index a36ed3f3..7bbf149b 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -21,6 +21,7 @@ import {Helpers} from '../libraries/helpers/Helpers.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol'; +import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; /** * @title LendingPoolLiquidationManager contract @@ -65,6 +66,24 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl bool receiveAToken ); + /** + @dev emitted when a borrower/liquidator repays with the borrower's collateral + @param collateral the address of the collateral being swapped to repay + @param principal the address of the reserve of the debt + @param user the borrower's address + @param liquidator the address of the liquidator, same as the one of the borrower on self-repayment + @param principalAmount the amount of the debt finally covered + @param swappedCollateralAmount the amount of collateral finally swapped + */ + event RepaidWithCollateral( + address indexed collateral, + address indexed principal, + address indexed user, + address liquidator, + uint256 principalAmount, + uint256 swappedCollateralAmount + ); + enum LiquidationErrors { NO_ERROR, NO_COLLATERAL_AVAILABLE, @@ -271,6 +290,166 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl return (uint256(LiquidationErrors.NO_ERROR), 'No errors'); } + /** + * @dev flashes the underlying collateral on an user to swap for the owed asset and repay + * - Both the owner of the position and other liquidators can execute it. + * - The owner can repay with his collateral at any point, no matter the health factor. + * - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below. + * @param collateral The address of the collateral asset. + * @param principal The address of the owed asset. + * @param user Address of the borrower. + * @param principalAmount Amount of the debt to repay. + * @param receiver Address of the contract receiving the collateral to swap. + * @param params Variadic bytes param to pass with extra information to the receiver + **/ + function repayWithCollateral( + address collateral, + address principal, + address user, + uint256 principalAmount, + address receiver, + bytes calldata params + ) external returns (uint256, string memory) { + ReserveLogic.ReserveData storage debtReserve = reserves[principal]; + ReserveLogic.ReserveData storage collateralReserve = reserves[collateral]; + + UserConfiguration.Map storage userConfig = usersConfig[user]; + + LiquidationCallLocalVars memory vars; + + (, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData( + user, + reserves, + usersConfig[user], + reservesList, + addressesProvider.getPriceOracle() + ); + + if (msg.sender != user && vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) { + return ( + uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD), + 'HEALTH_FACTOR_ABOVE_THRESHOLD' + ); + } + + if (msg.sender != user) { + vars.isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 && + userConfig.isUsingAsCollateral(collateralReserve.index); + + //if collateral isn't enabled as collateral by user, it cannot be liquidated + if (!vars.isCollateralEnabled) { + return ( + uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED), + 'COLLATERAL_CANNOT_BE_LIQUIDATED' + ); + } + } + + (vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt( + user, + debtReserve + ); + + if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) { + return ( + uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED), + 'CURRRENCY_NOT_BORROWED' + ); + } + + if (msg.sender == user || vars.healthFactor < GenericLogic.HEALTH_FACTOR_CRITICAL_THRESHOLD) { + vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt); + } else { + vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul( + LIQUIDATION_CLOSE_FACTOR_PERCENT + ); + } + + vars.actualAmountToLiquidate = principalAmount > vars.maxPrincipalAmountToLiquidate + ? vars.maxPrincipalAmountToLiquidate + : principalAmount; + + vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress); + vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user); + + ( + vars.maxCollateralToLiquidate, + vars.principalAmountNeeded + ) = calculateAvailableCollateralToLiquidate( + collateralReserve, + debtReserve, + collateral, + principal, + vars.actualAmountToLiquidate, + vars.userCollateralBalance + ); + + //if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough + //of collateral to cover the actual amount that is being liquidated, hence we liquidate + //a smaller amount + if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) { + vars.actualAmountToLiquidate = vars.principalAmountNeeded; + } + + vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate); + + // Notifies the receiver to proceed, sending as param the underlying already transferred + IFlashLoanReceiver(receiver).executeOperation( + collateral, + address(vars.collateralAtoken), + vars.maxCollateralToLiquidate, + 0, + params + ); + + //updating debt reserve + debtReserve.updateCumulativeIndexesAndTimestamp(); + debtReserve.updateInterestRates( + principal, + debtReserve.aTokenAddress, + vars.actualAmountToLiquidate, + 0 + ); + IERC20(principal).transferFrom(receiver, debtReserve.aTokenAddress, vars.actualAmountToLiquidate); + + if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { + IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( + user, + vars.actualAmountToLiquidate + ); + } else { + IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( + user, + vars.userVariableDebt + ); + IStableDebtToken(debtReserve.stableDebtTokenAddress).burn( + user, + vars.actualAmountToLiquidate.sub(vars.userVariableDebt) + ); + } + + + //updating collateral reserve + collateralReserve.updateCumulativeIndexesAndTimestamp(); + collateralReserve.updateInterestRates( + collateral, + address(vars.collateralAtoken), + 0, + vars.maxCollateralToLiquidate + ); + + emit RepaidWithCollateral( + collateral, + principal, + user, + msg.sender, + vars.actualAmountToLiquidate, + vars.maxCollateralToLiquidate + ); + + return (uint256(LiquidationErrors.NO_ERROR), 'SUCCESS'); + } + struct AvailableCollateralToLiquidateLocalVars { uint256 userCompoundedBorrowBalance; uint256 liquidationBonus; diff --git a/contracts/libraries/logic/GenericLogic.sol b/contracts/libraries/logic/GenericLogic.sol index ead09d38..efe053a0 100644 --- a/contracts/libraries/logic/GenericLogic.sol +++ b/contracts/libraries/logic/GenericLogic.sol @@ -24,7 +24,8 @@ library GenericLogic { using ReserveConfiguration for ReserveConfiguration.Map; using UserConfiguration for UserConfiguration.Map; - uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18; + uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1 ether; + uint256 public constant HEALTH_FACTOR_CRITICAL_THRESHOLD = 0.98 ether; struct balanceDecreaseAllowedLocalVars { uint256 decimals; From 027a7e11e803c7e3054b4d9a428e4ca963ad0982 Mon Sep 17 00:00:00 2001 From: The3D Date: Sat, 5 Sep 2020 18:37:48 +0200 Subject: [PATCH 03/31] Removed userIndex --- contracts/tokenization/AToken.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index de178819..4fe82ada 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -26,9 +26,10 @@ contract AToken is VersionedInitializable, ERC20, IAToken { address public immutable UNDERLYING_ASSET_ADDRESS; - mapping(address => uint256) private _userIndexes; mapping(address => address) private _interestRedirectionAddresses; mapping(address => uint256) private _redirectedBalances; + mapping(address => uint256) private _redirectionIndexes; + mapping(address => address) private _interestRedirectionAllowances; LendingPool private immutable _pool; @@ -420,7 +421,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { balance .wadToRay() .rayMul(_pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)) - .rayDiv(_userIndexes[user]) .rayToWad(); } From a7b6beef4807c003562ee7784d8c86f437ce2bab Mon Sep 17 00:00:00 2001 From: The3D Date: Mon, 7 Sep 2020 17:55:47 +0200 Subject: [PATCH 04/31] Initial refactor commit --- contracts/libraries/helpers/Errors.sol | 2 +- contracts/tokenization/AToken.sol | 348 +++++++----------- contracts/tokenization/interfaces/IAToken.sol | 45 +-- 3 files changed, 138 insertions(+), 257 deletions(-) diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index 974aa5e1..796ff62d 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -46,7 +46,7 @@ library Errors { string public constant TRANSFER_AMOUNT_NOT_GT_0 = '31'; // 'Transferred amount needs to be greater than zero' string public constant INTEREST_ALREADY_REDIRECTED = '32'; // 'Interest is already redirected to the user' string public constant NO_VALID_BALANCE_FOR_REDIRECTION = '33'; // 'Interest stream can only be redirected if there is a valid balance' - + string public constant INVALID_ATOKEN_BALANCE = '52'; // burn balannce not valid // require error messages - ReserveLogic string public constant RESERVE_ALREADY_INITIALIZED = '34'; // 'Reserve has already been initialized' string public constant LIQUIDITY_INDEX_OVERFLOW = '47'; // Liquidity index overflows uint128 diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 4fe82ada..f477c40e 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -27,8 +27,10 @@ contract AToken is VersionedInitializable, ERC20, IAToken { address public immutable UNDERLYING_ASSET_ADDRESS; mapping(address => address) private _interestRedirectionAddresses; + mapping(address => uint256) private _interestRedirectionIndexes; + mapping(address => uint256) private _redirectedBalances; - mapping(address => uint256) private _redirectionIndexes; + mapping(address => uint256) private _redirectedBalanceIndexes; mapping(address => address) private _interestRedirectionAllowances; @@ -41,11 +43,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { _; } - modifier whenTransferAllowed(address from, uint256 amount) { - require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED); - _; - } - constructor( LendingPool pool, address underlyingAssetAddress, @@ -72,14 +69,35 @@ contract AToken is VersionedInitializable, ERC20, IAToken { /** * @notice ERC20 implementation internal function backing transfer() and transferFrom() - * @dev validates the transfer before allowing it. NOTE: This is not standard ERC20 behavior **/ function _transfer( address from, address to, - uint256 amount - ) internal override whenTransferAllowed(from, amount) { - _executeTransfer(from, to, amount); + uint256 amount, + bool validate + ) internal { + if(validate){ + require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED); + } + + uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + + uint256 scaledAmount = amount.rayDiv(index); + + super._transfer(from, to, scaledAmount); + + //if the sender is redirecting his interest towards someone else, + //adds to the redirected balance the accrued interest and removes the amount + //being transferred + _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledAmount, index); + + //if the receiver is redirecting his interest towards someone else, + //adds to the redirected balance the accrued interest and the amount + //being transferred + _updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index); + + emit BalanceTransfer(from, to, amount, index); + } /** @@ -127,37 +145,35 @@ contract AToken is VersionedInitializable, ERC20, IAToken { **/ function burn( address user, - address underlyingTarget, + address receiverOfUnderlying, uint256 amount ) external override onlyLendingPool { - //cumulates the balance of the user - (, uint256 currentBalance, uint256 balanceIncrease) = _calculateBalanceIncrease(user); + + uint256 currentBalance = balanceOf(user); + + require(currentBalance <= amount, Errors.INVALID_ATOKEN_BALANCE); + + uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + + uint256 scaledAmount = amount.rayDiv(index); + + _burn(user, scaledAmount); + + + if(amount == currentBalance){ + _resetDataOnZeroBalance(user); + } //if the user is redirecting his interest towards someone else, //we update the redirected balance of the redirection address by adding the accrued interest, //and removing the amount to redeem - _updateRedirectedBalanceOfRedirectionAddress(user, balanceIncrease, amount); - - if (balanceIncrease > amount) { - _mint(user, balanceIncrease.sub(amount)); - } else { - _burn(user, amount.sub(balanceIncrease)); - } - - uint256 userIndex = 0; - - //reset the user data if the remaining balance is 0 - if (currentBalance.sub(amount) == 0) { - _resetDataOnZeroBalance(user); - } else { - //updates the user index - userIndex = _userIndexes[user] = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - } + _updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index); //transfers the underlying to the target - ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(underlyingTarget, amount); + ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount); - emit Burn(msg.sender, underlyingTarget, amount, balanceIncrease, userIndex); + + emit Burn(msg.sender, receiverOfUnderlying, amount, index); } /** @@ -167,21 +183,20 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @param amount the amount of tokens to mint */ function mint(address user, uint256 amount) external override onlyLendingPool { - //cumulates the balance of the user - (, , uint256 balanceIncrease) = _calculateBalanceIncrease(user); - //updates the user index - uint256 index = _userIndexes[user] = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + + uint256 scaledAmount = amount.rayDiv(index); + + //mint an equivalent amount of tokens to cover the new deposit + _mint(user,scaledAmount); //if the user is redirecting his interest towards someone else, //we update the redirected balance of the redirection address by adding the accrued interest //and the amount deposited - _updateRedirectedBalanceOfRedirectionAddress(user, balanceIncrease.add(amount), 0); + _updateRedirectedBalanceOfRedirectionAddress(user, user, amount, 0, index); - //mint an equivalent amount of tokens to cover the new deposit - _mint(user, amount.add(balanceIncrease)); - - emit Mint(user, amount, balanceIncrease, index); + emit Mint(user, amount, index); } /** @@ -198,7 +213,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { ) external override onlyLendingPool { //being a normal transfer, the Transfer() and BalanceTransfer() are emitted //so no need to emit a specific event here - _executeTransfer(from, to, value); + _transfer(from, to, value, false); } /** @@ -208,42 +223,26 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @return the total balance of the user **/ function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) { - //current principal balance of the user - uint256 currentPrincipalBalance = super.balanceOf(user); + //current scaled balance of the user + uint256 currentScaledBalance = super.balanceOf(user); + //balance redirected by other users to user for interest rate accrual uint256 redirectedBalance = _redirectedBalances[user]; - if (currentPrincipalBalance == 0 && redirectedBalance == 0) { + if (currentScaledBalance == 0 && redirectedBalance == 0) { return 0; } - //if the user is not redirecting the interest to anybody, accrues - //the interest for himself - if (_interestRedirectionAddresses[user] == address(0)) { - //accruing for himself means that both the principal balance and - //the redirected balance partecipate in the interest - return - _calculateCumulatedBalance(user, currentPrincipalBalance.add(redirectedBalance)).sub( - redirectedBalance - ); - } else { - //if the user redirected the interest, then only the redirected - //balance generates interest. In that case, the interest generated - //by the redirected balance is added to the current principal balance. - return - currentPrincipalBalance.add( - _calculateCumulatedBalance(user, redirectedBalance).sub(redirectedBalance) - ); - } + return _calculateCumulatedBalance(user, currentScaledBalance, redirectedBalance); } /** - * @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. + * @dev returns the scaled balance of the user. The scaled balance is the sum of all the + * updated stored balance divided the reserve index at the moment of the update * @param user the address of the user - * @return the principal balance of the user + * @return the scaled balance of the user **/ - function principalBalanceOf(address user) external override view returns (uint256) { + function scaledBalanceOf(address user) external override view returns (uint256) { return super.balanceOf(user); } @@ -254,14 +253,14 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @return the current total supply **/ function totalSupply() public override(ERC20, IERC20) view returns (uint256) { - uint256 currentSupplyPrincipal = super.totalSupply(); + uint256 currentSupplyScaled = super.totalSupply(); - if (currentSupplyPrincipal == 0) { + if (currentSupplyScaled == 0) { return 0; } return - currentSupplyPrincipal + currentSupplyScaled .wadToRay() .rayMul(_pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)) .rayToWad(); @@ -277,15 +276,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return _pool.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount); } - /** - * @dev returns the last index of the user, used to calculate the balance of the user - * @param user address of the user - * @return the last user index - **/ - function getUserIndex(address user) external override view returns (uint256) { - return _userIndexes[user]; - } - /** * @dev returns the address to which the interest is redirected * @param user address of the user @@ -305,73 +295,20 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return _redirectedBalances[user]; } - /** - * @dev calculates the increase in balance since the last user action - * @param user the address of the user - * @return the last user principal balance, the current balance and the balance increase - **/ - function _calculateBalanceIncrease(address user) - internal - view - returns ( - uint256, - uint256, - uint256 - ) - { - uint256 currentBalance = balanceOf(user); - uint256 balanceIncrease = 0; - uint256 previousBalance = 0; - - if (currentBalance != 0) { - previousBalance = super.balanceOf(user); - //calculate the accrued interest since the last accumulation - balanceIncrease = currentBalance.sub(previousBalance); - } - - return (previousBalance, currentBalance, balanceIncrease); - } - - /** - * @dev accumulates the accrued interest of the user to the principal balance - * @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 _cumulateBalance(address user) - internal - returns ( - uint256, - uint256, - uint256, - uint256 - ) - { - ( - uint256 previousBalance, - uint256 currentBalance, - uint256 balanceIncrease - ) = _calculateBalanceIncrease(user); - - _mint(user, balanceIncrease); - - //updates the user index - uint256 index = _userIndexes[user] = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - - return (previousBalance, currentBalance, balanceIncrease, index); - } /** * @dev updates the redirected balance of the user. If the user is not redirecting his * interest, nothing is executed. * @param user the address of the user for which the interest is being accumulated - * @param balanceToAdd the amount to add to the redirected balance - * @param balanceToRemove the amount to remove from the redirected balance + * @param scaledBalanceToAdd the amount to add to the redirected balance + * @param scaledBalanceToRemove the amount to remove from the redirected balance **/ function _updateRedirectedBalanceOfRedirectionAddress( + address origin, address user, - uint256 balanceToAdd, - uint256 balanceToRemove + uint256 scaledBalanceToAdd, + uint256 scaledBalanceToRemove, + uint256 index ) internal { address redirectionAddress = _interestRedirectionAddresses[user]; //if there isn't any redirection, nothing to be done @@ -379,101 +316,72 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return; } - //compound balances of the redirected address - (, , uint256 balanceIncrease, uint256 index) = _cumulateBalance(redirectionAddress); + //updating the interest redirection index of the user + _interestRedirectionIndexes[user] = index; + //updating the redirected balance _redirectedBalances[redirectionAddress] = _redirectedBalances[redirectionAddress] - .add(balanceToAdd) - .sub(balanceToRemove); + .add(scaledBalanceToAdd) + .sub(scaledBalanceToRemove); + + //updating the redirected balance index of the redirection target + _redirectedBalanceIndexes[redirectionAddress] = index; + //if the interest of redirectionAddress is also being redirected, we need to update //the redirected balance of the redirection target by adding the balance increase address targetOfRedirectionAddress = _interestRedirectionAddresses[redirectionAddress]; - // if the redirection address is also redirecting the interest, we accumulate his balance - // and update his chain of redirection - if (targetOfRedirectionAddress != address(0)) { - _updateRedirectedBalanceOfRedirectionAddress(redirectionAddress, balanceIncrease, 0); + + // if the redirection address is also redirecting the interest, we update his index to + // accumulate the interest until now + // note: if the next address of redirection is the same as the one who originated the update, + // it means a loop of redirection has been formed: in this case, we break the recursion as no + // further updates are needed + if (targetOfRedirectionAddress != address(0) && targetOfRedirectionAddress != origin) { + _updateRedirectedBalanceOfRedirectionAddress(origin, redirectionAddress, 0, 0, index); } emit RedirectedBalanceUpdated( redirectionAddress, - balanceIncrease, - index, - balanceToAdd, - balanceToRemove + scaledBalanceToAdd, + scaledBalanceToRemove, + index ); } /** * @dev calculate the interest accrued by user on a specific balance * @param user the address of the user for which the interest is being accumulated - * @param balance the balance on which the interest is calculated + * @param scaledBalance the balance on which the interest is calculated + * @param redirectedBalance the balance redirected to the user * @return the interest rate accrued **/ - function _calculateCumulatedBalance(address user, uint256 balance) + function _calculateCumulatedBalance(address user, uint256 scaledBalance, uint256 redirectedBalance) internal view returns (uint256) { - return - balance - .wadToRay() - .rayMul(_pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)) - .rayToWad(); - } + uint256 scaledRedirectedBalance = redirectedBalance.wadToRay().rayDiv(_interestRedirectionIndexes[user]).rayToWad(); - /** - * @dev executes the transfer of aTokens, invoked by both _transfer() and - * transferOnLiquidation() - * @param from the address from which transfer the aTokens - * @param to the destination address - * @param value the amount to transfer - **/ - function _executeTransfer( - address from, - address to, - uint256 value - ) internal { - require(value > 0, Errors.TRANSFER_AMOUNT_NOT_GT_0); + uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - //cumulate the balance of the sender - (, uint256 fromBalance, uint256 fromBalanceIncrease, uint256 fromIndex) = _cumulateBalance( - from - ); + if(_interestRedirectionAddresses[user] == address(0)){ + //if the user is not redirecting the interest, his balance is the result of + //the interest accrued by his current scaled balance and the interest accrued by his + //scaled redirected balance - //cumulate the balance of the receiver - (, , uint256 toBalanceIncrease, uint256 toIndex) = _cumulateBalance(to); - - //if the sender is redirecting his interest towards someone else, - //adds to the redirected balance the accrued interest and removes the amount - //being transferred - _updateRedirectedBalanceOfRedirectionAddress(from, fromBalanceIncrease, value); - - //if the receiver is redirecting his interest towards someone else, - //adds to the redirected balance the accrued interest and the amount - //being transferred - _updateRedirectedBalanceOfRedirectionAddress(to, toBalanceIncrease.add(value), 0); - - //performs the transfer - super._transfer(from, to, value); - - bool fromIndexReset = false; - //reset the user data if the remaining balance is 0 - if (fromBalance.sub(value) == 0 && from != to) { - fromIndexReset = _resetDataOnZeroBalance(from); + return scaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(scaledRedirectedBalance); } - emit BalanceTransfer( - from, - to, - value, - fromBalanceIncrease, - toBalanceIncrease, - fromIndexReset ? 0 : fromIndex, - toIndex - ); + //if the user is redirecting, his balance only increases by the balance he is being redirected + uint256 lastRedirectedBalance = scaledBalance.rayDiv(_interestRedirectionIndexes[user]); + + return + lastRedirectedBalance.add( + scaledRedirectedBalance.rayMul(index) + ).sub(scaledRedirectedBalance); } /** @@ -483,42 +391,42 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @param to the destination address **/ function _redirectInterestStream(address from, address to) internal { + address currentRedirectionAddress = _interestRedirectionAddresses[from]; require(to != currentRedirectionAddress, Errors.INTEREST_ALREADY_REDIRECTED); - //accumulates the accrued interest to the principal - ( - uint256 previousPrincipalBalance, - uint256 fromBalance, - uint256 balanceIncrease, - uint256 fromIndex - ) = _cumulateBalance(from); + uint256 fromBalance = balanceOf(from); require(fromBalance > 0, Errors.NO_VALID_BALANCE_FOR_REDIRECTION); + uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + + uint256 scaledBalance = fromBalance.rayDiv(index); + //if the user is already redirecting the interest to someone, before changing //the redirection address we substract the redirected balance of the previous //recipient if (currentRedirectionAddress != address(0)) { - _updateRedirectedBalanceOfRedirectionAddress(from, 0, previousPrincipalBalance); + _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledBalance, index); } //if the user is redirecting the interest back to himself, //we simply set to 0 the interest redirection address if (to == from) { _interestRedirectionAddresses[from] = address(0); - emit InterestStreamRedirected(from, address(0), fromBalance, balanceIncrease, fromIndex); + emit InterestStreamRedirected(from, address(0), scaledBalance, index); return; } //first set the redirection address to the new recipient _interestRedirectionAddresses[from] = to; + _interestRedirectionIndexes[from] = index; //adds the user balance to the redirected balance of the destination - _updateRedirectedBalanceOfRedirectionAddress(from, fromBalance, 0); + _updateRedirectedBalanceOfRedirectionAddress(from, from, fromBalance, 0, index); - emit InterestStreamRedirected(from, to, fromBalance, balanceIncrease, fromIndex); + emit InterestStreamRedirected(from, to, fromBalance, index); } /** @@ -532,15 +440,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { _interestRedirectionAddresses[user] = address(0); //emits a InterestStreamRedirected event to notify that the redirection has been reset - emit InterestStreamRedirected(user, address(0), 0, 0, 0); - - //if the redirected balance is also 0, we clear up the user index - if (_redirectedBalances[user] == 0) { - _userIndexes[user] = 0; - return true; - } else { - return false; - } + emit InterestStreamRedirected(user, address(0), 0, 0); } /** diff --git a/contracts/tokenization/interfaces/IAToken.sol b/contracts/tokenization/interfaces/IAToken.sol index 65ad0cfa..d2b23da9 100644 --- a/contracts/tokenization/interfaces/IAToken.sol +++ b/contracts/tokenization/interfaces/IAToken.sol @@ -8,44 +8,35 @@ interface IAToken is IERC20 { * @dev emitted after aTokens are burned * @param from the address performing the redeem * @param value the amount to be redeemed - * @param fromBalanceIncrease the cumulated balance since the last update of the user - * @param fromIndex the last index of the user + * @param index the last index of the reserve **/ event Burn( address indexed from, address indexed target, uint256 value, - uint256 fromBalanceIncrease, - uint256 fromIndex + uint256 index ); /** * @dev emitted after the mint action * @param from the address performing the mint * @param value the amount to be minted - * @param fromBalanceIncrease the cumulated balance since the last update of the user - * @param fromIndex the last index of the user + * @param index the last index of the reserve **/ - event Mint(address indexed from, uint256 value, uint256 fromBalanceIncrease, uint256 fromIndex); + 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 * @param to the adress of the destination * @param value the amount to be minted - * @param fromBalanceIncrease the cumulated balance since the last update of the user - * @param toBalanceIncrease the cumulated balance since the last update of the destination - * @param fromIndex the last index of the user - * @param toIndex the last index of the liquidator + * @param index the last index of the reserve **/ event BalanceTransfer( address indexed from, address indexed to, uint256 value, - uint256 fromBalanceIncrease, - uint256 toBalanceIncrease, - uint256 fromIndex, - uint256 toIndex + uint256 index ); /** @@ -53,31 +44,28 @@ interface IAToken is IERC20 { * by an user is redirected to another user * @param from the address from which the interest is being redirected * @param to the adress of the destination - * @param fromBalanceIncrease the cumulated balance since the last update of the user - * @param fromIndex the last index of the user + * @param redirectedBalance the scaled balance being redirected + * @param index the last index of the reserve **/ event InterestStreamRedirected( address indexed from, address indexed to, uint256 redirectedBalance, - uint256 fromBalanceIncrease, - uint256 fromIndex + uint256 index ); /** * @dev emitted when the redirected balance of an user is being updated * @param targetAddress the address of which the balance is being updated - * @param targetBalanceIncrease the cumulated balance since the last update of the target - * @param targetIndex the last index of the user * @param redirectedBalanceAdded the redirected balance being added * @param redirectedBalanceRemoved the redirected balance being removed + * @param index the last index of the reserve **/ event RedirectedBalanceUpdated( address indexed targetAddress, - uint256 targetBalanceIncrease, - uint256 targetIndex, uint256 redirectedBalanceAdded, - uint256 redirectedBalanceRemoved + uint256 redirectedBalanceRemoved, + uint256 index ); event InterestRedirectionAllowanceChanged(address indexed from, address indexed to); @@ -146,7 +134,7 @@ interface IAToken is IERC20 { * @param user the address of the user * @return the principal balance of the user **/ - function principalBalanceOf(address user) external view returns (uint256); + function scaledBalanceOf(address user) external view returns (uint256); /** * @dev Used to validate transfers before actually executing them. @@ -156,13 +144,6 @@ interface IAToken is IERC20 { **/ function isTransferAllowed(address user, uint256 amount) external view returns (bool); - /** - * @dev returns the last index of the user, used to calculate the balance of the user - * @param user address of the user - * @return the last user index - **/ - function getUserIndex(address user) external view returns (uint256); - /** * @dev returns the address to which the interest is redirected * @param user address of the user From 03767e003fd41f9e084c10ecb7b3f40bf37edf4b Mon Sep 17 00:00:00 2001 From: The3D Date: Tue, 8 Sep 2020 13:45:24 +0200 Subject: [PATCH 05/31] Fixes deposits tests --- contracts/tokenization/AToken.sol | 144 +++++++++--------- contracts/tokenization/interfaces/IAToken.sol | 7 + deployed-contracts.json | 60 ++++---- test/helpers/utils/calculations.ts | 111 +++++--------- test/helpers/utils/helpers.ts | 20 +-- test/helpers/utils/interfaces/index.ts | 4 +- test/scenario.spec.ts | 2 +- 7 files changed, 154 insertions(+), 194 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index f477c40e..3e06d52a 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -67,39 +67,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { _setDecimals(underlyingAssetDecimals); } - /** - * @notice ERC20 implementation internal function backing transfer() and transferFrom() - **/ - function _transfer( - address from, - address to, - uint256 amount, - bool validate - ) internal { - if(validate){ - require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED); - } - - uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - - uint256 scaledAmount = amount.rayDiv(index); - - super._transfer(from, to, scaledAmount); - - //if the sender is redirecting his interest towards someone else, - //adds to the redirected balance the accrued interest and removes the amount - //being transferred - _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledAmount, index); - - //if the receiver is redirecting his interest towards someone else, - //adds to the redirected balance the accrued interest and the amount - //being transferred - _updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index); - - emit BalanceTransfer(from, to, amount, index); - - } - /** * @dev redirects the interest generated to a target address. * when the interest is redirected, the user balance is added to @@ -187,7 +154,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); uint256 scaledAmount = amount.rayDiv(index); - + //mint an equivalent amount of tokens to cover the new deposit _mint(user,scaledAmount); @@ -232,8 +199,24 @@ contract AToken is VersionedInitializable, ERC20, IAToken { if (currentScaledBalance == 0 && redirectedBalance == 0) { return 0; } + uint256 scaledRedirectedBalance = redirectedBalance > 0 ? redirectedBalance.rayDiv(_interestRedirectionIndexes[user]) : 0; - return _calculateCumulatedBalance(user, currentScaledBalance, redirectedBalance); + uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + + if(_interestRedirectionAddresses[user] == address(0)){ + //if the user is not redirecting the interest, his balance is the result of + //the interest accrued by his current scaled balance and the interest accrued by his + //scaled redirected balance + return currentScaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(scaledRedirectedBalance); + } + + //if the user is redirecting, his balance only increases by the balance he is being redirected to + uint256 lastRedirectedBalance = currentScaledBalance.rayDiv(_interestRedirectionIndexes[user]); + + return + lastRedirectedBalance.add( + scaledRedirectedBalance.rayMul(index) + ).sub(scaledRedirectedBalance); } /** @@ -246,6 +229,17 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return super.balanceOf(user); } + + /** + * @dev returns the scaled balance of the user. The scaled balance is the sum of all the + * updated stored balance divided the reserve index at the moment of the update + * @param user the address of the user + * @return the scaled balance of the user + **/ + function getUserInterestRedirectionIndex(address user) external override view returns (uint256) { + return _interestRedirectionIndexes[user]; + } + /** * @dev calculates the total supply of the specific aToken * since the balance of every single user increases over time, the total supply @@ -351,39 +345,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { ); } - /** - * @dev calculate the interest accrued by user on a specific balance - * @param user the address of the user for which the interest is being accumulated - * @param scaledBalance the balance on which the interest is calculated - * @param redirectedBalance the balance redirected to the user - * @return the interest rate accrued - **/ - function _calculateCumulatedBalance(address user, uint256 scaledBalance, uint256 redirectedBalance) - internal - view - returns (uint256) - { - uint256 scaledRedirectedBalance = redirectedBalance.wadToRay().rayDiv(_interestRedirectionIndexes[user]).rayToWad(); - - uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - - if(_interestRedirectionAddresses[user] == address(0)){ - //if the user is not redirecting the interest, his balance is the result of - //the interest accrued by his current scaled balance and the interest accrued by his - //scaled redirected balance - - return scaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(scaledRedirectedBalance); - } - - //if the user is redirecting, his balance only increases by the balance he is being redirected - uint256 lastRedirectedBalance = scaledBalance.rayDiv(_interestRedirectionIndexes[user]); - - return - lastRedirectedBalance.add( - scaledRedirectedBalance.rayMul(index) - ).sub(scaledRedirectedBalance); - } - /** * @dev executes the redirection of the interest from one address to another. * immediately after redirection, the destination address will start to accrue interest. @@ -444,13 +405,12 @@ contract AToken is VersionedInitializable, ERC20, IAToken { } /** - * @dev transfers the underlying asset to the target. Used by the lendingpool to transfer - * assets in borrow(), redeem() and flashLoan() - * @param target the target of the transfer - * @param amount the amount to transfer - * @return the amount transferred - **/ - + * @dev transfers the underlying asset to the target. Used by the lendingpool to transfer + * assets in borrow(), redeem() and flashLoan() + * @param target the target of the transfer + * @param amount the amount to transfer + * @return the amount transferred + **/ function transferUnderlyingTo(address target, uint256 amount) external override @@ -461,6 +421,40 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return amount; } + + /** + * @notice ERC20 implementation internal function backing transfer() and transferFrom() + **/ + function _transfer( + address from, + address to, + uint256 amount, + bool validate + ) internal { + if(validate){ + require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED); + } + + uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + + uint256 scaledAmount = amount.rayDiv(index); + + super._transfer(from, to, scaledAmount); + + //if the sender is redirecting his interest towards someone else, + //adds to the redirected balance the accrued interest and removes the amount + //being transferred + _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledAmount, index); + + //if the receiver is redirecting his interest towards someone else, + //adds to the redirected balance the accrued interest and the amount + //being transferred + _updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index); + + emit BalanceTransfer(from, to, amount, index); + + } + /** * @dev aTokens should not receive ETH **/ diff --git a/contracts/tokenization/interfaces/IAToken.sol b/contracts/tokenization/interfaces/IAToken.sol index d2b23da9..8250e242 100644 --- a/contracts/tokenization/interfaces/IAToken.sol +++ b/contracts/tokenization/interfaces/IAToken.sol @@ -151,6 +151,13 @@ interface IAToken is IERC20 { **/ function getInterestRedirectionAddress(address user) external view returns (address); + /** + * @dev returns the index of the user at the moment of redirection + * @param user address of the user + * @return interest redirection index + **/ + function getUserInterestRedirectionIndex(address user) external view returns (uint256); + /** * @dev returns the redirected balance of the user. The redirected balance is the balance * redirected by other accounts to the user, that is accrueing interest for him. diff --git a/deployed-contracts.json b/deployed-contracts.json index d04816b9..07078f5d 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -5,7 +5,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xf8c6eB390cDc5C08717bC2268aa0c1169A9B5deE", + "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -15,7 +15,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x4a716924Dad0c0d0E558844F304548814e7089F1", + "address": "0xa4bcDF64Cdd5451b6ac3743B414124A6299B65FF", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -25,7 +25,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x798c5b4b62b1eA9D64955D6751B03075A003F123", + "address": "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -188,7 +188,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x11df1AF606b85226Ab9a8B1FDa90395298e7494F", + "address": "0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -198,7 +198,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8f9A92c125FFEb83d8eC808Cd9f8cb80084c1E37", + "address": "0x8858eeB3DfffA017D4BCE9801D340D36Cf895CCf", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -208,7 +208,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xc4007844AE6bBe168cE8D692C86a7A4414FBcD26", + "address": "0x0078371BDeDE8aAc7DeBfFf451B74c5EDB385Af7", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -218,7 +218,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xAb768C858C33DfcB6651d1174AFb750433a87Be0", + "address": "0xf4e77E5Da47AC3125140c470c71cBca77B5c638c", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -228,7 +228,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xA089557D64DAE4b4FcB65aB7C8A520AABb213e37", + "address": "0x3619DbE27d7c1e7E91aA738697Ae7Bc5FC3eACA5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -238,7 +238,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x20FAE2042b362E3FaB2806820b9A43CC116e2846", + "address": "0x038B86d9d8FAFdd0a02ebd1A476432877b0107C8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -248,7 +248,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8880F314112f15C2AfF674c3B27f9a44Ca86e4d0", + "address": "0x1A1FEe7EeD918BD762173e4dc5EfDB8a78C924A8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -258,7 +258,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xDcb10C2e15110Db4B02C0a1df459768E680ce245", + "address": "0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -268,7 +268,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xfD408ec64Da574b1859814F810564f73ea2Ff003", + "address": "0xc4905364b78a742ccce7B890A89514061E47068D", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -278,7 +278,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x0006F7c3542BEE76Dd887f54eD22405Ac4ae905a", + "address": "0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -288,7 +288,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x6ca94a51c644eca3F9CA315bcC41CbA6940A66Eb", + "address": "0x8B5B7a6055E54a36fF574bbE40cf2eA68d5554b3", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -298,7 +298,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x6765291Cab755B980F377445eFd0F9F945CDA6C4", + "address": "0xEcc0a6dbC0bb4D51E4F84A315a9e5B0438cAD4f0", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -308,7 +308,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xa7dB4d25Fc525d19Fbda4E74AAF447B88420FbcB", + "address": "0x20Ce94F404343aD2752A2D01b43fa407db9E0D00", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -318,7 +318,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x273D60904A8DBa3Ae6B20505c59902644124fF0E", + "address": "0x1d80315fac6aBd3EfeEbE97dEc44461ba7556160", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -328,7 +328,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xfc37dE87C1Ee39cc856782BF96fEdcB6FA5c5A7f", + "address": "0x2D8553F9ddA85A9B3259F6Bf26911364B85556F5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -338,7 +338,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x049228dFFEdf91ff224e9F96247aEBA700e3590c", + "address": "0x52d3b94181f8654db2530b0fEe1B19173f519C52", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -348,7 +348,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xA410D1f3fEAF300842142Cd7AA1709D84944DCb7", + "address": "0xd15468525c35BDBC1eD8F2e09A00F8a173437f2f", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -358,7 +358,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x835973768750b3ED2D5c3EF5AdcD5eDb44d12aD4", + "address": "0x7e35Eaf7e8FBd7887ad538D4A38Df5BbD073814a", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -368,7 +368,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1181FC27dbF04B5105243E60BB1936c002e9d5C8", + "address": "0x5bcb88A0d20426e451332eE6C4324b0e663c50E0", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -378,7 +378,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x6F96975e2a0e1380b6e2e406BB33Ae96e4b6DB65", + "address": "0x3521eF8AaB0323004A6dD8b03CE890F4Ea3A13f5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -388,7 +388,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xc032930653da193EDE295B4DcE3DD093a695c3b3", + "address": "0x53369fd4680FfE3DfF39Fc6DDa9CfbfD43daeA2E", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -398,7 +398,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xb3363f4349b1160DbA55ec4D82fDe874A4123A2a", + "address": "0xB00cC45B4a7d3e1FEE684cFc4417998A1c183e6d", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -408,7 +408,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xf8c6eB390cDc5C08717bC2268aa0c1169A9B5deE", + "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -456,7 +456,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1203D1b97BF6E546c00C45Cda035D3010ACe1180", + "address": "0xF5E6E6B10E4F2f27DaC1fFdDE83367dE9525552a", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -466,7 +466,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x2cc20bE530F92865c2ed8CeD0b020a11bFe62Fe7", + "address": "0xf784709d2317D872237C4bC22f867d1BAe2913AB", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -476,7 +476,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8733AfE8174BA7c04c6CD694bD673294079b7E10", + "address": "0x7f23223A2FAf869962B38f5eC4aAB7f37454A45e", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -486,7 +486,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xA8083d78B6ABC328b4d3B714F76F384eCC7147e1", + "address": "0x1203D1b97BF6E546c00C45Cda035D3010ACe1180", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } } diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index 4ff03b19..d14a0eb6 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -41,7 +41,7 @@ export const calcExpectedUserDataAfterDeposit = ( txTimestamp ); - expectedUserData.principalATokenBalance = userDataBeforeAction.principalStableDebt; + expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate; @@ -49,9 +49,6 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.liquidityRate = reserveDataAfterAction.liquidityRate; - expectedUserData.currentATokenBalance = userDataBeforeAction.currentATokenBalance.plus( - amountDeposited - ); if (userDataBeforeAction.currentATokenBalance.eq(0)) { expectedUserData.usageAsCollateralEnabled = true; @@ -67,7 +64,10 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited); - expectedUserData.principalATokenBalance = expectedUserData.currentATokenBalance = calcExpectedATokenBalance( + expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(reserveDataAfterAction, userDataBeforeAction, new BigNumber(amountDeposited), new BigNumber(0)); + + + expectedUserData.currentATokenBalance = calcExpectedATokenBalance( reserveDataBeforeAction, userDataBeforeAction, txTimestamp @@ -75,12 +75,8 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex( - reserveDataBeforeAction, - expectedUserData.currentATokenBalance, - expectedUserData.redirectedBalance, - txTimestamp - ); + expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS ? new BigNumber(0) : reserveDataAfterAction.liquidityIndex; + expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( userDataBeforeAction, @@ -169,12 +165,6 @@ export const calcExpectedUserDataAfterWithdraw = ( } else { expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; } - expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex( - reserveDataBeforeAction, - expectedUserData.currentATokenBalance, - expectedUserData.redirectedBalance, - txTimestamp - ); expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( userDataBeforeAction, @@ -814,12 +804,6 @@ export const calcExpectedUserDataAfterSwapRateMode = ( expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; expectedUserData.redirectionAddressRedirectedBalance = userDataBeforeAction.redirectionAddressRedirectedBalance; - expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex( - reserveDataBeforeAction, - expectedUserData.currentATokenBalance, - expectedUserData.redirectedBalance, - txTimestamp - ); if (rateMode === RateMode.Stable) { // swap to variable @@ -966,18 +950,12 @@ export const calcExpectedUserDataAfterStableRateRebalance = ( userDataBeforeAction, txTimestamp ); - expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance; + expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; expectedUserData.redirectionAddressRedirectedBalance = userDataBeforeAction.redirectionAddressRedirectedBalance; - expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex( - reserveDataBeforeAction, - expectedUserData.currentATokenBalance, - expectedUserData.redirectedBalance, - txTimestamp - ); return expectedUserData; }; @@ -1012,7 +990,9 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( expectedFromData.stableBorrowRate = fromDataBeforeAction.stableBorrowRate; expectedToData.stableBorrowRate = toDataBeforeAction.stableBorrowRate; - expectedFromData.principalATokenBalance = expectedFromData.currentATokenBalance = calcExpectedATokenBalance( + expectedFromData.scaledATokenBalance = + + expectedFromData.currentATokenBalance = calcExpectedATokenBalance( reserveDataBeforeAction, fromDataBeforeAction, txTimestamp @@ -1047,70 +1027,52 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( ); } - expectedFromData.currentATokenUserIndex = calcExpectedATokenUserIndex( - reserveDataBeforeAction, - expectedFromData.currentATokenBalance, - expectedFromData.redirectedBalance, - txTimestamp - ); - - expectedToData.currentATokenUserIndex = calcExpectedATokenUserIndex( - reserveDataBeforeAction, - expectedToData.currentATokenBalance, - expectedToData.redirectedBalance, - txTimestamp - ); - return [expectedFromData, expectedToData]; }; -const calcExpectedATokenUserIndex = ( - reserveDataBeforeAction: ReserveData, - expectedUserBalanceAfterAction: BigNumber, - expectedUserRedirectedBalanceAterAction: BigNumber, - currentTimestamp: BigNumber + +const calcExpectedScaledATokenBalance = ( + reserveDataAfterAction: ReserveData, + userDataBeforeAction: UserReserveData, + amountAdded: BigNumber, + amountTaken: BigNumber + ) => { - if (expectedUserBalanceAfterAction.eq(0) && expectedUserRedirectedBalanceAterAction.eq(0)) { - return new BigNumber(0); - } - return calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp); -}; + return userDataBeforeAction.scaledATokenBalance.plus(amountAdded.rayDiv(reserveDataAfterAction.liquidityIndex)); +} const calcExpectedATokenBalance = ( reserveDataBeforeAction: ReserveData, userDataBeforeAction: UserReserveData, currentTimestamp: BigNumber ) => { - const income = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp); + const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp); const { interestRedirectionAddress, - currentATokenUserIndex: userIndexBeforeAction, + interestRedirectionIndex: redirectionIndexBeforeAction, redirectedBalance, - principalATokenBalance: principalBalanceBeforeAction, + scaledATokenBalance: scaledBalanceBeforeAction, } = userDataBeforeAction; - if (userIndexBeforeAction.eq(0)) { - return principalBalanceBeforeAction; + if (scaledBalanceBeforeAction.eq(0) && redirectedBalance.eq(0)) { + return new BigNumber(0); } + if (interestRedirectionAddress === ZERO_ADDRESS) { - return principalBalanceBeforeAction + return scaledBalanceBeforeAction .plus(redirectedBalance) - .wadToRay() - .rayMul(income) - .rayDiv(userIndexBeforeAction) - .rayToWad() + .rayMul(index) .minus(redirectedBalance); - } else { - return principalBalanceBeforeAction.plus( + } + + const lastRedirectedBalance = scaledBalanceBeforeAction.rayDiv(redirectionIndexBeforeAction); + + return lastRedirectedBalance.plus( redirectedBalance - .wadToRay() - .rayMul(income) - .rayDiv(userIndexBeforeAction) - .rayToWad() + .rayMul(index) .minus(redirectedBalance) ); - } }; const calcExpectedRedirectedBalance = ( @@ -1120,12 +1082,9 @@ const calcExpectedRedirectedBalance = ( amountToAdd: BigNumber, amountToSubstract: BigNumber ): BigNumber => { - const balanceIncrease = userDataBeforeAction.currentATokenBalance.minus( - userDataBeforeAction.principalATokenBalance - ); return expectedUserDataAfterAction.interestRedirectionAddress !== ZERO_ADDRESS - ? redirectedBalanceBefore.plus(balanceIncrease).plus(amountToAdd).minus(amountToSubstract) + ? redirectedBalanceBefore.plus(amountToAdd).minus(amountToSubstract) : new BigNumber('0'); }; const calcExpectedAverageStableBorrowRate = ( diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index c20c67ac..aa564de0 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -69,22 +69,22 @@ export const getUserData = async ( ]); const [ - userIndex, redirectedBalance, - principalATokenBalance, + scaledATokenBalance, redirectionAddressRedirectedBalance, interestRedirectionAddress, + interestRedirectionIndex, ] = aTokenData; const token = await getMintableErc20(reserve); const walletBalance = new BigNumber((await token.balanceOf(user)).toString()); return { - principalATokenBalance: new BigNumber(principalATokenBalance), + scaledATokenBalance: new BigNumber(scaledATokenBalance), interestRedirectionAddress, + interestRedirectionIndex, redirectionAddressRedirectedBalance: new BigNumber(redirectionAddressRedirectedBalance), redirectedBalance: new BigNumber(redirectedBalance), - currentATokenUserIndex: new BigNumber(userIndex), currentATokenBalance: new BigNumber(userData.currentATokenBalance.toString()), currentStableDebt: new BigNumber(userData.currentStableDebt.toString()), currentVariableDebt: new BigNumber(userData.currentVariableDebt.toString()), @@ -115,15 +115,15 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo const aToken = await getAToken(aTokenAddress); const [ - userIndex, interestRedirectionAddress, redirectedBalance, - principalTokenBalance, + scaledATokenBalance, + interestRedirectionIndex ] = await Promise.all([ - aToken.getUserIndex(user), aToken.getInterestRedirectionAddress(user), aToken.getRedirectedBalance(user), - aToken.principalBalanceOf(user), + aToken.scaledBalanceOf(user), + aToken.getUserInterestRedirectionIndex(user) ]); const redirectionAddressRedirectedBalance = @@ -132,10 +132,10 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo : new BigNumber('0'); return [ - userIndex.toString(), redirectedBalance.toString(), - principalTokenBalance.toString(), + scaledATokenBalance.toString(), redirectionAddressRedirectedBalance.toString(), interestRedirectionAddress, + interestRedirectionIndex ]; }; diff --git a/test/helpers/utils/interfaces/index.ts b/test/helpers/utils/interfaces/index.ts index aa84ad3d..d64d8457 100644 --- a/test/helpers/utils/interfaces/index.ts +++ b/test/helpers/utils/interfaces/index.ts @@ -1,10 +1,10 @@ import BigNumber from 'bignumber.js'; export interface UserReserveData { - principalATokenBalance: BigNumber; + scaledATokenBalance: BigNumber; currentATokenBalance: BigNumber; - currentATokenUserIndex: BigNumber; interestRedirectionAddress: string; + interestRedirectionIndex: BigNumber; redirectionAddressRedirectedBalance: BigNumber; redirectedBalance: BigNumber; currentStableDebt: BigNumber; diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index 5d449d76..ff6382bd 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); const scenarioFolder = './test/helpers/scenarios/'; -const selectedScenarios: string[] = []; +const selectedScenarios: string[] = ['deposit.json']; fs.readdirSync(scenarioFolder).forEach((file) => { if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return; From e3c422468d6c996e62ff64b555c07e635a776a49 Mon Sep 17 00:00:00 2001 From: The3D Date: Tue, 8 Sep 2020 13:48:33 +0200 Subject: [PATCH 06/31] Updated comment --- contracts/libraries/helpers/Errors.sol | 5 +++-- test/scenario.spec.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index 796ff62d..0f0f4789 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -46,8 +46,9 @@ library Errors { string public constant TRANSFER_AMOUNT_NOT_GT_0 = '31'; // 'Transferred amount needs to be greater than zero' string public constant INTEREST_ALREADY_REDIRECTED = '32'; // 'Interest is already redirected to the user' string public constant NO_VALID_BALANCE_FOR_REDIRECTION = '33'; // 'Interest stream can only be redirected if there is a valid balance' - string public constant INVALID_ATOKEN_BALANCE = '52'; // burn balannce not valid - // require error messages - ReserveLogic + 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' string public constant LIQUIDITY_INDEX_OVERFLOW = '47'; // Liquidity index overflows uint128 string public constant VARIABLE_BORROW_INDEX_OVERFLOW = '48'; // Variable borrow index overflows uint128 diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index ff6382bd..471bb9ab 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); const scenarioFolder = './test/helpers/scenarios/'; -const selectedScenarios: string[] = ['deposit.json']; +const selectedScenarios: string[] = ['withdraw.json']; fs.readdirSync(scenarioFolder).forEach((file) => { if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return; From 3aa0dbc570be1b76b8a13e97951f0d9099051908 Mon Sep 17 00:00:00 2001 From: eboado Date: Tue, 8 Sep 2020 15:05:53 +0200 Subject: [PATCH 07/31] - Added tests of repayWithCollateral(), only for self-liquidation. --- contracts/interfaces/ISwapAdapter.sol | 21 + .../LendingPoolLiquidationManager.sol | 14 +- contracts/mocks/flashloan/MockSwapAdapter.sol | 43 ++ helpers/contracts-helpers.ts | 15 + helpers/types.ts | 1 + package.json | 1 + test/__setup.spec.ts | 4 + test/helpers/actions.ts | 2 +- test/helpers/make-suite.ts | 6 + test/repay-with-collateral.spec.ts | 471 ++++++++++++++++++ 10 files changed, 571 insertions(+), 7 deletions(-) create mode 100644 contracts/interfaces/ISwapAdapter.sol create mode 100644 contracts/mocks/flashloan/MockSwapAdapter.sol create mode 100644 test/repay-with-collateral.spec.ts diff --git a/contracts/interfaces/ISwapAdapter.sol b/contracts/interfaces/ISwapAdapter.sol new file mode 100644 index 00000000..c51ee0fd --- /dev/null +++ b/contracts/interfaces/ISwapAdapter.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface ISwapAdapter { + + /** + * @dev Swaps an `amountToSwap` of an asset to another, approving a `fundsDestination` to pull the funds + * @param assetToSwapFrom Origin asset + * @param assetToSwapTo Destination asset + * @param amountToSwap How much `assetToSwapFrom` needs to be swapped + * @param fundsDestination Address that will be pulling the swapped funds + * @param params Additional variadic field to include extra params + */ + function executeOperation( + address assetToSwapFrom, + address assetToSwapTo, + uint256 amountToSwap, + address fundsDestination, + bytes calldata params + ) external; +} \ No newline at end of file diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 7bbf149b..b79bfed7 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -21,7 +21,7 @@ import {Helpers} from '../libraries/helpers/Helpers.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol'; -import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; +import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol'; /** * @title LendingPoolLiquidationManager contract @@ -393,12 +393,14 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate); + address principalAToken = debtReserve.aTokenAddress; + // Notifies the receiver to proceed, sending as param the underlying already transferred - IFlashLoanReceiver(receiver).executeOperation( + ISwapAdapter(receiver).executeOperation( collateral, - address(vars.collateralAtoken), + principal, vars.maxCollateralToLiquidate, - 0, + address(this), params ); @@ -406,11 +408,11 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl debtReserve.updateCumulativeIndexesAndTimestamp(); debtReserve.updateInterestRates( principal, - debtReserve.aTokenAddress, + principalAToken, vars.actualAmountToLiquidate, 0 ); - IERC20(principal).transferFrom(receiver, debtReserve.aTokenAddress, vars.actualAmountToLiquidate); + IERC20(principal).transferFrom(receiver, principalAToken, vars.actualAmountToLiquidate); if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( diff --git a/contracts/mocks/flashloan/MockSwapAdapter.sol b/contracts/mocks/flashloan/MockSwapAdapter.sol new file mode 100644 index 00000000..6b2f2330 --- /dev/null +++ b/contracts/mocks/flashloan/MockSwapAdapter.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import {MintableERC20} from '../tokens/MintableERC20.sol'; +import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; +import {ISwapAdapter} from '../../interfaces/ISwapAdapter.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +contract MockSwapAdapter is ISwapAdapter { + + uint256 amountToReturn; + ILendingPoolAddressesProvider public addressesProvider; + + event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); + + constructor(ILendingPoolAddressesProvider provider) public { + addressesProvider = provider; + } + + function setAmountToReturn(uint256 amount) public { + amountToReturn = amount; + } + + function executeOperation( + address assetToSwapFrom, + address assetToSwapTo, + uint256 amountToSwap, + address fundsDestination, + bytes calldata params + ) external override { + params; + IERC20(assetToSwapFrom).transfer(address(1), amountToSwap); // We don't want to keep funds here + MintableERC20(assetToSwapTo).mint(amountToReturn); + IERC20(assetToSwapTo).approve(fundsDestination, amountToReturn); + + emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, amountToReturn); + } + + function burnAsset(IERC20 asset, uint256 amount) public { + uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount; + asset.transfer(address(0), amountToBurn); + } +} \ No newline at end of file diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index b5b483d3..909676a1 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 { MockSwapAdapter } from '../types/MockSwapAdapter'; export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => { const currentNetwork = BRE.network.name; @@ -212,6 +213,11 @@ export const deployMockFlashLoanReceiver = async (addressesProvider: tEthereumAd addressesProvider, ]); +export const deployMockSwapAdapter = async (addressesProvider: tEthereumAddress) => + await deployContract(eContractid.MockSwapAdapter, [ + addressesProvider, + ]); + export const deployWalletBalancerProvider = async (addressesProvider: tEthereumAddress) => await deployContract(eContractid.WalletBalanceProvider, [ addressesProvider, @@ -387,6 +393,15 @@ export const getMockFlashLoanReceiver = async (address?: tEthereumAddress) => { ); }; +export const getMockSwapAdapter = async (address?: tEthereumAddress) => { + return await getContract( + eContractid.MockSwapAdapter, + address || + (await getDb().get(`${eContractid.MockSwapAdapter}.${BRE.network.name}`).value()) + .address + ); +}; + export const getLendingRateOracle = async (address?: tEthereumAddress) => { return await getContract( eContractid.LendingRateOracle, diff --git a/helpers/types.ts b/helpers/types.ts index 106e5376..68ec503d 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -32,6 +32,7 @@ export enum eContractid { LendingPoolLiquidationManager = 'LendingPoolLiquidationManager', InitializableAdminUpgradeabilityProxy = 'InitializableAdminUpgradeabilityProxy', MockFlashLoanReceiver = 'MockFlashLoanReceiver', + MockSwapAdapter = 'MockSwapAdapter', WalletBalanceProvider = 'WalletBalanceProvider', AToken = 'AToken', MockAToken = 'MockAToken', diff --git a/package.json b/package.json index 34e56bfa..94ec647c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'", "test": "buidler test", "test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts", + "test-repay-with-collateral": "buidler test test/__setup.spec.ts test/repay-with-collateral.spec.ts", "dev:coverage": "buidler coverage", "dev:deployment": "buidler dev-deployment", "dev:deployExample": "buidler deploy-Example", diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index 342b7b64..689ecf3a 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -23,6 +23,7 @@ import { deployStableDebtToken, deployVariableDebtToken, deployGenericAToken, + deployMockSwapAdapter, } from '../helpers/contracts-helpers'; import {LendingPoolAddressesProvider} from '../types/LendingPoolAddressesProvider'; import {ContractTransaction, Signer} from 'ethers'; @@ -503,6 +504,9 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address); await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address); + const mockSwapAdapter = await deployMockSwapAdapter(addressesProvider.address) + await insertContractAddressInDb(eContractid.MockSwapAdapter, mockSwapAdapter.address) + await deployWalletBalancerProvider(addressesProvider.address); const testHelpers = await deployAaveProtocolTestHelpers(addressesProvider.address); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index be0daac5..4442b052 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -845,7 +845,7 @@ const getTxCostAndTimestamp = async (tx: ContractReceipt) => { return {txCost, txTimestamp}; }; -const getContractsData = async (reserve: string, user: string, testEnv: TestEnv) => { +export const getContractsData = async (reserve: string, user: string, testEnv: TestEnv) => { const {pool} = testEnv; const reserveData = await getReserveData(pool, reserve); const userData = await getUserData(pool, reserve, user); diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts index e6e5df98..a0787a17 100644 --- a/test/helpers/make-suite.ts +++ b/test/helpers/make-suite.ts @@ -9,6 +9,7 @@ import { getMintableErc20, getLendingPoolConfiguratorProxy, getPriceOracle, + getMockSwapAdapter, } from '../../helpers/contracts-helpers'; import {tEthereumAddress} from '../../helpers/types'; import {LendingPool} from '../../types/LendingPool'; @@ -23,6 +24,7 @@ import bignumberChai from 'chai-bignumber'; import {almostEqual} from './almost-equal'; import {PriceOracle} from '../../types/PriceOracle'; import {LendingPoolAddressesProvider} from '../../types/LendingPoolAddressesProvider'; +import { MockSwapAdapter } from '../../types/MockSwapAdapter'; chai.use(bignumberChai()); chai.use(almostEqual()); @@ -44,6 +46,7 @@ export interface TestEnv { usdc: MintableErc20; lend: MintableErc20; addressesProvider: LendingPoolAddressesProvider; + mockSwapAdapter: MockSwapAdapter; } let buidlerevmSnapshotId: string = '0x1'; @@ -67,6 +70,7 @@ const testEnv: TestEnv = { usdc: {} as MintableErc20, lend: {} as MintableErc20, addressesProvider: {} as LendingPoolAddressesProvider, + mockSwapAdapter: {} as MockSwapAdapter } as TestEnv; export async function initializeMakeSuite() { @@ -125,6 +129,8 @@ export async function initializeMakeSuite() { testEnv.usdc = await getMintableErc20(usdcAddress); testEnv.lend = await getMintableErc20(lendAddress); testEnv.weth = await getMintableErc20(wethAddress); + + testEnv.mockSwapAdapter = await getMockSwapAdapter() } export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) { diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts new file mode 100644 index 00000000..c22dd7a7 --- /dev/null +++ b/test/repay-with-collateral.spec.ts @@ -0,0 +1,471 @@ +import {TestEnv, makeSuite} from './helpers/make-suite'; +import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants'; +import {ethers} from 'ethers'; +import BigNumber from 'bignumber.js'; +import { + calcExpectedVariableDebtTokenBalance, + calcExpectedStableDebtTokenBalance, +} from './helpers/utils/calculations'; +import {getContractsData} from './helpers/actions'; +import {waitForTx} from './__setup.spec'; +import {timeLatest} from '../helpers/misc-utils'; +import {tEthereumAddress} from '../helpers/types'; + +const {expect} = require('chai'); +const {parseUnits, parseEther} = ethers.utils; + +const expectRepayWithCollateralEvent = ( + events: ethers.Event[], + pool: tEthereumAddress, + collateral: tEthereumAddress, + borrowing: tEthereumAddress, + user: tEthereumAddress +) => { + if (!events || events.length < 14) { + expect(false, 'INVALID_EVENTS_LENGTH_ON_REPAY_COLLATERAL'); + } + + const repayWithCollateralEvent = events[13]; + + expect(repayWithCollateralEvent.address).to.be.equal(pool); + expect(`0x${repayWithCollateralEvent.topics[1].slice(26)}`.toLowerCase()).to.be.equal( + collateral.toLowerCase() + ); + expect(`0x${repayWithCollateralEvent.topics[2].slice(26)}`).to.be.equal(borrowing.toLowerCase()); + expect(`0x${repayWithCollateralEvent.topics[3].slice(26)}`.toLowerCase()).to.be.equal( + user.toLowerCase() + ); +}; + +makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { + it('User 1 provides some liquidity for others to borrow', async () => { + const {pool, weth, dai, usdc} = testEnv; + + await weth.mint(parseEther('200')); + await weth.approve(pool.address, parseEther('200')); + await pool.deposit(weth.address, parseEther('200'), 0); + await dai.mint(parseEther('20000')); + await dai.approve(pool.address, parseEther('20000')); + await pool.deposit(dai.address, parseEther('20000'), 0); + await usdc.mint(parseEther('20000')); + await usdc.approve(pool.address, parseEther('20000')); + await pool.deposit(usdc.address, parseEther('20000'), 0); + }); + + it('User 2 deposit WETH and borrows DAI at Variable', async () => { + const {pool, weth, dai, users} = testEnv; + const user = users[1]; + const amountToDeposit = ethers.utils.parseEther('1'); + const amountToBorrow = ethers.utils.parseEther('20'); + + await weth.connect(user.signer).mint(amountToDeposit); + + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0); + }); + + it('User 2 tries to repay his DAI Variable loan using his WETH collateral. First half the amount, after that, the rest', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[1]; + + const amountToRepay = parseEther('10'); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(dai.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + daiReserveDataBefore, + daiUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(daiUserDataBefore.currentVariableDebt); + + expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(daiUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString() + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ) + ); + }); + + it('User 3 deposits WETH and borrows USDC at Variable', async () => { + const {pool, weth, usdc, users} = testEnv; + const user = users[2]; + const amountToDeposit = parseEther('10'); + const amountToBorrow = parseUnits('40', 6); + + await weth.connect(user.signer).mint(amountToDeposit); + + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0); + }); + + it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[2]; + + const amountToRepay = parseUnits('10', 6); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString(), + 'INVALID_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + }); + + it('User tries to repay with his collateral a currency he havent borrow', async () => { + const {pool, weth, dai, users, mockSwapAdapter} = testEnv; + const user = users[2]; + + const amountToRepay = parseUnits('10', 6); + + await expect( + pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ).to.be.revertedWith('revert CURRRENCY_NOT_BORROWED'); + }); + + it('User tries to repay with his collateral all his variable debt and part of the stable', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[2]; + + const amountToDeposit = parseEther('20'); + const amountToBorrowStable = parseUnits('40', 6); + const amountToBorrowVariable = parseUnits('40', 6); + + await weth.connect(user.signer).mint(amountToDeposit); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0); + + const amountToRepay = parseUnits('80', 6); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + const txReceipt = await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance( + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentStableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .gte(0) + ? new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString() + : '0', + 'INVALID_VARIABLE_DEBT_POSITION' + ); + + const stableDebtRepaid = new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .abs(); + + expect(usdcUserDataAfter.currentStableDebt).to.be.bignumber.equal( + new BigNumber(usdcUserDataBefore.currentStableDebt) + .minus(stableDebtRepaid) + .plus(expectedStableDebtIncrease) + .gte(0) + ? new BigNumber(usdcUserDataBefore.currentStableDebt) + .minus(stableDebtRepaid) + .plus(expectedStableDebtIncrease) + .toString() + : '0', + 'INVALID_STABLE_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + + const eventsEmitted = txReceipt.events || []; + + expectRepayWithCollateralEvent( + eventsEmitted, + pool.address, + weth.address, + usdc.address, + user.address + ); + }); + + // WIP + it('User tries to repay a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[3]; + + const amountToDepositWeth = parseEther('0.1'); + const amountToDepositDAI = parseEther('500'); + const amountToBorrowVariable = parseEther('80'); + + await weth.connect(user.signer).mint(amountToDepositWeth); + await dai.connect(user.signer).mint(amountToDepositDAI); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, '0'); + await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, '0'); + + await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0); + + const amountToRepay = parseEther('80'); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + console.log('BEFORE'); + console.log(wethUserDataBefore.currentATokenBalance.toString()); + console.log(daiUserDataBefore.currentVariableDebt.toString()); + console.log(daiUserDataBefore.currentStableDebt.toString()); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + const txReceipt = await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + + console.log('AFTER'); + console.log(wethUserDataAfter.currentATokenBalance.toString()); + console.log(daiUserDataAfter.currentVariableDebt.toString()); + console.log(daiUserDataAfter.currentStableDebt.toString()); + }); +}); From 2e8f6ee02ce52d3bcbbb35db27094faf355a20f6 Mon Sep 17 00:00:00 2001 From: The3D Date: Tue, 8 Sep 2020 16:14:32 +0200 Subject: [PATCH 08/31] Updating withdrawal tests --- contracts/tokenization/AToken.sol | 7 ++++++- test/helpers/utils/calculations.ts | 19 +++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 3e06d52a..ce12db28 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -11,6 +11,8 @@ import { import {IAToken} from './interfaces/IAToken.sol'; import {IERC20} from '../interfaces/IERC20.sol'; import {SafeERC20} from "../misc/SafeERC20.sol"; +import "@nomiclabs/buidler/console.sol"; + /** * @title Aave ERC20 AToken @@ -118,7 +120,9 @@ contract AToken is VersionedInitializable, ERC20, IAToken { uint256 currentBalance = balanceOf(user); - require(currentBalance <= amount, Errors.INVALID_ATOKEN_BALANCE); + console.log("Amount is %s, balance is %s", amount, currentBalance); + + require(amount <= currentBalance, Errors.INVALID_ATOKEN_BALANCE); uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); @@ -240,6 +244,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return _interestRedirectionIndexes[user]; } + /** * @dev calculates the total supply of the specific aToken * since the balance of every single user increases over time, the total supply diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index d14a0eb6..f66117d9 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -65,8 +65,6 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited); expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(reserveDataAfterAction, userDataBeforeAction, new BigNumber(amountDeposited), new BigNumber(0)); - - expectedUserData.currentATokenBalance = calcExpectedATokenBalance( reserveDataBeforeAction, userDataBeforeAction, @@ -121,23 +119,26 @@ export const calcExpectedUserDataAfterWithdraw = ( amountWithdrawn = aTokenBalance.toFixed(0); } - expectedUserData.principalATokenBalance = expectedUserData.currentATokenBalance = aTokenBalance.minus( + expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(reserveDataAfterAction, userDataBeforeAction, new BigNumber(0), new BigNumber(amountWithdrawn)); + + expectedUserData.currentATokenBalance = aTokenBalance.minus( amountWithdrawn ); - expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( + expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; + expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; + + expectedUserData.currentStableDebt = calcExpectedStableDebtTokenBalance( userDataBeforeAction, txTimestamp ); - expectedUserData.currentVariableDebt = expectedUserData.principalStableDebt = calcExpectedVariableDebtTokenBalance( + expectedUserData.currentVariableDebt = calcExpectedVariableDebtTokenBalance( reserveDataBeforeAction, userDataBeforeAction, txTimestamp ); - expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; - expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate; expectedUserData.stableRateLastUpdated = userDataBeforeAction.stableRateLastUpdated; @@ -155,9 +156,7 @@ export const calcExpectedUserDataAfterWithdraw = ( } } - expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountWithdrawn); - expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.redirectedBalance.eq(0)) { @@ -1038,7 +1037,7 @@ const calcExpectedScaledATokenBalance = ( amountTaken: BigNumber ) => { - return userDataBeforeAction.scaledATokenBalance.plus(amountAdded.rayDiv(reserveDataAfterAction.liquidityIndex)); + return userDataBeforeAction.scaledATokenBalance.plus(amountAdded.rayDiv(reserveDataAfterAction.liquidityIndex)).minus(amountTaken.rayDiv(reserveDataAfterAction.liquidityIndex)); } const calcExpectedATokenBalance = ( From 56ddeceb942dd6955aca52bf0ed7dbb817149135 Mon Sep 17 00:00:00 2001 From: eboado Date: Tue, 8 Sep 2020 16:25:16 +0200 Subject: [PATCH 09/31] - Added extra test of repayWithCollateral() on self-liquidation. --- test/repay-with-collateral.spec.ts | 52 ++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts index c22dd7a7..0bd25680 100644 --- a/test/repay-with-collateral.spec.ts +++ b/test/repay-with-collateral.spec.ts @@ -247,7 +247,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { ); }); - it('User tries to repay with his collateral a currency he havent borrow', async () => { + it('Revert expected. User 3 tries to repay with his collateral a currency he havent borrow', async () => { const {pool, weth, dai, users, mockSwapAdapter} = testEnv; const user = users[2]; @@ -267,7 +267,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { ).to.be.revertedWith('revert CURRRENCY_NOT_BORROWED'); }); - it('User tries to repay with his collateral all his variable debt and part of the stable', async () => { + it('User 3 tries to repay with his collateral all his variable debt and part of the stable', async () => { const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; const user = users[2]; @@ -402,8 +402,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { ); }); - // WIP - it('User tries to repay a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { + it('User 4 tries to repay a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; const user = users[3]; @@ -435,13 +434,8 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { testEnv ); - console.log('BEFORE'); - console.log(wethUserDataBefore.currentATokenBalance.toString()); - console.log(daiUserDataBefore.currentVariableDebt.toString()); - console.log(daiUserDataBefore.currentStableDebt.toString()); - await mockSwapAdapter.setAmountToReturn(amountToRepay); - const txReceipt = await waitForTx( + await waitForTx( await pool .connect(user.signer) .repayWithCollateral( @@ -463,9 +457,39 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); - console.log('AFTER'); - console.log(wethUserDataAfter.currentATokenBalance.toString()); - console.log(daiUserDataAfter.currentVariableDebt.toString()); - console.log(daiUserDataAfter.currentStableDebt.toString()); + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + + const collateralConfig = await pool.getReserveConfigurationData(weth.address); + + const collateralDecimals = collateralConfig.decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(dai.address) + ).decimals.toString(); + const collateralLiquidationBonus = collateralConfig.liquidationBonus.toString(); + + const expectedDebtCovered = new BigNumber(collateralPrice.toString()) + .times(new BigNumber(wethUserDataBefore.currentATokenBalance.toString())) + .times(new BigNumber(10).pow(principalDecimals)) + .div( + new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) + ) + .div(new BigNumber(collateralLiquidationBonus).div(10000).toString()) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + daiReserveDataBefore, + daiUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(daiUserDataBefore.currentVariableDebt); + + expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + new BigNumber(daiUserDataBefore.currentVariableDebt) + .minus(expectedDebtCovered.toString()) + .plus(expectedVariableDebtIncrease), + 'INVALID_VARIABLE_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0); }); }); From 863d888be600a0ce62d0b3e9defcd51e5cbe4445 Mon Sep 17 00:00:00 2001 From: David Racero Date: Tue, 8 Sep 2020 20:06:28 +0200 Subject: [PATCH 10/31] Added flash-liquidation tests, mimics self-liquidation tests --- .../flash-liquidation-with-collateral.spec.ts | 695 ++++++++++++++++++ 1 file changed, 695 insertions(+) create mode 100644 test/flash-liquidation-with-collateral.spec.ts diff --git a/test/flash-liquidation-with-collateral.spec.ts b/test/flash-liquidation-with-collateral.spec.ts new file mode 100644 index 00000000..5dd2122f --- /dev/null +++ b/test/flash-liquidation-with-collateral.spec.ts @@ -0,0 +1,695 @@ +import {TestEnv, makeSuite} from './helpers/make-suite'; +import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants'; +import {ethers} from 'ethers'; +import BigNumber from 'bignumber.js'; +import { + calcExpectedVariableDebtTokenBalance, + calcExpectedStableDebtTokenBalance, +} from './helpers/utils/calculations'; +import {getContractsData} from './helpers/actions'; +import {waitForTx} from './__setup.spec'; +import {timeLatest} from '../helpers/misc-utils'; +import {tEthereumAddress, ProtocolErrors} from '../helpers/types'; +import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; + +const {expect} = require('chai'); +const {parseUnits, parseEther} = ethers.utils; + +const expectRepayWithCollateralEvent = ( + events: ethers.Event[], + pool: tEthereumAddress, + collateral: tEthereumAddress, + borrowing: tEthereumAddress, + user: tEthereumAddress +) => { + if (!events || events.length < 14) { + expect(false, 'INVALID_EVENTS_LENGTH_ON_REPAY_COLLATERAL'); + } + + const repayWithCollateralEvent = events[13]; + + expect(repayWithCollateralEvent.address).to.be.equal(pool); + expect(`0x${repayWithCollateralEvent.topics[1].slice(26)}`.toLowerCase()).to.be.equal( + collateral.toLowerCase() + ); + expect(`0x${repayWithCollateralEvent.topics[2].slice(26)}`).to.be.equal(borrowing.toLowerCase()); + expect(`0x${repayWithCollateralEvent.topics[3].slice(26)}`.toLowerCase()).to.be.equal( + user.toLowerCase() + ); +}; + +makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEnv) => { + const {INVALID_HF} = ProtocolErrors; + + it('User 1 provides some liquidity for others to borrow', async () => { + const {pool, weth, dai, usdc} = testEnv; + + await weth.mint(parseEther('200')); + await weth.approve(pool.address, parseEther('200')); + await pool.deposit(weth.address, parseEther('200'), 0); + await dai.mint(parseEther('20000')); + await dai.approve(pool.address, parseEther('20000')); + await pool.deposit(dai.address, parseEther('20000'), 0); + await usdc.mint(parseEther('20000')); + await usdc.approve(pool.address, parseEther('20000')); + await pool.deposit(usdc.address, parseEther('20000'), 0); + }); + + it('User 5 liquidate User 3 collateral, all his variable debt and part of the stable', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[2]; + const liquidator = users[4]; + const amountToDeposit = parseEther('20'); + const amountToBorrow = parseUnits('40', 6); + + await weth.connect(user.signer).mint(amountToDeposit); + + await weth.connect(user.signer).approve(pool.address, amountToDeposit); + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + const usdcPrice = await oracle.getAssetPrice(usdc.address); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + // Set HF below 1 + await oracle.setAssetPrice( + usdc.address, + new BigNumber(usdcPrice.toString()).multipliedBy(60).toFixed(0) + ); + const userGlobalDataPrior = await pool.getUserAccountData(user.address); + expect(userGlobalDataPrior.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF); + + const amountToRepay = parseUnits('80', 6); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + const txReceipt = await waitForTx( + await pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance( + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentStableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .gte(0) + ? new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString() + : '0', + 'INVALID_VARIABLE_DEBT_POSITION' + ); + + const stableDebtRepaid = new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .abs(); + + expect(usdcUserDataAfter.currentStableDebt).to.be.bignumber.equal( + new BigNumber(usdcUserDataBefore.currentStableDebt) + .minus(stableDebtRepaid) + .plus(expectedStableDebtIncrease) + .gte(0) + ? new BigNumber(usdcUserDataBefore.currentStableDebt) + .minus(stableDebtRepaid) + .plus(expectedStableDebtIncrease) + .toString() + : '0', + 'INVALID_STABLE_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + + const eventsEmitted = txReceipt.events || []; + + expectRepayWithCollateralEvent( + eventsEmitted, + pool.address, + weth.address, + usdc.address, + user.address + ); + // Resets USDC Price + await oracle.setAssetPrice(usdc.address, usdcPrice); + }); + + it('User 3 deposits WETH and borrows USDC at Variable', async () => { + const {pool, weth, usdc, users, oracle} = testEnv; + const user = users[2]; + const amountToDeposit = parseEther('10'); + + await weth.connect(user.signer).mint(amountToDeposit); + + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + const userGlobalData = await pool.getUserAccountData(user.address); + + const usdcPrice = await oracle.getAssetPrice(usdc.address); + + const amountUSDCToBorrow = await convertToCurrencyDecimals( + usdc.address, + new BigNumber(userGlobalData.availableBorrowsETH.toString()) + .div(usdcPrice.toString()) + .multipliedBy(0.95) + .toFixed(0) + ); + + await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 0); + }); + + it('User 5 liquidates half the USDC loan of User 3 by swapping his WETH collateral', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[2]; + const liquidator = users[4]; + // Sets USDC Price higher to decrease health factor below 1 + const usdcPrice = await oracle.getAssetPrice(usdc.address); + + await oracle.setAssetPrice( + usdc.address, + new BigNumber(usdcPrice.toString()).multipliedBy(1.15).toFixed(0) + ); + + const userGlobalData = await pool.getUserAccountData(user.address); + + expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + const amountToRepay = usdcReserveDataBefore.totalBorrowsVariable.dividedBy(2).toFixed(0); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString(), + 'INVALID_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + + // Resets USDC Price + await oracle.setAssetPrice(usdc.address, usdcPrice); + }); + + it('Revert expected. User 5 tries to liquidate an User 3 collateral a currency he havent borrow', async () => { + const {pool, weth, dai, users, oracle, mockSwapAdapter, usdc} = testEnv; + const user = users[2]; + const liquidator = users[4]; + + const amountToRepay = parseUnits('10', 6); + + // Sets USDC Price higher to decrease health factor below 1 + const usdcPrice = await oracle.getAssetPrice(usdc.address); + + await oracle.setAssetPrice( + usdc.address, + new BigNumber(usdcPrice.toString()).multipliedBy(6.4).toFixed(0) + ); + const userGlobalData = await pool.getUserAccountData(user.address); + + expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF); + + await expect( + pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ).to.be.revertedWith('revert CURRRENCY_NOT_BORROWED'); + + await oracle.setAssetPrice(usdc.address, usdcPrice); + }); + + it('User 2 deposit WETH and borrows DAI at Variable', async () => { + const {pool, weth, dai, users, oracle} = testEnv; + const user = users[1]; + const amountToDeposit = ethers.utils.parseEther('1'); + + await weth.connect(user.signer).mint(amountToDeposit); + + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + const userGlobalData = await pool.getUserAccountData(user.address); + + const daiPrice = await oracle.getAssetPrice(dai.address); + + const amountDAIToBorrow = await convertToCurrencyDecimals( + dai.address, + new BigNumber(userGlobalData.availableBorrowsETH.toString()) + .div(daiPrice.toString()) + .multipliedBy(0.95) + .toFixed(0) + ); + + await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0); + }); + it('User 5 tries to liquidate User 2 DAI Variable loan using his WETH collateral, with good HF', async () => { + const {pool, weth, dai, users, mockSwapAdapter} = testEnv; + const user = users[1]; + const liquidator = users[4]; + + const {reserveData: daiReserveDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + // First half + const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.dividedBy(2).toString(); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await expect( + pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ).to.be.revertedWith('HEALTH_FACTOR_ABOVE_THRESHOLD'); + }); + it('User 5 liquidates User 2 DAI Variable loan using his WETH collateral, half the amount', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[1]; + const liquidator = users[4]; + + // Sets DAI Price higher to decrease health factor below 1 + const daiPrice = await oracle.getAssetPrice(dai.address); + + await oracle.setAssetPrice( + dai.address, + new BigNumber(daiPrice.toString()).multipliedBy(1.4).toFixed(0) + ); + + const userGlobalData = await pool.getUserAccountData(user.address); + + expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + // First half + const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.dividedBy(2).toString(); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(dai.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + daiReserveDataBefore, + daiUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(daiUserDataBefore.currentVariableDebt); + + expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(daiUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString() + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ) + ); + // Resets DAI price + await oracle.setAssetPrice(dai.address, daiPrice); + }); + + it('User 2 tries to repay remaining DAI Variable loan using his WETH collateral', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[1]; + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + // Repay the remaining DAI + const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.toString(); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(dai.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + daiReserveDataBefore, + daiUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(daiUserDataBefore.currentVariableDebt); + + expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(daiUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString() + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ) + ); + }); + + it.skip('WIP Liquidator tries to repay 4 user a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[3]; + const liquidator = users[5]; + + const amountToDepositWeth = parseEther('0.1'); + const amountToDepositDAI = parseEther('500'); + const amountToBorrowVariable = parseEther('80'); + + await weth.connect(user.signer).mint(amountToDepositWeth); + await dai.connect(user.signer).mint(amountToDepositDAI); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, '0'); + await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, '0'); + + await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0); + + const amountToRepay = parseEther('80'); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + const wethPrice = await oracle.getAssetPrice(weth.address); + // Set HF below 1 + await oracle.setAssetPrice( + weth.address, + new BigNumber(wethPrice.toString()).multipliedBy(0.1).toFixed(0) + ); + const userGlobalDataPrior = await pool.getUserAccountData(user.address); + expect(userGlobalDataPrior.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF); + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + + const collateralConfig = await pool.getReserveConfigurationData(weth.address); + + const collateralDecimals = collateralConfig.decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(dai.address) + ).decimals.toString(); + const collateralLiquidationBonus = collateralConfig.liquidationBonus.toString(); + + const expectedDebtCovered = new BigNumber(collateralPrice.toString()) + .times(new BigNumber(wethUserDataBefore.currentATokenBalance.toString())) + .times(new BigNumber(10).pow(principalDecimals)) + .div( + new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) + ) + .div(new BigNumber(collateralLiquidationBonus).div(10000).toString()) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + daiReserveDataBefore, + daiUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(daiUserDataBefore.currentVariableDebt); + + expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + new BigNumber(daiUserDataBefore.currentVariableDebt) + .minus(expectedDebtCovered.toString()) + .plus(expectedVariableDebtIncrease), + 'INVALID_VARIABLE_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0); + // Resets WETH Price + await oracle.setAssetPrice(weth.address, wethPrice); + }); +}); From 90de5d2b0fdbea8a4f7027d0b7093609b6a906c3 Mon Sep 17 00:00:00 2001 From: The3D Date: Wed, 9 Sep 2020 10:03:19 +0200 Subject: [PATCH 11/31] Fixed transfer tests --- contracts/tokenization/AToken.sol | 17 ++++++++++++++++- test/atoken-transfer.spec.ts | 8 -------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index ce12db28..0b0d7473 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -229,7 +229,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @param user the address of the user * @return the scaled balance of the user **/ - function scaledBalanceOf(address user) external override view returns (uint256) { + function scaledBalanceOf(address user) public override view returns (uint256) { return super.balanceOf(user); } @@ -444,6 +444,10 @@ contract AToken is VersionedInitializable, ERC20, IAToken { uint256 scaledAmount = amount.rayDiv(index); + console.log("scaled balanceOf from: %s", scaledBalanceOf(from)); + console.log("scaled balanceOf to: %s", scaledBalanceOf(to)); + console.log("scaled amount: %s", scaledAmount); + super._transfer(from, to, scaledAmount); //if the sender is redirecting his interest towards someone else, @@ -460,6 +464,17 @@ contract AToken is VersionedInitializable, ERC20, IAToken { } + /** + * @notice ERC20 implementation internal function backing transfer() and transferFrom() + **/ + function _transfer( + address from, + address to, + uint256 amount + ) internal override { + + _transfer(from, to, amount, true); + } /** * @dev aTokens should not receive ETH **/ diff --git a/test/atoken-transfer.spec.ts b/test/atoken-transfer.spec.ts index 1c161608..84cfb6b2 100644 --- a/test/atoken-transfer.spec.ts +++ b/test/atoken-transfer.spec.ts @@ -124,14 +124,6 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { ).to.be.revertedWith(TRANSFER_NOT_ALLOWED); }); - it('User 0 tries to transfer 0 balance (revert expected)', async () => { - const {users, pool, aDai, dai, weth} = testEnv; - await expect( - aDai.connect(users[0].signer).transfer(users[1].address, '0'), - TRANSFER_AMOUNT_NOT_GT_0 - ).to.be.revertedWith(TRANSFER_AMOUNT_NOT_GT_0); - }); - it('User 1 repays the borrow, transfers aDAI back to user 0', async () => { const {users, pool, aDai, dai, weth} = testEnv; From 6454f040e8f0d6d7c4b94feee2be094095c5bd7a Mon Sep 17 00:00:00 2001 From: The3D Date: Wed, 9 Sep 2020 10:44:34 +0200 Subject: [PATCH 12/31] Fixed withdrawal tests --- contracts/tokenization/AToken.sol | 4 ---- test/helpers/actions.ts | 11 +++++++---- test/helpers/utils/calculations.ts | 11 ++++++++--- test/scenario.spec.ts | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 0b0d7473..ad7d1b07 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -444,10 +444,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { uint256 scaledAmount = amount.rayDiv(index); - console.log("scaled balanceOf from: %s", scaledBalanceOf(from)); - console.log("scaled balanceOf to: %s", scaledBalanceOf(to)); - console.log("scaled amount: %s", scaledAmount); - super._transfer(from, to, scaledAmount); //if the sender is redirecting his interest towards someone else, diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index be0daac5..e5d6efa6 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -56,16 +56,19 @@ const almostEqualOrEqual = function ( return; } + this.assert(actual[key] != undefined, `Property ${key} is undefined in the actual data`); expect(expected[key] != undefined, `Property ${key} is undefined in the expected data`); + if (!expected[key] || !actual[key]) { + console.log('Found a undefined value for Key ', key, ' value ', expected[key], actual[key]); + } + if (actual[key] instanceof BigNumber) { - if (!expected[key]) { - console.log('Key ', key, ' value ', expected[key], actual[key]); - } + const actualValue = (actual[key]).decimalPlaces(0, BigNumber.ROUND_DOWN); const expectedValue = (expected[key]).decimalPlaces(0, BigNumber.ROUND_DOWN); - + this.assert( actualValue.eq(expectedValue) || actualValue.plus(1).eq(expectedValue) || diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index f66117d9..fcdece3e 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -120,7 +120,9 @@ export const calcExpectedUserDataAfterWithdraw = ( } expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(reserveDataAfterAction, userDataBeforeAction, new BigNumber(0), new BigNumber(amountWithdrawn)); - + + console.log("Scaled balance is ", expectedUserData.scaledATokenBalance.toFixed()); + expectedUserData.currentATokenBalance = aTokenBalance.minus( amountWithdrawn ); @@ -161,8 +163,10 @@ export const calcExpectedUserDataAfterWithdraw = ( if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.redirectedBalance.eq(0)) { expectedUserData.interestRedirectionAddress = ZERO_ADDRESS; + expectedUserData.interestRedirectionIndex = new BigNumber(0); } else { expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; + expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS ? new BigNumber(0) : reserveDataAfterAction.liquidityIndex; } expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( @@ -172,7 +176,7 @@ export const calcExpectedUserDataAfterWithdraw = ( new BigNumber(0), new BigNumber(amountWithdrawn) ); - + return expectedUserData; }; @@ -564,9 +568,10 @@ export const calcExpectedUserDataAfterBorrow = ( userDataBeforeAction, currentTimestamp ); - expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance; + expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; + expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex; expectedUserData.redirectionAddressRedirectedBalance = userDataBeforeAction.redirectionAddressRedirectedBalance; expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex; diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index 471bb9ab..5d449d76 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); const scenarioFolder = './test/helpers/scenarios/'; -const selectedScenarios: string[] = ['withdraw.json']; +const selectedScenarios: string[] = []; fs.readdirSync(scenarioFolder).forEach((file) => { if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return; From b0084aaf338c2d97118501d8b7d5c76f263725a7 Mon Sep 17 00:00:00 2001 From: The3D Date: Wed, 9 Sep 2020 11:43:11 +0200 Subject: [PATCH 13/31] Fixes borrow, repay, swap rate mode, rebalance tests --- contracts/tokenization/AToken.sol | 2 - test/helpers/actions.ts | 2 +- test/helpers/utils/calculations.ts | 106 ++++++++++++----------------- test/scenario.spec.ts | 2 +- 4 files changed, 47 insertions(+), 65 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index ad7d1b07..5f308d76 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -120,8 +120,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { uint256 currentBalance = balanceOf(user); - console.log("Amount is %s, balance is %s", amount, currentBalance); - require(amount <= currentBalance, Errors.INVALID_ATOKEN_BALANCE); uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index e5d6efa6..616e8ffa 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -60,7 +60,7 @@ const almostEqualOrEqual = function ( this.assert(actual[key] != undefined, `Property ${key} is undefined in the actual data`); expect(expected[key] != undefined, `Property ${key} is undefined in the expected data`); - if (!expected[key] || !actual[key]) { + if (expected[key] == null || !actual[key] == null) { console.log('Found a undefined value for Key ', key, ' value ', expected[key], actual[key]); } diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index fcdece3e..c45ad951 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -49,32 +49,33 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.liquidityRate = reserveDataAfterAction.liquidityRate; - - if (userDataBeforeAction.currentATokenBalance.eq(0)) { - expectedUserData.usageAsCollateralEnabled = true; - } else { - //if the user is withdrawing everything, usageAsCollateralEnabled must be false - if (expectedUserData.currentATokenBalance.eq(0)) { - expectedUserData.usageAsCollateralEnabled = false; - } else { - expectedUserData.usageAsCollateralEnabled = userDataBeforeAction.usageAsCollateralEnabled; - } - } - - expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; - expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited); - - expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(reserveDataAfterAction, userDataBeforeAction, new BigNumber(amountDeposited), new BigNumber(0)); + expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance( + reserveDataAfterAction, + userDataBeforeAction, + new BigNumber(amountDeposited), + new BigNumber(0) + ); expectedUserData.currentATokenBalance = calcExpectedATokenBalance( reserveDataBeforeAction, userDataBeforeAction, txTimestamp ).plus(amountDeposited); + if (userDataBeforeAction.currentATokenBalance.eq(0)) { + expectedUserData.usageAsCollateralEnabled = true; + } else { + expectedUserData.usageAsCollateralEnabled = userDataBeforeAction.usageAsCollateralEnabled; + } + + expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; + expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited); + expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS ? new BigNumber(0) : reserveDataAfterAction.liquidityIndex; - + expectedUserData.interestRedirectionIndex = + userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS + ? new BigNumber(0) + : reserveDataAfterAction.liquidityIndex; expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( userDataBeforeAction, @@ -119,14 +120,15 @@ export const calcExpectedUserDataAfterWithdraw = ( amountWithdrawn = aTokenBalance.toFixed(0); } - expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(reserveDataAfterAction, userDataBeforeAction, new BigNumber(0), new BigNumber(amountWithdrawn)); - - console.log("Scaled balance is ", expectedUserData.scaledATokenBalance.toFixed()); - - expectedUserData.currentATokenBalance = aTokenBalance.minus( - amountWithdrawn + expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance( + reserveDataAfterAction, + userDataBeforeAction, + new BigNumber(0), + new BigNumber(amountWithdrawn) ); + expectedUserData.currentATokenBalance = aTokenBalance.minus(amountWithdrawn); + expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt; expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; @@ -166,7 +168,10 @@ export const calcExpectedUserDataAfterWithdraw = ( expectedUserData.interestRedirectionIndex = new BigNumber(0); } else { expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS ? new BigNumber(0) : reserveDataAfterAction.liquidityIndex; + expectedUserData.interestRedirectionIndex = + userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS + ? new BigNumber(0) + : reserveDataAfterAction.liquidityIndex; } expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( @@ -176,7 +181,7 @@ export const calcExpectedUserDataAfterWithdraw = ( new BigNumber(0), new BigNumber(amountWithdrawn) ); - + return expectedUserData; }; @@ -658,9 +663,10 @@ export const calcExpectedUserDataAfterRepay = ( userDataBeforeAction, txTimestamp ); - expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance; + expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; + expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex; expectedUserData.redirectionAddressRedirectedBalance = userDataBeforeAction.redirectionAddressRedirectedBalance; expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex; @@ -803,12 +809,6 @@ export const calcExpectedUserDataAfterSwapRateMode = ( txTimestamp ); - expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance; - expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; - expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.redirectionAddressRedirectedBalance = - userDataBeforeAction.redirectionAddressRedirectedBalance; - if (rateMode === RateMode.Stable) { // swap to variable expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = new BigNumber(0); @@ -954,12 +954,6 @@ export const calcExpectedUserDataAfterStableRateRebalance = ( userDataBeforeAction, txTimestamp ); - expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; - expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; - expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.redirectionAddressRedirectedBalance = - userDataBeforeAction.redirectionAddressRedirectedBalance; - return expectedUserData; }; @@ -994,9 +988,7 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( expectedFromData.stableBorrowRate = fromDataBeforeAction.stableBorrowRate; expectedToData.stableBorrowRate = toDataBeforeAction.stableBorrowRate; - expectedFromData.scaledATokenBalance = - - expectedFromData.currentATokenBalance = calcExpectedATokenBalance( + expectedFromData.scaledATokenBalance = expectedFromData.currentATokenBalance = calcExpectedATokenBalance( reserveDataBeforeAction, fromDataBeforeAction, txTimestamp @@ -1034,16 +1026,16 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( return [expectedFromData, expectedToData]; }; - const calcExpectedScaledATokenBalance = ( reserveDataAfterAction: ReserveData, userDataBeforeAction: UserReserveData, amountAdded: BigNumber, amountTaken: BigNumber - ) => { - return userDataBeforeAction.scaledATokenBalance.plus(amountAdded.rayDiv(reserveDataAfterAction.liquidityIndex)).minus(amountTaken.rayDiv(reserveDataAfterAction.liquidityIndex)); -} + return userDataBeforeAction.scaledATokenBalance + .plus(amountAdded.rayDiv(reserveDataAfterAction.liquidityIndex)) + .minus(amountTaken.rayDiv(reserveDataAfterAction.liquidityIndex)); +}; const calcExpectedATokenBalance = ( reserveDataBeforeAction: ReserveData, @@ -1062,21 +1054,14 @@ const calcExpectedATokenBalance = ( if (scaledBalanceBeforeAction.eq(0) && redirectedBalance.eq(0)) { return new BigNumber(0); } - - if (interestRedirectionAddress === ZERO_ADDRESS) { - return scaledBalanceBeforeAction - .plus(redirectedBalance) - .rayMul(index) - .minus(redirectedBalance); - } - const lastRedirectedBalance = scaledBalanceBeforeAction.rayDiv(redirectionIndexBeforeAction); - - return lastRedirectedBalance.plus( - redirectedBalance - .rayMul(index) - .minus(redirectedBalance) - ); + if (interestRedirectionAddress === ZERO_ADDRESS) { + return scaledBalanceBeforeAction.plus(redirectedBalance).rayMul(index).minus(redirectedBalance); + } + + const lastRedirectedBalance = scaledBalanceBeforeAction.rayDiv(redirectionIndexBeforeAction); + + return lastRedirectedBalance.plus(redirectedBalance.rayMul(index).minus(redirectedBalance)); }; const calcExpectedRedirectedBalance = ( @@ -1086,7 +1071,6 @@ const calcExpectedRedirectedBalance = ( amountToAdd: BigNumber, amountToSubstract: BigNumber ): BigNumber => { - return expectedUserDataAfterAction.interestRedirectionAddress !== ZERO_ADDRESS ? redirectedBalanceBefore.plus(amountToAdd).minus(amountToSubstract) : new BigNumber('0'); diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index 5d449d76..025bc720 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); const scenarioFolder = './test/helpers/scenarios/'; -const selectedScenarios: string[] = []; +const selectedScenarios: string[] = ['']; fs.readdirSync(scenarioFolder).forEach((file) => { if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return; From 9d7bf388a6afac723b313c499034c15ab8de5163 Mon Sep 17 00:00:00 2001 From: andyk Date: Wed, 9 Sep 2020 13:47:27 +0300 Subject: [PATCH 14/31] initial changes + test --- contracts/interfaces/ILendingPool.sol | 4 ++- contracts/lendingpool/LendingPool.sol | 33 +++++++++++++------------ package.json | 2 +- test/atoken-transfer.spec.ts | 9 +++++-- test/configurator.spec.ts | 4 +-- test/flashloan.spec.ts | 13 +++++----- test/helpers/actions.ts | 25 +++++++++++++------ test/helpers/scenario-engine.ts | 16 ++++++++++-- test/helpers/scenarios/deposit.json | 35 ++++++++++++++++++++++++++- test/helpers/utils/helpers.ts | 5 ++-- test/liquidation-atoken.spec.ts | 16 +++++++++--- test/liquidation-underlying.spec.ts | 20 +++++++++++---- 12 files changed, 132 insertions(+), 50 deletions(-) diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index a7a5e1ca..239787ed 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -15,7 +15,8 @@ interface ILendingPool { **/ event Deposit( address indexed reserve, - address indexed user, + address user, + address indexed onBehalfOf, uint256 amount, uint16 indexed referral ); @@ -139,6 +140,7 @@ interface ILendingPool { function deposit( address reserve, uint256 amount, + address onBehalfOf, uint16 referralCode ) external; diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 9e4e6707..a4f62ce8 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -89,6 +89,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { function deposit( address asset, uint256 amount, + address onBehalfOf, uint16 referralCode ) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; @@ -100,18 +101,18 @@ contract LendingPool is VersionedInitializable, ILendingPool { reserve.updateCumulativeIndexesAndTimestamp(); reserve.updateInterestRates(asset, aToken, amount, 0); - bool isFirstDeposit = IAToken(aToken).balanceOf(msg.sender) == 0; + bool isFirstDeposit = IAToken(aToken).balanceOf(onBehalfOf) == 0; if (isFirstDeposit) { - _usersConfig[msg.sender].setUsingAsCollateral(reserve.index, true); + _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.index, true); } //minting AToken to user 1:1 with the specific exchange rate - IAToken(aToken).mint(msg.sender, amount); + IAToken(aToken).mint(onBehalfOf, amount); //transfer to the aToken contract IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); - emit Deposit(asset, msg.sender, amount, referralCode); + emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode); } /** @@ -450,15 +451,13 @@ contract LendingPool is VersionedInitializable, ILendingPool { vars.amountPlusPremium = amount.add(vars.premium); if (debtMode == ReserveLogic.InterestRateMode.NONE) { - IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium); - + reserve.updateCumulativeIndexesAndTimestamp(); reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium); reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0); - - emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode); + emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode); } else { // If the transfer didn't succeed, the receiver either didn't return the funds, or didn't approve the transfer. _executeBorrow( @@ -728,13 +727,11 @@ contract LendingPool is VersionedInitializable, ILendingPool { oracle ); - uint256 reserveIndex = reserve.index; if (!userConfig.isBorrowing(reserveIndex)) { userConfig.setBorrowing(reserveIndex, true); } - reserve.updateCumulativeIndexesAndTimestamp(); //caching the current stable borrow rate @@ -754,13 +751,17 @@ contract LendingPool is VersionedInitializable, ILendingPool { IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.user, vars.amount); } - reserve.updateInterestRates(vars.asset, vars.aTokenAddress, 0, vars.releaseUnderlying ? vars.amount : 0); - - if(vars.releaseUnderlying){ - IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount); + reserve.updateInterestRates( + vars.asset, + vars.aTokenAddress, + 0, + vars.releaseUnderlying ? vars.amount : 0 + ); + + if (vars.releaseUnderlying) { + IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount); } - - + emit Borrow( vars.asset, msg.sender, diff --git a/package.json b/package.json index 606aaaf9..ab5f751a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'", "test": "buidler test", "test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts", - "test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts", + "test-flash": "buidler test test/__setup.spec.ts test/collateral-swap.spec.ts", "dev:coverage": "buidler coverage", "dev:deployment": "buidler dev-deployment", "dev:deployExample": "buidler deploy-Example", diff --git a/test/atoken-transfer.spec.ts b/test/atoken-transfer.spec.ts index 1c161608..bd113ce3 100644 --- a/test/atoken-transfer.spec.ts +++ b/test/atoken-transfer.spec.ts @@ -33,7 +33,9 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { //user 1 deposits 1000 DAI const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(users[0].signer).deposit(dai.address, amountDAItoDeposit, '0'); + await pool + .connect(users[0].signer) + .deposit(dai.address, amountDAItoDeposit, users[0].address, '0'); await aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit); @@ -94,12 +96,15 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { it('User 0 deposits 1 WETH and user 1 tries to borrow, but the aTokens received as a transfer are not available as collateral (revert expected)', async () => { const {users, pool, weth} = testEnv; + const userAddress = await pool.signer.getAddress(); await weth.connect(users[0].signer).mint(await convertToCurrencyDecimals(weth.address, '1')); await weth.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(users[0].signer).deposit(weth.address, ethers.utils.parseEther('1.0'), '0'); + await pool + .connect(users[0].signer) + .deposit(weth.address, ethers.utils.parseEther('1.0'), userAddress, '0'); await expect( pool .connect(users[1].signer) diff --git a/test/configurator.spec.ts b/test/configurator.spec.ts index a6513e54..6a85791c 100644 --- a/test/configurator.spec.ts +++ b/test/configurator.spec.ts @@ -234,7 +234,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { it('Reverts when trying to disable the DAI reserve with liquidity on it', async () => { const {dai, pool, configurator} = testEnv; - + const userAddress = await pool.signer.getAddress(); await dai.mint(await convertToCurrencyDecimals(dai.address, '1000')); //approve protocol to access depositor wallet @@ -242,7 +242,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); //user 1 deposits 1000 DAI - await pool.deposit(dai.address, amountDAItoDeposit, '0'); + await pool.deposit(dai.address, amountDAItoDeposit, userAddress, '0'); await expect( configurator.deactivateReserve(dai.address), diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 79db4bcb..724be7ef 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -30,13 +30,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { it('Deposits ETH into the reserve', async () => { const {pool, weth} = testEnv; + const userAddress = await pool.signer.getAddress(); const amountToDeposit = ethers.utils.parseEther('1'); await weth.mint(amountToDeposit); await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.deposit(weth.address, amountToDeposit, '0'); + await pool.deposit(weth.address, amountToDeposit, userAddress, '0'); }); it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => { @@ -143,7 +144,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0'); + await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0'); await _mockFlashLoanReceiver.setFailExecutionTransfer(true); @@ -210,6 +211,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { it('Deposits USDC into the reserve', async () => { const {usdc, pool} = testEnv; + const userAddress = await pool.signer.getAddress(); await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000')); @@ -217,7 +219,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); - await pool.deposit(usdc.address, amountToDeposit, '0'); + await pool.deposit(usdc.address, amountToDeposit, userAddress, '0'); }); it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => { @@ -284,7 +286,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(weth.address, '5'); - await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, '0'); + await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, caller.address, '0'); await _mockFlashLoanReceiver.setFailExecutionTransfer(true); @@ -307,7 +309,6 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { it('Caller deposits 1000 DAI as collateral, Takes a WETH flashloan with mode = 0, does not approve the transfer of the funds', async () => { const {dai, pool, weth, users} = testEnv; - const caller = users[3]; await dai.connect(caller.signer).mint(await convertToCurrencyDecimals(dai.address, '1000')); @@ -316,7 +317,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0'); + await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0'); const flashAmount = ethers.utils.parseEther('0.8'); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index be0daac5..76d4d907 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -133,7 +133,8 @@ export const approve = async (reserveSymbol: string, user: SignerWithAddress, te export const deposit = async ( reserveSymbol: string, amount: string, - user: SignerWithAddress, + sender: SignerWithAddress, + onBehalfOf: tEthereumAddress, sendValue: string, expectedResult: string, testEnv: TestEnv, @@ -149,8 +150,9 @@ export const deposit = async ( const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData( reserve, - user.address, - testEnv + onBehalfOf, + testEnv, + sender.address ); if (sendValue) { @@ -158,14 +160,16 @@ export const deposit = async ( } if (expectedResult === 'success') { const txResult = await waitForTx( - await await pool.connect(user.signer).deposit(reserve, amountToDeposit, '0', txOptions) + await pool + .connect(sender.signer) + .deposit(reserve, amountToDeposit, onBehalfOf, '0', txOptions) ); const { reserveData: reserveDataAfter, userData: userDataAfter, timestamp, - } = await getContractsData(reserve, user.address, testEnv); + } = await getContractsData(reserve, onBehalfOf, testEnv, sender.address); const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); @@ -198,7 +202,7 @@ export const deposit = async ( // }); } else if (expectedResult === 'revert') { await expect( - pool.connect(user.signer).deposit(reserve, amountToDeposit, '0', txOptions), + pool.connect(sender.signer).deposit(reserve, amountToDeposit, onBehalfOf, '0', txOptions), revertMessage ).to.be.reverted; } @@ -845,10 +849,15 @@ const getTxCostAndTimestamp = async (tx: ContractReceipt) => { return {txCost, txTimestamp}; }; -const getContractsData = async (reserve: string, user: string, testEnv: TestEnv) => { +const getContractsData = async ( + reserve: string, + user: string, + testEnv: TestEnv, + sender?: string +) => { const {pool} = testEnv; const reserveData = await getReserveData(pool, reserve); - const userData = await getUserData(pool, reserve, user); + const userData = await getUserData(pool, reserve, user, sender || user); const timestamp = await timeLatest(); return { diff --git a/test/helpers/scenario-engine.ts b/test/helpers/scenario-engine.ts index 735d3b84..260e87e5 100644 --- a/test/helpers/scenario-engine.ts +++ b/test/helpers/scenario-engine.ts @@ -92,13 +92,25 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv case 'deposit': { - const {amount, sendValue} = action.args; + const {amount, sendValue, onBehalfOf: onBehalfOfIndex} = action.args; + const onBehalfOf = onBehalfOfIndex + ? users[parseInt(onBehalfOfIndex)].address + : user.address; if (!amount || amount === '') { throw `Invalid amount to deposit into the ${reserve} reserve`; } - await deposit(reserve, amount, user, sendValue, expected, testEnv, revertMessage); + await deposit( + reserve, + amount, + user, + onBehalfOf, + sendValue, + expected, + testEnv, + revertMessage + ); } break; diff --git a/test/helpers/scenarios/deposit.json b/test/helpers/scenarios/deposit.json index 34f9c9e9..2456d931 100644 --- a/test/helpers/scenarios/deposit.json +++ b/test/helpers/scenarios/deposit.json @@ -206,7 +206,6 @@ "name": "deposit", "args": { "reserve": "WETH", - "amount": "0", "user": "1" }, @@ -229,6 +228,40 @@ "revertMessage": "Amount must be greater than 0" } ] + }, + { + "description": "User 1 deposits 100 DAI on behalf of user 2, user 2 tries to borrow 0.1 WETH", + "actions": [ + { + "name": "mint", + "args": { + "reserve": "DAI", + "amount": "100", + "user": "1" + }, + "expected": "success" + }, + { + "name": "deposit", + "args": { + "reserve": "DAI", + "amount": "100", + "user": "1", + "onBehalfOf": "2" + }, + "expected": "success" + }, + { + "name": "borrow", + "args": { + "reserve": "WETH", + "amount": "0.1", + "borrowRateMode": "variable", + "user": "2" + }, + "expected": "success" + } + ] } ] } diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index c20c67ac..9dac685b 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -61,7 +61,8 @@ export const getReserveData = async ( export const getUserData = async ( pool: LendingPool, reserve: string, - user: string + user: tEthereumAddress, + sender?: tEthereumAddress ): Promise => { const [userData, aTokenData] = await Promise.all([ pool.getUserReserveData(reserve, user), @@ -77,7 +78,7 @@ export const getUserData = async ( ] = aTokenData; const token = await getMintableErc20(reserve); - const walletBalance = new BigNumber((await token.balanceOf(user)).toString()); + const walletBalance = new BigNumber((await token.balanceOf(sender || user)).toString()); return { principalATokenBalance: new BigNumber(principalATokenBalance), diff --git a/test/liquidation-atoken.spec.ts b/test/liquidation-atoken.spec.ts index 921114f0..90293397 100644 --- a/test/liquidation-atoken.spec.ts +++ b/test/liquidation-atoken.spec.ts @@ -32,7 +32,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => //user 1 deposits 1000 DAI const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(depositor.signer).deposit(dai.address, amountDAItoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(dai.address, amountDAItoDeposit, depositor.address, '0'); const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -43,7 +45,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); //user 2 deposits 1 WETH - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //user 2 borrows const userGlobalData = await pool.getUserAccountData(borrower.address); @@ -224,7 +228,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => //user 3 deposits 1000 USDC const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); - await pool.connect(depositor.signer).deposit(usdc.address, amountUSDCtoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0'); //user 4 deposits 1 ETH const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -235,7 +241,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => //approve protocol to access borrower wallet await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //user 4 borrows const userGlobalData = await pool.getUserAccountData(borrower.address); diff --git a/test/liquidation-underlying.spec.ts b/test/liquidation-underlying.spec.ts index 064e3856..4be6097f 100644 --- a/test/liquidation-underlying.spec.ts +++ b/test/liquidation-underlying.spec.ts @@ -29,7 +29,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //user 1 deposits 1000 DAI const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); - await pool.connect(depositor.signer).deposit(dai.address, amountDAItoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(dai.address, amountDAItoDeposit, depositor.address, '0'); //user 2 deposits 1 ETH const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -39,7 +41,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //approve protocol to access the borrower wallet await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //user 2 borrows @@ -194,7 +198,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //depositor deposits 1000 USDC const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); - await pool.connect(depositor.signer).deposit(usdc.address, amountUSDCtoDeposit, '0'); + await pool + .connect(depositor.signer) + .deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0'); //borrower deposits 1 ETH const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); @@ -205,7 +211,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //approve protocol to access the borrower wallet await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); //borrower borrows const userGlobalData = await pool.getUserAccountData(borrower.address); @@ -334,7 +342,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', //borrower deposits 1000 LEND const amountLENDtoDeposit = await convertToCurrencyDecimals(lend.address, '1000'); - await pool.connect(borrower.signer).deposit(lend.address, amountLENDtoDeposit, '0'); + await pool + .connect(borrower.signer) + .deposit(lend.address, amountLENDtoDeposit, borrower.address, '0'); const usdcPrice = await oracle.getAssetPrice(usdc.address); //drops HF below 1 From 940d9d44f257c159d91ceba79693b7e1eed52be2 Mon Sep 17 00:00:00 2001 From: andyk Date: Wed, 9 Sep 2020 13:48:52 +0300 Subject: [PATCH 15/31] redo useless change of package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab5f751a..606aaaf9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'", "test": "buidler test", "test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts", - "test-flash": "buidler test test/__setup.spec.ts test/collateral-swap.spec.ts", + "test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts", "dev:coverage": "buidler coverage", "dev:deployment": "buidler dev-deployment", "dev:deployExample": "buidler deploy-Example", From 37a9c7ad885b95bcfe4de35fc5f33d1e43ad510e Mon Sep 17 00:00:00 2001 From: eboado Date: Wed, 9 Sep 2020 13:06:46 +0200 Subject: [PATCH 16/31] - Added reentrancy guard on repayWithCollateral() and test. --- contracts/lendingpool/LendingPool.sol | 9 ++++++- .../LendingPoolLiquidationManager.sol | 5 ++++ contracts/mocks/flashloan/MockSwapAdapter.sol | 27 +++++++++++++++---- test/repay-with-collateral.spec.ts | 26 ++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 1d266080..9b9372f2 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -51,6 +51,8 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { address[] internal _reservesList; + bool internal _flashLiquidationLocked; + /** * @dev only lending pools configurator can use functions affected by this modifier **/ @@ -474,7 +476,10 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { uint256 principalAmount, address receiver, bytes calldata params - ) external override nonReentrant { + ) external override { + require(!_flashLiquidationLocked, "REENTRANCY_NOT_ALLOWED"); + _flashLiquidationLocked = true; + address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager(); //solium-disable-next-line @@ -496,6 +501,8 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { if (returnCode != 0) { revert(string(abi.encodePacked(returnMessage))); } + + _flashLiquidationLocked = false; } /** diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index b79bfed7..888edb93 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -37,6 +37,9 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl using ReserveConfiguration for ReserveConfiguration.Map; using UserConfiguration for UserConfiguration.Map; + // IMPORTANT The storage layout of the LendingPool is reproduced here because this contract + // is gonna be used through DELEGATECALL + LendingPoolAddressesProvider internal addressesProvider; mapping(address => ReserveLogic.ReserveData) internal reserves; @@ -44,6 +47,8 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl address[] internal reservesList; + bool internal _flashLiquidationLocked; + uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000; /** diff --git a/contracts/mocks/flashloan/MockSwapAdapter.sol b/contracts/mocks/flashloan/MockSwapAdapter.sol index 6b2f2330..85c7d84b 100644 --- a/contracts/mocks/flashloan/MockSwapAdapter.sol +++ b/contracts/mocks/flashloan/MockSwapAdapter.sol @@ -4,11 +4,13 @@ pragma solidity ^0.6.8; import {MintableERC20} from '../tokens/MintableERC20.sol'; import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; import {ISwapAdapter} from '../../interfaces/ISwapAdapter.sol'; +import {ILendingPool} from "../../interfaces/ILendingPool.sol"; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; contract MockSwapAdapter is ISwapAdapter { - uint256 amountToReturn; + uint256 internal _amountToReturn; + bool internal _tryReentrancy; ILendingPoolAddressesProvider public addressesProvider; event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); @@ -18,7 +20,11 @@ contract MockSwapAdapter is ISwapAdapter { } function setAmountToReturn(uint256 amount) public { - amountToReturn = amount; + _amountToReturn = amount; + } + + function setTryReentrancy(bool tryReentrancy) public { + _tryReentrancy = tryReentrancy; } function executeOperation( @@ -30,10 +36,21 @@ contract MockSwapAdapter is ISwapAdapter { ) external override { params; IERC20(assetToSwapFrom).transfer(address(1), amountToSwap); // We don't want to keep funds here - MintableERC20(assetToSwapTo).mint(amountToReturn); - IERC20(assetToSwapTo).approve(fundsDestination, amountToReturn); + MintableERC20(assetToSwapTo).mint(_amountToReturn); + IERC20(assetToSwapTo).approve(fundsDestination, _amountToReturn); - emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, amountToReturn); + if (_tryReentrancy) { + ILendingPool(fundsDestination).repayWithCollateral( + assetToSwapFrom, + assetToSwapTo, + address(1), // Doesn't matter, we just want to test the reentrancy + 1 ether, // Same + address(1), // Same + "0x" + ); + } + + emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, _amountToReturn); } function burnAsset(IERC20 asset, uint256 amount) public { diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts index 0bd25680..d7f9d77f 100644 --- a/test/repay-with-collateral.spec.ts +++ b/test/repay-with-collateral.spec.ts @@ -67,12 +67,38 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0); }); + it('It is not possible to do reentrancy on repayWithCollateral()', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[1]; + + const amountToRepay = parseEther('10'); + + await waitForTx(await mockSwapAdapter.setTryReentrancy(true)) + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await expect( + pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ).to.be.revertedWith("FAILED_REPAY_WITH_COLLATERAL") + + }); + it('User 2 tries to repay his DAI Variable loan using his WETH collateral. First half the amount, after that, the rest', async () => { const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; const user = users[1]; const amountToRepay = parseEther('10'); + await waitForTx(await mockSwapAdapter.setTryReentrancy(false)) + const {userData: wethUserDataBefore} = await getContractsData( weth.address, user.address, From d828c63a83e86cf578bb0abfa2635f154e97140f Mon Sep 17 00:00:00 2001 From: eboado Date: Wed, 9 Sep 2020 13:21:19 +0200 Subject: [PATCH 17/31] - Added reset of user's usage as collateral on repayWithCollateral(). --- contracts/lendingpool/LendingPoolLiquidationManager.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 888edb93..247c01e9 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -398,6 +398,10 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate); + if (vars.userCollateralBalance == vars.maxCollateralToLiquidate) { + usersConfig[user].setUsingAsCollateral(collateralReserve.index, false); + } + address principalAToken = debtReserve.aTokenAddress; // Notifies the receiver to proceed, sending as param the underlying already transferred From 223690f5f1fd6ad923699e4d31357113e16768ac Mon Sep 17 00:00:00 2001 From: The3D Date: Wed, 9 Sep 2020 14:15:38 +0200 Subject: [PATCH 18/31] Fixed test on transfer, updated interest redirection tests --- contracts/tokenization/AToken.sol | 8 +++++- test/helpers/utils/calculations.ts | 39 ++++++++++++++---------------- test/scenario.spec.ts | 2 +- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 5f308d76..e0e874d7 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -201,7 +201,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { if (currentScaledBalance == 0 && redirectedBalance == 0) { return 0; } - uint256 scaledRedirectedBalance = redirectedBalance > 0 ? redirectedBalance.rayDiv(_interestRedirectionIndexes[user]) : 0; + uint256 scaledRedirectedBalance = redirectedBalance > 0 ? redirectedBalance.rayDiv(_redirectedBalanceIndexes[user]) : 0; uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); @@ -340,6 +340,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken { _updateRedirectedBalanceOfRedirectionAddress(origin, redirectionAddress, 0, 0, index); } + console.log("Interest redirection completed"); + emit RedirectedBalanceUpdated( redirectionAddress, scaledBalanceToAdd, @@ -454,6 +456,10 @@ contract AToken is VersionedInitializable, ERC20, IAToken { //being transferred _updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index); + if(scaledBalanceOf(from) == 0){ + _resetDataOnZeroBalance(from); + } + emit BalanceTransfer(from, to, amount, index); } diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index c45ad951..14a3a1f5 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -50,8 +50,8 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.liquidityRate = reserveDataAfterAction.liquidityRate; expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance( - reserveDataAfterAction, userDataBeforeAction, + reserveDataAfterAction.liquidityIndex, new BigNumber(amountDeposited), new BigNumber(0) ); @@ -89,8 +89,8 @@ export const calcExpectedUserDataAfterDeposit = ( ); expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( - userDataBeforeAction, expectedUserData, + reserveDataAfterAction.liquidityIndex, userDataBeforeAction.redirectionAddressRedirectedBalance, new BigNumber(amountDeposited), new BigNumber(0) @@ -121,8 +121,8 @@ export const calcExpectedUserDataAfterWithdraw = ( } expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance( - reserveDataAfterAction, userDataBeforeAction, + reserveDataAfterAction.liquidityIndex, new BigNumber(0), new BigNumber(amountWithdrawn) ); @@ -175,8 +175,8 @@ export const calcExpectedUserDataAfterWithdraw = ( } expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( - userDataBeforeAction, expectedUserData, + reserveDataAfterAction.liquidityIndex, userDataBeforeAction.redirectionAddressRedirectedBalance, new BigNumber(0), new BigNumber(amountWithdrawn) @@ -900,6 +900,7 @@ export const calcExpectedReserveDataAfterStableRateRebalance = ( expectedReserveData.totalBorrowsVariable, expectedReserveData.averageStableBorrowRate ); + expectedReserveData.liquidityRate = rates[0]; expectedReserveData.stableBorrowRate = rates[1]; @@ -941,10 +942,6 @@ export const calcExpectedUserDataAfterStableRateRebalance = ( expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt; - const debtAccrued = expectedUserData.currentStableDebt.minus( - userDataBeforeAction.principalStableDebt - ); - expectedUserData.stableBorrowRate = reserveDataBeforeAction.stableBorrowRate; expectedUserData.liquidityRate = expectedDataAfterAction.liquidityRate; @@ -971,6 +968,8 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( const expectedFromData = { ...fromDataBeforeAction }; const expectedToData = { ...toDataBeforeAction }; + const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, txTimestamp); + expectedFromData.currentStableDebt = calcExpectedStableDebtTokenBalance( fromDataBeforeAction, txTimestamp @@ -988,20 +987,17 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( expectedFromData.stableBorrowRate = fromDataBeforeAction.stableBorrowRate; expectedToData.stableBorrowRate = toDataBeforeAction.stableBorrowRate; - expectedFromData.scaledATokenBalance = expectedFromData.currentATokenBalance = calcExpectedATokenBalance( + expectedFromData.scaledATokenBalance = fromDataBeforeAction.scaledATokenBalance; + + expectedFromData.currentATokenBalance = calcExpectedATokenBalance( reserveDataBeforeAction, fromDataBeforeAction, txTimestamp ); - expectedToData.principalATokenBalance = expectedToData.currentATokenBalance = calcExpectedATokenBalance( - reserveDataBeforeAction, - toDataBeforeAction, - txTimestamp - ); expectedToData.redirectedBalance = toDataBeforeAction.redirectedBalance.plus( - expectedFromData.currentATokenBalance + expectedFromData.currentATokenBalance.rayDiv(index) ); if (fromAddress === toAddress) { @@ -1013,10 +1009,11 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( expectedToData.redirectionAddressRedirectedBalance = new BigNumber(0); } else { expectedFromData.interestRedirectionAddress = toAddress; + expectedFromData.interestRedirectionIndex = index; expectedFromData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( - toDataBeforeAction, expectedFromData, + index, toDataBeforeAction.redirectedBalance, expectedFromData.currentATokenBalance, new BigNumber(0) @@ -1027,14 +1024,14 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( }; const calcExpectedScaledATokenBalance = ( - reserveDataAfterAction: ReserveData, userDataBeforeAction: UserReserveData, + index: BigNumber, amountAdded: BigNumber, amountTaken: BigNumber ) => { return userDataBeforeAction.scaledATokenBalance - .plus(amountAdded.rayDiv(reserveDataAfterAction.liquidityIndex)) - .minus(amountTaken.rayDiv(reserveDataAfterAction.liquidityIndex)); + .plus(amountAdded.rayDiv(index)) + .minus(amountTaken.rayDiv(index)); }; const calcExpectedATokenBalance = ( @@ -1065,14 +1062,14 @@ const calcExpectedATokenBalance = ( }; const calcExpectedRedirectedBalance = ( - userDataBeforeAction: UserReserveData, expectedUserDataAfterAction: UserReserveData, + index: BigNumber, redirectedBalanceBefore: BigNumber, amountToAdd: BigNumber, amountToSubstract: BigNumber ): BigNumber => { return expectedUserDataAfterAction.interestRedirectionAddress !== ZERO_ADDRESS - ? redirectedBalanceBefore.plus(amountToAdd).minus(amountToSubstract) + ? redirectedBalanceBefore.plus(amountToAdd.rayDiv(index)).minus(amountToSubstract.rayDiv(index)) : new BigNumber('0'); }; const calcExpectedAverageStableBorrowRate = ( diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index 025bc720..bc295986 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); const scenarioFolder = './test/helpers/scenarios/'; -const selectedScenarios: string[] = ['']; +const selectedScenarios: string[] = ['interest-redirection.json']; fs.readdirSync(scenarioFolder).forEach((file) => { if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return; From 75c5c7c615aa1700fff0933f6c31c9ec72ff0774 Mon Sep 17 00:00:00 2001 From: eboado Date: Wed, 9 Sep 2020 14:22:35 +0200 Subject: [PATCH 19/31] - Added test for user's usage as collateral on repayWithCollateral(). --- test/repay-with-collateral.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts index d7f9d77f..d4bac478 100644 --- a/test/repay-with-collateral.spec.ts +++ b/test/repay-with-collateral.spec.ts @@ -171,6 +171,8 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { expectedCollateralLiquidated.toString() ) ); + + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true; }); it('User 3 deposits WETH and borrows USDC at Variable', async () => { @@ -271,6 +273,8 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { ), 'INVALID_COLLATERAL_POSITION' ); + + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true; }); it('Revert expected. User 3 tries to repay with his collateral a currency he havent borrow', async () => { @@ -426,6 +430,8 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { usdc.address, user.address ); + + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true; }); it('User 4 tries to repay a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { @@ -517,5 +523,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { ); expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0); + + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false; }); }); From f1bd569346e234b8d443020110d0edf757d17425 Mon Sep 17 00:00:00 2001 From: David Racero Date: Wed, 9 Sep 2020 14:47:33 +0200 Subject: [PATCH 20/31] Added reentrancy test and full amount flash liquidation test --- .../flash-liquidation-with-collateral.spec.ts | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/test/flash-liquidation-with-collateral.spec.ts b/test/flash-liquidation-with-collateral.spec.ts index 5dd2122f..30ca5c19 100644 --- a/test/flash-liquidation-with-collateral.spec.ts +++ b/test/flash-liquidation-with-collateral.spec.ts @@ -362,6 +362,106 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn await oracle.setAssetPrice(usdc.address, usdcPrice); }); + it('User 5 liquidates all the USDC loan of User 3 by swapping his WETH collateral', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[2]; + const liquidator = users[4]; + // Sets USDC Price higher to decrease health factor below 1 + const usdcPrice = await oracle.getAssetPrice(usdc.address); + + await oracle.setAssetPrice( + usdc.address, + new BigNumber(usdcPrice.toString()).multipliedBy(1.35).toFixed(0) + ); + + const userGlobalData = await pool.getUserAccountData(user.address); + + expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + const amountToRepay = usdcReserveDataBefore.totalBorrowsVariable.toFixed(0); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString(), + 'INVALID_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + + // Resets USDC Price + await oracle.setAssetPrice(usdc.address, usdcPrice); + }); + it('User 2 deposit WETH and borrows DAI at Variable', async () => { const {pool, weth, dai, users, oracle} = testEnv; const user = users[1]; @@ -387,6 +487,50 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0); }); + + it('It is not possible to do reentrancy on repayWithCollateral()', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[1]; + const liquidator = users[4]; + + // Sets DAI Price higher to decrease health factor below 1 + const daiPrice = await oracle.getAssetPrice(dai.address); + + await oracle.setAssetPrice( + dai.address, + new BigNumber(daiPrice.toString()).multipliedBy(1.4).toFixed(0) + ); + + const {reserveData: daiReserveDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.toString(); + + await waitForTx(await mockSwapAdapter.setTryReentrancy(true)); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await expect( + pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ).to.be.revertedWith('FAILED_REPAY_WITH_COLLATERAL'); + + // Resets DAI Price + await oracle.setAssetPrice(dai.address, daiPrice); + // Resets mock + await waitForTx(await mockSwapAdapter.setTryReentrancy(false)); + }); + it('User 5 tries to liquidate User 2 DAI Variable loan using his WETH collateral, with good HF', async () => { const {pool, weth, dai, users, mockSwapAdapter} = testEnv; const user = users[1]; From 398335124f1620384385a2a5e1099e9accfa47c5 Mon Sep 17 00:00:00 2001 From: David Racero Date: Wed, 9 Sep 2020 15:43:02 +0200 Subject: [PATCH 21/31] Added collateral test to flash liquidation --- deployed-contracts.json | 11 +++- package.json | 1 + .../flash-liquidation-with-collateral.spec.ts | 61 +++++++++++-------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/deployed-contracts.json b/deployed-contracts.json index 8286b8f5..31972f1b 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -174,7 +174,7 @@ }, "WalletBalanceProvider": { "buidlerevm": { - "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2", + "address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { @@ -414,7 +414,7 @@ }, "AaveProtocolTestHelpers": { "buidlerevm": { - "address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10" + "address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4" }, "localhost": { "address": "0x3b050AFb4ac4ACE646b31fF3639C1CD43aC31460" @@ -489,5 +489,10 @@ "address": "0x8733AfE8174BA7c04c6CD694bD673294079b7E10", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } + }, + "MockSwapAdapter": { + "buidlerevm": { + "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2" + } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 94ec647c..30e7d371 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test": "buidler test", "test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts", "test-repay-with-collateral": "buidler test test/__setup.spec.ts test/repay-with-collateral.spec.ts", + "test-liquidate-with-collateral": "buidler test test/__setup.spec.ts test/flash-liquidation-with-collateral.spec.ts", "dev:coverage": "buidler coverage", "dev:deployment": "buidler dev-deployment", "dev:deployExample": "buidler deploy-Example", diff --git a/test/flash-liquidation-with-collateral.spec.ts b/test/flash-liquidation-with-collateral.spec.ts index 30ca5c19..2ffaa936 100644 --- a/test/flash-liquidation-with-collateral.spec.ts +++ b/test/flash-liquidation-with-collateral.spec.ts @@ -11,6 +11,7 @@ import {waitForTx} from './__setup.spec'; import {timeLatest} from '../helpers/misc-utils'; import {tEthereumAddress, ProtocolErrors} from '../helpers/types'; import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; +import {formatUnits, formatEther} from 'ethers/lib/utils'; const {expect} = require('chai'); const {parseUnits, parseEther} = ethers.utils; @@ -323,6 +324,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn ), 'INVALID_COLLATERAL_POSITION' ); + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true; // Resets USDC Price await oracle.setAssetPrice(usdc.address, usdcPrice); @@ -651,6 +653,8 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn expectedCollateralLiquidated.toString() ) ); + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true; + // Resets DAI price await oracle.setAssetPrice(dai.address, daiPrice); }); @@ -736,14 +740,14 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn ); }); - it.skip('WIP Liquidator tries to repay 4 user a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { - const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + it('Liquidator tries to repay 4 user a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { + const {pool, weth, dai, usdc, users, mockSwapAdapter, oracle} = testEnv; const user = users[3]; const liquidator = users[5]; const amountToDepositWeth = parseEther('0.1'); const amountToDepositDAI = parseEther('500'); - const amountToBorrowVariable = parseEther('80'); + const amountToBorrowVariable = parseUnits('80', '6'); await weth.connect(user.signer).mint(amountToDepositWeth); await dai.connect(user.signer).mint(amountToDepositDAI); @@ -753,9 +757,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, '0'); await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, '0'); - await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0); + await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0); - const amountToRepay = parseEther('80'); + const amountToRepay = amountToBorrowVariable; const {userData: wethUserDataBefore} = await getContractsData( weth.address, @@ -763,26 +767,28 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn testEnv ); - const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( - dai.address, - user.address, - testEnv - ); - const wethPrice = await oracle.getAssetPrice(weth.address); + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + // Set HF below 1 + const daiPrice = await oracle.getAssetPrice(dai.address); await oracle.setAssetPrice( - weth.address, - new BigNumber(wethPrice.toString()).multipliedBy(0.1).toFixed(0) + dai.address, + new BigNumber(daiPrice.toString()).multipliedBy(0.1).toFixed(0) ); const userGlobalDataPrior = await pool.getUserAccountData(user.address); expect(userGlobalDataPrior.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF); + + // Execute liquidation await mockSwapAdapter.setAmountToReturn(amountToRepay); await waitForTx( await pool .connect(liquidator.signer) .repayWithCollateral( weth.address, - dai.address, + usdc.address, user.address, amountToRepay, mockSwapAdapter.address, @@ -797,16 +803,20 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn testEnv ); - const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); const collateralPrice = await oracle.getAssetPrice(weth.address); - const principalPrice = await oracle.getAssetPrice(dai.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); const collateralConfig = await pool.getReserveConfigurationData(weth.address); const collateralDecimals = collateralConfig.decimals.toString(); const principalDecimals = ( - await pool.getReserveConfigurationData(dai.address) + await pool.getReserveConfigurationData(usdc.address) ).decimals.toString(); const collateralLiquidationBonus = collateralConfig.liquidationBonus.toString(); @@ -820,20 +830,23 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn .decimalPlaces(0, BigNumber.ROUND_DOWN); const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( - daiReserveDataBefore, - daiUserDataBefore, + usdcReserveDataBefore, + usdcUserDataBefore, new BigNumber(repayWithCollateralTimestamp) - ).minus(daiUserDataBefore.currentVariableDebt); + ).minus(usdcUserDataBefore.currentVariableDebt); - expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.equal( - new BigNumber(daiUserDataBefore.currentVariableDebt) + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + new BigNumber(usdcUserDataBefore.currentVariableDebt) .minus(expectedDebtCovered.toString()) .plus(expectedVariableDebtIncrease), 'INVALID_VARIABLE_DEBT_POSITION' ); + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false; + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0); - // Resets WETH Price - await oracle.setAssetPrice(weth.address, wethPrice); + + // Resets DAI Price + await oracle.setAssetPrice(dai.address, daiPrice); }); }); From 23b7226a73865b055915624ec957d1cb8aed407b Mon Sep 17 00:00:00 2001 From: David Racero Date: Wed, 9 Sep 2020 16:35:49 +0200 Subject: [PATCH 22/31] Fix bignumber global test config --- test/__setup.spec.ts | 4 ++-- test/liquidation-underlying.spec.ts | 6 ++++++ test/scenario.spec.ts | 9 +++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index 689ecf3a..e2db2752 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -504,8 +504,8 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address); await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address); - const mockSwapAdapter = await deployMockSwapAdapter(addressesProvider.address) - await insertContractAddressInDb(eContractid.MockSwapAdapter, mockSwapAdapter.address) + const mockSwapAdapter = await deployMockSwapAdapter(addressesProvider.address); + await insertContractAddressInDb(eContractid.MockSwapAdapter, mockSwapAdapter.address); await deployWalletBalancerProvider(addressesProvider.address); diff --git a/test/liquidation-underlying.spec.ts b/test/liquidation-underlying.spec.ts index 676c9c26..9a58965e 100644 --- a/test/liquidation-underlying.spec.ts +++ b/test/liquidation-underlying.spec.ts @@ -19,6 +19,12 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', USER_DID_NOT_BORROW_SPECIFIED, THE_COLLATERAL_CHOSEN_CANNOT_BE_LIQUIDATED, } = ProtocolErrors; + before('Before LendingPool liquidation: set config', () => { + BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); + }); + after('After LendingPool liquidation: reset config', () => { + BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP}); + }); it('LIQUIDATION - Deposits WETH, borrows DAI', async () => { const {dai, weth, users, pool, oracle} = testEnv; diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index 5d449d76..54fe7433 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -8,8 +8,6 @@ import {getReservesConfigByPool} from '../helpers/constants'; import {AavePools, iAavePoolAssets, IReserveParams} from '../helpers/types'; import {executeStory} from './helpers/scenario-engine'; -BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); - const scenarioFolder = './test/helpers/scenarios/'; const selectedScenarios: string[] = []; @@ -21,12 +19,19 @@ fs.readdirSync(scenarioFolder).forEach((file) => { makeSuite(scenario.title, async (testEnv) => { before('Initializing configuration', async () => { + // Sets BigNumber for this suite, instead of globally + BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); + actionsConfiguration.skipIntegrityCheck = false; //set this to true to execute solidity-coverage calculationsConfiguration.reservesParams = >( getReservesConfigByPool(AavePools.proto) ); }); + after('Reset', () => { + // Reset BigNumber + BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP}); + }); for (const story of scenario.stories) { it(story.description, async () => { From 0f06c3b72e75b912cd29ee7f4d03a60190dd87cf Mon Sep 17 00:00:00 2001 From: The3D Date: Wed, 9 Sep 2020 17:20:36 +0200 Subject: [PATCH 23/31] updated interest redirection tests --- contracts/tokenization/AToken.sol | 1 + test/helpers/actions.ts | 8 +++++--- test/helpers/utils/helpers.ts | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index e0e874d7..2a810641 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -404,6 +404,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { function _resetDataOnZeroBalance(address user) internal returns (bool) { //if the user has 0 principal balance, the interest stream redirection gets reset _interestRedirectionAddresses[user] = address(0); + _interestRedirectionIndexes[user] = 0; //emits a InterestStreamRedirected event to notify that the redirection has been reset emit InterestStreamRedirected(user, address(0), 0, 0); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 616e8ffa..2a84fa66 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -49,14 +49,13 @@ const almostEqualOrEqual = function ( key === 'marketStableRate' || key === 'symbol' || key === 'aTokenAddress' || - key === 'initialATokenExchangeRate' || key === 'decimals' ) { // skipping consistency check on accessory data return; } - + this.assert(actual[key] != undefined, `Property ${key} is undefined in the actual data`); expect(expected[key] != undefined, `Property ${key} is undefined in the expected data`); @@ -682,7 +681,7 @@ export const redirectInterestStream = async ( const {userData: toDataAfter} = await getContractsData(reserve, to, testEnv); const [expectedFromData, expectedToData] = calcExpectedUsersDataAfterRedirectInterest( - reserveDataBefore, + reserveDataBefore, fromDataBefore, toDataBefore, user.address, @@ -692,7 +691,10 @@ export const redirectInterestStream = async ( txTimestamp ); + console.log("Checking from data"); + expectEqual(fromDataAfter, expectedFromData); + console.log("Checking to data"); expectEqual(toDataAfter, expectedToData); // truffleAssert.eventEmitted(txResult, 'InterestStreamRedirected', (ev: any) => { diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index aa564de0..ccd7d1d1 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -82,7 +82,7 @@ export const getUserData = async ( return { scaledATokenBalance: new BigNumber(scaledATokenBalance), interestRedirectionAddress, - interestRedirectionIndex, + interestRedirectionIndex: new BigNumber(interestRedirectionIndex), redirectionAddressRedirectedBalance: new BigNumber(redirectionAddressRedirectedBalance), redirectedBalance: new BigNumber(redirectedBalance), currentATokenBalance: new BigNumber(userData.currentATokenBalance.toString()), @@ -136,6 +136,6 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo scaledATokenBalance.toString(), redirectionAddressRedirectedBalance.toString(), interestRedirectionAddress, - interestRedirectionIndex + interestRedirectionIndex.toString() ]; }; From a3934152fe6fa9d4c5ef45197d4c79b752ad2585 Mon Sep 17 00:00:00 2001 From: The3D Date: Wed, 9 Sep 2020 19:43:41 +0200 Subject: [PATCH 24/31] updated interest redirection --- contracts/tokenization/AToken.sol | 51 ++++++++------ contracts/tokenization/interfaces/IAToken.sol | 14 +++- test.log | 4 +- test/helpers/actions.ts | 2 +- test/helpers/utils/calculations.ts | 66 +++++++++++-------- test/helpers/utils/helpers.ts | 25 ++++--- test/helpers/utils/interfaces/index.ts | 5 +- 7 files changed, 103 insertions(+), 64 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 2a810641..0161b149 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -31,7 +31,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { mapping(address => address) private _interestRedirectionAddresses; mapping(address => uint256) private _interestRedirectionIndexes; - mapping(address => uint256) private _redirectedBalances; + mapping(address => uint256) private _scaledRedirectedBalances; mapping(address => uint256) private _redirectedBalanceIndexes; mapping(address => address) private _interestRedirectionAllowances; @@ -163,7 +163,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { //if the user is redirecting his interest towards someone else, //we update the redirected balance of the redirection address by adding the accrued interest //and the amount deposited - _updateRedirectedBalanceOfRedirectionAddress(user, user, amount, 0, index); + _updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index); emit Mint(user, amount, index); } @@ -192,16 +192,17 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @return the total balance of the user **/ function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) { + //current scaled balance of the user uint256 currentScaledBalance = super.balanceOf(user); //balance redirected by other users to user for interest rate accrual - uint256 redirectedBalance = _redirectedBalances[user]; + uint256 scaledRedirectedBalance = _scaledRedirectedBalances[user]; - if (currentScaledBalance == 0 && redirectedBalance == 0) { + if (currentScaledBalance == 0 && scaledRedirectedBalance == 0) { return 0; } - uint256 scaledRedirectedBalance = redirectedBalance > 0 ? redirectedBalance.rayDiv(_redirectedBalanceIndexes[user]) : 0; + uint256 actualBalanceRedirectedToUser = scaledRedirectedBalance > 0 ? scaledRedirectedBalance.rayMul(_redirectedBalanceIndexes[user]) : 0; uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); @@ -209,16 +210,16 @@ contract AToken is VersionedInitializable, ERC20, IAToken { //if the user is not redirecting the interest, his balance is the result of //the interest accrued by his current scaled balance and the interest accrued by his //scaled redirected balance - return currentScaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(scaledRedirectedBalance); + return currentScaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(actualBalanceRedirectedToUser); } //if the user is redirecting, his balance only increases by the balance he is being redirected to - uint256 lastRedirectedBalance = currentScaledBalance.rayDiv(_interestRedirectionIndexes[user]); + uint256 lastBalanceRedirectedByUser = currentScaledBalance.rayMul(_interestRedirectionIndexes[user]); return - lastRedirectedBalance.add( + lastBalanceRedirectedByUser.add( scaledRedirectedBalance.rayMul(index) - ).sub(scaledRedirectedBalance); + ).sub(actualBalanceRedirectedToUser); } /** @@ -227,7 +228,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @param user the address of the user * @return the scaled balance of the user **/ - function scaledBalanceOf(address user) public override view returns (uint256) { + function scaledBalanceOf(address user) external override view returns (uint256) { return super.balanceOf(user); } @@ -288,10 +289,22 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @param user address of the user * @return the total redirected balance **/ - function getRedirectedBalance(address user) external override view returns (uint256) { - return _redirectedBalances[user]; + function getScaledRedirectedBalance(address user) external override view returns (uint256) { + return _scaledRedirectedBalances[user]; } + /** + * @dev returns the index stored during the last action that affected the redirected balance. + * scaledRedirectedBalance * redirectedBalanceIndex allows to calculate the actual redirected balance + * which is needed to calculate the interest accrued + * @param user address of the user + * @return the total redirected balance + **/ + function getRedirectedBalanceIndex(address user) external override view returns (uint256) { + return _redirectedBalanceIndexes[user]; + } + + /** * @dev updates the redirected balance of the user. If the user is not redirecting his @@ -313,12 +326,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return; } - //updating the interest redirection index of the user - _interestRedirectionIndexes[user] = index; - - //updating the redirected balance - _redirectedBalances[redirectionAddress] = _redirectedBalances[redirectionAddress] + _scaledRedirectedBalances[redirectionAddress] = _scaledRedirectedBalances[redirectionAddress] .add(scaledBalanceToAdd) .sub(scaledBalanceToRemove); @@ -362,6 +371,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken { require(to != currentRedirectionAddress, Errors.INTEREST_ALREADY_REDIRECTED); + uint256 scaledFromBalance = super.balanceOf(from); + uint256 fromBalance = balanceOf(from); require(fromBalance > 0, Errors.NO_VALID_BALANCE_FOR_REDIRECTION); @@ -374,7 +385,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { //the redirection address we substract the redirected balance of the previous //recipient if (currentRedirectionAddress != address(0)) { - _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledBalance, index); + _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledFromBalance, index); } //if the user is redirecting the interest back to himself, @@ -390,7 +401,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { _interestRedirectionIndexes[from] = index; //adds the user balance to the redirected balance of the destination - _updateRedirectedBalanceOfRedirectionAddress(from, from, fromBalance, 0, index); + _updateRedirectedBalanceOfRedirectionAddress(from, from, scaledBalance, 0, index); emit InterestStreamRedirected(from, to, fromBalance, index); } @@ -457,7 +468,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { //being transferred _updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index); - if(scaledBalanceOf(from) == 0){ + if(super.balanceOf(from) == 0){ _resetDataOnZeroBalance(from); } diff --git a/contracts/tokenization/interfaces/IAToken.sol b/contracts/tokenization/interfaces/IAToken.sol index 8250e242..68b70e60 100644 --- a/contracts/tokenization/interfaces/IAToken.sol +++ b/contracts/tokenization/interfaces/IAToken.sol @@ -159,12 +159,20 @@ interface IAToken is IERC20 { function getUserInterestRedirectionIndex(address user) external view returns (uint256); /** - * @dev returns the redirected balance of the user. The redirected balance is the balance - * redirected by other accounts to the user, that is accrueing interest for him. + * @dev returns the scaled redirected balance of the user. The scaled redirected balance is the sum of all the redirected balances + * divided by the index at the moment of each specific redirection. * @param user address of the user * @return the total redirected balance **/ - function getRedirectedBalance(address user) external view returns (uint256); + function getScaledRedirectedBalance(address user) external view returns (uint256); + + /** + * @dev returns the redirected balance index. The redirected balance index is the + * index at the moment the last scaled redirected balance has been performed, and allows to calculate the actual redirected balance + * @param user address of the user + * @return the redirected balance index + **/ + function getRedirectedBalanceIndex(address user) external view returns (uint256); /** * @dev transfers the underlying asset to the target. Used by the lendingpool to transfer diff --git a/test.log b/test.log index 64ab456c..73e89e84 100644 --- a/test.log +++ b/test.log @@ -2483,7 +2483,7 @@ gas used: 6117750 28) AToken: interest rate redirection negative test cases User 0 tries to redirect the interest to user 2 twice (revert expected): - AssertionError: expected '3000000004839170420641' to be almost equal or equal '3000002810040899373373' for property redirectionAddressRedirectedBalance + AssertionError: expected '3000000004839170420641' to be almost equal or equal '3000002810040899373373' for property redirectionAddressScaledRedirectedBalance + expected - actual -3000000004839170420641 @@ -2550,7 +2550,7 @@ gas used: 6117750 32) AToken: interest rate redirection User 0 deposits 1000 DAI, redirects interest to user 2, user 1 borrows 100 DAI. After one year, user 0 redirects interest back to himself, user 1 borrows another 100 DAI and after another year repays the whole amount. Users 0 and User 2 withdraw: - AssertionError: expected '1020673496610825275870' to be almost equal or equal '1020673496616475023834' for property redirectionAddressRedirectedBalance + AssertionError: expected '1020673496610825275870' to be almost equal or equal '1020673496616475023834' for property redirectionAddressScaledRedirectedBalance + expected - actual -1020673496610825275870 diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 2a84fa66..6e654349 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -59,7 +59,7 @@ const almostEqualOrEqual = function ( this.assert(actual[key] != undefined, `Property ${key} is undefined in the actual data`); expect(expected[key] != undefined, `Property ${key} is undefined in the expected data`); - if (expected[key] == null || !actual[key] == null) { + if (expected[key] == null || actual[key] == null) { console.log('Found a undefined value for Key ', key, ' value ', expected[key], actual[key]); } diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index 14a3a1f5..c8b3e1d4 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -70,12 +70,14 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited); - expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; + expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; + expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS ? new BigNumber(0) : reserveDataAfterAction.liquidityIndex; + expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( userDataBeforeAction, @@ -88,10 +90,10 @@ export const calcExpectedUserDataAfterDeposit = ( txTimestamp ); - expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( + expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance( expectedUserData, reserveDataAfterAction.liquidityIndex, - userDataBeforeAction.redirectionAddressRedirectedBalance, + userDataBeforeAction.redirectionAddressScaledRedirectedBalance, new BigNumber(amountDeposited), new BigNumber(0) ); @@ -161,9 +163,11 @@ export const calcExpectedUserDataAfterWithdraw = ( } expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountWithdrawn); - expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; + expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; + expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; - if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.redirectedBalance.eq(0)) { + + if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.scaledRedirectedBalance.eq(0)) { expectedUserData.interestRedirectionAddress = ZERO_ADDRESS; expectedUserData.interestRedirectionIndex = new BigNumber(0); } else { @@ -174,10 +178,10 @@ export const calcExpectedUserDataAfterWithdraw = ( : reserveDataAfterAction.liquidityIndex; } - expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( + expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance( expectedUserData, reserveDataAfterAction.liquidityIndex, - userDataBeforeAction.redirectionAddressRedirectedBalance, + userDataBeforeAction.redirectionAddressScaledRedirectedBalance, new BigNumber(0), new BigNumber(amountWithdrawn) ); @@ -574,11 +578,12 @@ export const calcExpectedUserDataAfterBorrow = ( currentTimestamp ); expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; - expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; + expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; + expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex; - expectedUserData.redirectionAddressRedirectedBalance = - userDataBeforeAction.redirectionAddressRedirectedBalance; + expectedUserData.redirectionAddressScaledRedirectedBalance = + userDataBeforeAction.redirectionAddressScaledRedirectedBalance; expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex; expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed); @@ -664,11 +669,12 @@ export const calcExpectedUserDataAfterRepay = ( txTimestamp ); expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; - expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance; + expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; + expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex; - expectedUserData.redirectionAddressRedirectedBalance = - userDataBeforeAction.redirectionAddressRedirectedBalance; + expectedUserData.redirectionAddressScaledRedirectedBalance = + userDataBeforeAction.redirectionAddressScaledRedirectedBalance; expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex; if (user === onBehalfOf) { @@ -996,25 +1002,29 @@ export const calcExpectedUsersDataAfterRedirectInterest = ( ); - expectedToData.redirectedBalance = toDataBeforeAction.redirectedBalance.plus( - expectedFromData.currentATokenBalance.rayDiv(index) + expectedToData.scaledRedirectedBalance = toDataBeforeAction.scaledRedirectedBalance.plus( + expectedFromData.scaledATokenBalance ); + expectedToData.redirectedBalanceIndex = index; + if (fromAddress === toAddress) { expectedFromData.interestRedirectionAddress = ZERO_ADDRESS; - expectedFromData.redirectedBalance = new BigNumber(0); - expectedFromData.redirectionAddressRedirectedBalance = new BigNumber(0); + expectedFromData.scaledRedirectedBalance = new BigNumber(0); + expectedFromData.redirectedBalanceIndex = new BigNumber(0); + + expectedFromData.redirectionAddressScaledRedirectedBalance = new BigNumber(0); expectedToData.interestRedirectionAddress = ZERO_ADDRESS; - expectedToData.redirectedBalance = new BigNumber(0); - expectedToData.redirectionAddressRedirectedBalance = new BigNumber(0); + expectedToData.scaledRedirectedBalance = new BigNumber(0); + expectedToData.redirectionAddressScaledRedirectedBalance = new BigNumber(0); } else { expectedFromData.interestRedirectionAddress = toAddress; expectedFromData.interestRedirectionIndex = index; - expectedFromData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance( + expectedFromData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance( expectedFromData, index, - toDataBeforeAction.redirectedBalance, + toDataBeforeAction.scaledRedirectedBalance, expectedFromData.currentATokenBalance, new BigNumber(0) ); @@ -1044,21 +1054,25 @@ const calcExpectedATokenBalance = ( const { interestRedirectionAddress, interestRedirectionIndex: redirectionIndexBeforeAction, - redirectedBalance, + scaledRedirectedBalance, + redirectedBalanceIndex: redirectedBalanceIndexBeforeAction, scaledATokenBalance: scaledBalanceBeforeAction, } = userDataBeforeAction; - if (scaledBalanceBeforeAction.eq(0) && redirectedBalance.eq(0)) { + if (scaledBalanceBeforeAction.eq(0) && scaledRedirectedBalance.eq(0)) { return new BigNumber(0); } + const actualRedirectedBalance = scaledRedirectedBalance.gt(0) ? scaledRedirectedBalance.rayMul(redirectedBalanceIndexBeforeAction) : new BigNumber(0); + if (interestRedirectionAddress === ZERO_ADDRESS) { - return scaledBalanceBeforeAction.plus(redirectedBalance).rayMul(index).minus(redirectedBalance); + return scaledBalanceBeforeAction.plus(scaledRedirectedBalance).rayMul(index).minus(actualRedirectedBalance); } - const lastRedirectedBalance = scaledBalanceBeforeAction.rayDiv(redirectionIndexBeforeAction); - return lastRedirectedBalance.plus(redirectedBalance.rayMul(index).minus(redirectedBalance)); + const lastRedirectedBalance = scaledBalanceBeforeAction.rayMul(redirectionIndexBeforeAction); + + return lastRedirectedBalance.plus(scaledRedirectedBalance.rayMul(index).minus(actualRedirectedBalance)); }; const calcExpectedRedirectedBalance = ( diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index ccd7d1d1..008b904e 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -69,9 +69,10 @@ export const getUserData = async ( ]); const [ - redirectedBalance, + scaledRedirectedBalance, + redirectedBalanceIndex, scaledATokenBalance, - redirectionAddressRedirectedBalance, + redirectionAddressScaledRedirectedBalance, interestRedirectionAddress, interestRedirectionIndex, ] = aTokenData; @@ -83,8 +84,9 @@ export const getUserData = async ( scaledATokenBalance: new BigNumber(scaledATokenBalance), interestRedirectionAddress, interestRedirectionIndex: new BigNumber(interestRedirectionIndex), - redirectionAddressRedirectedBalance: new BigNumber(redirectionAddressRedirectedBalance), - redirectedBalance: new BigNumber(redirectedBalance), + redirectionAddressScaledRedirectedBalance: new BigNumber(redirectionAddressScaledRedirectedBalance), + scaledRedirectedBalance: new BigNumber(scaledRedirectedBalance), + redirectedBalanceIndex: new BigNumber(redirectedBalanceIndex), currentATokenBalance: new BigNumber(userData.currentATokenBalance.toString()), currentStableDebt: new BigNumber(userData.currentStableDebt.toString()), currentVariableDebt: new BigNumber(userData.currentVariableDebt.toString()), @@ -116,25 +118,28 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo const aToken = await getAToken(aTokenAddress); const [ interestRedirectionAddress, - redirectedBalance, + scaledRedirectedBalance, + redirectedBalanceIndex, scaledATokenBalance, interestRedirectionIndex ] = await Promise.all([ aToken.getInterestRedirectionAddress(user), - aToken.getRedirectedBalance(user), + aToken.getScaledRedirectedBalance(user), + aToken.getRedirectedBalanceIndex(user), aToken.scaledBalanceOf(user), aToken.getUserInterestRedirectionIndex(user) ]); - const redirectionAddressRedirectedBalance = + const redirectionAddressScaledRedirectedBalance = interestRedirectionAddress !== ZERO_ADDRESS - ? new BigNumber((await aToken.getRedirectedBalance(interestRedirectionAddress)).toString()) + ? new BigNumber((await aToken.getScaledRedirectedBalance(interestRedirectionAddress)).toString()) : new BigNumber('0'); return [ - redirectedBalance.toString(), + scaledRedirectedBalance.toString(), + redirectedBalanceIndex.toString(), scaledATokenBalance.toString(), - redirectionAddressRedirectedBalance.toString(), + redirectionAddressScaledRedirectedBalance.toString(), interestRedirectionAddress, interestRedirectionIndex.toString() ]; diff --git a/test/helpers/utils/interfaces/index.ts b/test/helpers/utils/interfaces/index.ts index d64d8457..7aa89910 100644 --- a/test/helpers/utils/interfaces/index.ts +++ b/test/helpers/utils/interfaces/index.ts @@ -5,8 +5,9 @@ export interface UserReserveData { currentATokenBalance: BigNumber; interestRedirectionAddress: string; interestRedirectionIndex: BigNumber; - redirectionAddressRedirectedBalance: BigNumber; - redirectedBalance: BigNumber; + redirectionAddressScaledRedirectedBalance: BigNumber; + scaledRedirectedBalance: BigNumber; + redirectedBalanceIndex: BigNumber; currentStableDebt: BigNumber; currentVariableDebt: BigNumber; principalStableDebt: BigNumber; From a67c56c09f38332ca1d303c4ea0aebed1222cf2b Mon Sep 17 00:00:00 2001 From: The3D Date: Wed, 9 Sep 2020 21:16:39 +0200 Subject: [PATCH 25/31] Removed interest redirection, fixed tests --- contracts/tokenization/AToken.sol | 257 +---------- contracts/tokenization/interfaces/IAToken.sol | 94 +--- test/atoken-transfer.spec.ts | 119 ----- test/helpers/actions.ts | 151 ------- test/helpers/scenario-engine.ts | 71 +-- .../interest-redirection-negatives.json | 102 ----- .../scenarios/interest-redirection.json | 405 ------------------ test/helpers/utils/calculations.ts | 148 +------ test/helpers/utils/helpers.ts | 44 +- test/helpers/utils/interfaces/index.ts | 5 - test/scenario.spec.ts | 2 +- 11 files changed, 15 insertions(+), 1383 deletions(-) delete mode 100644 test/helpers/scenarios/interest-redirection-negatives.json delete mode 100644 test/helpers/scenarios/interest-redirection.json diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 0161b149..265a6cbb 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -28,13 +28,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken { address public immutable UNDERLYING_ASSET_ADDRESS; - mapping(address => address) private _interestRedirectionAddresses; - mapping(address => uint256) private _interestRedirectionIndexes; mapping(address => uint256) private _scaledRedirectedBalances; - mapping(address => uint256) private _redirectedBalanceIndexes; - - mapping(address => address) private _interestRedirectionAllowances; LendingPool private immutable _pool; @@ -69,44 +64,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { _setDecimals(underlyingAssetDecimals); } - /** - * @dev redirects the interest generated to a target address. - * when the interest is redirected, the user balance is added to - * the recepient redirected balance. - * @param to the address to which the interest will be redirected - **/ - function redirectInterestStream(address to) external override { - _redirectInterestStream(msg.sender, to); - } - - /** - * @dev redirects the interest generated by from to a target address. - * when the interest is redirected, the user balance is added to - * the recepient redirected balance. The caller needs to have allowance on - * the interest redirection to be able to execute the function. - * @param from the address of the user whom interest is being redirected - * @param to the address to which the interest will be redirected - **/ - function redirectInterestStreamOf(address from, address to) external override { - require( - msg.sender == _interestRedirectionAllowances[from], - Errors.INTEREST_REDIRECTION_NOT_ALLOWED - ); - _redirectInterestStream(from, to); - } - - /** - * @dev gives allowance to an address to execute the interest redirection - * on behalf of the caller. - * @param to the address to which the interest will be redirected. Pass address(0) to reset - * the allowance. - **/ - function allowInterestRedirectionTo(address to) external override { - require(to != msg.sender, Errors.CANNOT_GIVE_ALLOWANCE_TO_HIMSELF); - _interestRedirectionAllowances[msg.sender] = to; - emit InterestRedirectionAllowanceChanged(msg.sender, to); - } - /** * @dev burns the aTokens and sends the equivalent amount of underlying to the target. * only lending pools can call this function @@ -128,16 +85,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { _burn(user, scaledAmount); - - if(amount == currentBalance){ - _resetDataOnZeroBalance(user); - } - - //if the user is redirecting his interest towards someone else, - //we update the redirected balance of the redirection address by adding the accrued interest, - //and removing the amount to redeem - _updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index); - //transfers the underlying to the target ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount); @@ -160,11 +107,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { //mint an equivalent amount of tokens to cover the new deposit _mint(user,scaledAmount); - //if the user is redirecting his interest towards someone else, - //we update the redirected balance of the redirection address by adding the accrued interest - //and the amount deposited - _updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index); - emit Mint(user, amount, index); } @@ -187,39 +129,17 @@ contract AToken is VersionedInitializable, ERC20, IAToken { /** * @dev calculates the balance of the user, which is the - * principal balance + interest generated by the principal balance + interest generated by the redirected balance + * principal balance + interest generated by the principal balance * @param user the user for which the balance is being calculated * @return the total balance of the user **/ function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) { - //current scaled balance of the user - uint256 currentScaledBalance = super.balanceOf(user); - - //balance redirected by other users to user for interest rate accrual - uint256 scaledRedirectedBalance = _scaledRedirectedBalances[user]; - - if (currentScaledBalance == 0 && scaledRedirectedBalance == 0) { - return 0; - } - uint256 actualBalanceRedirectedToUser = scaledRedirectedBalance > 0 ? scaledRedirectedBalance.rayMul(_redirectedBalanceIndexes[user]) : 0; uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - if(_interestRedirectionAddresses[user] == address(0)){ - //if the user is not redirecting the interest, his balance is the result of - //the interest accrued by his current scaled balance and the interest accrued by his - //scaled redirected balance - return currentScaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(actualBalanceRedirectedToUser); - } + return super.balanceOf(user).rayMul(index); - //if the user is redirecting, his balance only increases by the balance he is being redirected to - uint256 lastBalanceRedirectedByUser = currentScaledBalance.rayMul(_interestRedirectionIndexes[user]); - - return - lastBalanceRedirectedByUser.add( - scaledRedirectedBalance.rayMul(index) - ).sub(actualBalanceRedirectedToUser); } /** @@ -232,18 +152,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return super.balanceOf(user); } - - /** - * @dev returns the scaled balance of the user. The scaled balance is the sum of all the - * updated stored balance divided the reserve index at the moment of the update - * @param user the address of the user - * @return the scaled balance of the user - **/ - function getUserInterestRedirectionIndex(address user) external override view returns (uint256) { - return _interestRedirectionIndexes[user]; - } - - /** * @dev calculates the total supply of the specific aToken * since the balance of every single user increases over time, the total supply @@ -274,153 +182,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return _pool.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount); } - /** - * @dev returns the address to which the interest is redirected - * @param user address of the user - * @return 0 if there is no redirection, an address otherwise - **/ - function getInterestRedirectionAddress(address user) external override view returns (address) { - return _interestRedirectionAddresses[user]; - } - - /** - * @dev returns the redirected balance of the user. The redirected balance is the balance - * redirected by other accounts to the user, that is accrueing interest for him. - * @param user address of the user - * @return the total redirected balance - **/ - function getScaledRedirectedBalance(address user) external override view returns (uint256) { - return _scaledRedirectedBalances[user]; - } - - /** - * @dev returns the index stored during the last action that affected the redirected balance. - * scaledRedirectedBalance * redirectedBalanceIndex allows to calculate the actual redirected balance - * which is needed to calculate the interest accrued - * @param user address of the user - * @return the total redirected balance - **/ - function getRedirectedBalanceIndex(address user) external override view returns (uint256) { - return _redirectedBalanceIndexes[user]; - } - - - - /** - * @dev updates the redirected balance of the user. If the user is not redirecting his - * interest, nothing is executed. - * @param user the address of the user for which the interest is being accumulated - * @param scaledBalanceToAdd the amount to add to the redirected balance - * @param scaledBalanceToRemove the amount to remove from the redirected balance - **/ - function _updateRedirectedBalanceOfRedirectionAddress( - address origin, - address user, - uint256 scaledBalanceToAdd, - uint256 scaledBalanceToRemove, - uint256 index - ) internal { - address redirectionAddress = _interestRedirectionAddresses[user]; - //if there isn't any redirection, nothing to be done - if (redirectionAddress == address(0)) { - return; - } - - //updating the redirected balance - _scaledRedirectedBalances[redirectionAddress] = _scaledRedirectedBalances[redirectionAddress] - .add(scaledBalanceToAdd) - .sub(scaledBalanceToRemove); - - //updating the redirected balance index of the redirection target - _redirectedBalanceIndexes[redirectionAddress] = index; - - - //if the interest of redirectionAddress is also being redirected, we need to update - //the redirected balance of the redirection target by adding the balance increase - address targetOfRedirectionAddress = _interestRedirectionAddresses[redirectionAddress]; - - - // if the redirection address is also redirecting the interest, we update his index to - // accumulate the interest until now - // note: if the next address of redirection is the same as the one who originated the update, - // it means a loop of redirection has been formed: in this case, we break the recursion as no - // further updates are needed - if (targetOfRedirectionAddress != address(0) && targetOfRedirectionAddress != origin) { - _updateRedirectedBalanceOfRedirectionAddress(origin, redirectionAddress, 0, 0, index); - } - - console.log("Interest redirection completed"); - - emit RedirectedBalanceUpdated( - redirectionAddress, - scaledBalanceToAdd, - scaledBalanceToRemove, - index - ); - } - - /** - * @dev executes the redirection of the interest from one address to another. - * immediately after redirection, the destination address will start to accrue interest. - * @param from the address from which transfer the aTokens - * @param to the destination address - **/ - function _redirectInterestStream(address from, address to) internal { - - address currentRedirectionAddress = _interestRedirectionAddresses[from]; - - require(to != currentRedirectionAddress, Errors.INTEREST_ALREADY_REDIRECTED); - - uint256 scaledFromBalance = super.balanceOf(from); - - uint256 fromBalance = balanceOf(from); - - require(fromBalance > 0, Errors.NO_VALID_BALANCE_FOR_REDIRECTION); - - uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - - uint256 scaledBalance = fromBalance.rayDiv(index); - - //if the user is already redirecting the interest to someone, before changing - //the redirection address we substract the redirected balance of the previous - //recipient - if (currentRedirectionAddress != address(0)) { - _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledFromBalance, index); - } - - //if the user is redirecting the interest back to himself, - //we simply set to 0 the interest redirection address - if (to == from) { - _interestRedirectionAddresses[from] = address(0); - emit InterestStreamRedirected(from, address(0), scaledBalance, index); - return; - } - - //first set the redirection address to the new recipient - _interestRedirectionAddresses[from] = to; - _interestRedirectionIndexes[from] = index; - - //adds the user balance to the redirected balance of the destination - _updateRedirectedBalanceOfRedirectionAddress(from, from, scaledBalance, 0, index); - - emit InterestStreamRedirected(from, to, fromBalance, index); - } - - /** - * @dev function to reset the interest stream redirection and the user index, if the - * user has no balance left. - * @param user the address of the user - * @return true if the user index has also been reset, false otherwise. useful to emit the proper user index value - **/ - function _resetDataOnZeroBalance(address user) internal returns (bool) { - //if the user has 0 principal balance, the interest stream redirection gets reset - _interestRedirectionAddresses[user] = address(0); - _interestRedirectionIndexes[user] = 0; - - //emits a InterestStreamRedirected event to notify that the redirection has been reset - emit InterestStreamRedirected(user, address(0), 0, 0); - } - /** * @dev transfers the underlying asset to the target. Used by the lendingpool to transfer * assets in borrow(), redeem() and flashLoan() @@ -458,20 +219,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { super._transfer(from, to, scaledAmount); - //if the sender is redirecting his interest towards someone else, - //adds to the redirected balance the accrued interest and removes the amount - //being transferred - _updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledAmount, index); - - //if the receiver is redirecting his interest towards someone else, - //adds to the redirected balance the accrued interest and the amount - //being transferred - _updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index); - - if(super.balanceOf(from) == 0){ - _resetDataOnZeroBalance(from); - } - emit BalanceTransfer(from, to, amount, index); } diff --git a/contracts/tokenization/interfaces/IAToken.sol b/contracts/tokenization/interfaces/IAToken.sol index 68b70e60..5085474a 100644 --- a/contracts/tokenization/interfaces/IAToken.sol +++ b/contracts/tokenization/interfaces/IAToken.sol @@ -39,63 +39,6 @@ interface IAToken is IERC20 { uint256 index ); - /** - * @dev emitted when the accumulation of the interest - * by an user is redirected to another user - * @param from the address from which the interest is being redirected - * @param to the adress of the destination - * @param redirectedBalance the scaled balance being redirected - * @param index the last index of the reserve - **/ - event InterestStreamRedirected( - address indexed from, - address indexed to, - uint256 redirectedBalance, - uint256 index - ); - - /** - * @dev emitted when the redirected balance of an user is being updated - * @param targetAddress the address of which the balance is being updated - * @param redirectedBalanceAdded the redirected balance being added - * @param redirectedBalanceRemoved the redirected balance being removed - * @param index the last index of the reserve - **/ - event RedirectedBalanceUpdated( - address indexed targetAddress, - uint256 redirectedBalanceAdded, - uint256 redirectedBalanceRemoved, - uint256 index - ); - - event InterestRedirectionAllowanceChanged(address indexed from, address indexed to); - - /** - * @dev redirects the interest generated to a target address. - * when the interest is redirected, the user balance is added to - * the recepient redirected balance. - * @param to the address to which the interest will be redirected - **/ - function redirectInterestStream(address to) external; - - /** - * @dev redirects the interest generated by from to a target address. - * when the interest is redirected, the user balance is added to - * the recepient redirected balance. The caller needs to have allowance on - * the interest redirection to be able to execute the function. - * @param from the address of the user whom interest is being redirected - * @param to the address to which the interest will be redirected - **/ - function redirectInterestStreamOf(address from, address to) external; - - /** - * @dev gives allowance to an address to execute the interest redirection - * on behalf of the caller. - * @param to the address to which the interest will be redirected. Pass address(0) to reset - * the allowance. - **/ - function allowInterestRedirectionTo(address to) external; - /** * @dev burns the aTokens and sends the equivalent amount of underlying to the target. * only lending pools can call this function @@ -145,42 +88,11 @@ interface IAToken is IERC20 { function isTransferAllowed(address user, uint256 amount) external view returns (bool); /** - * @dev returns the address to which the interest is redirected - * @param user address of the user - * @return 0 if there is no redirection, an address otherwise - **/ - function getInterestRedirectionAddress(address user) external view returns (address); - - /** - * @dev returns the index of the user at the moment of redirection - * @param user address of the user - * @return interest redirection index - **/ - function getUserInterestRedirectionIndex(address user) external view returns (uint256); - - /** - * @dev returns the scaled redirected balance of the user. The scaled redirected balance is the sum of all the redirected balances - * divided by the index at the moment of each specific redirection. - * @param user address of the user - * @return the total redirected balance - **/ - function getScaledRedirectedBalance(address user) external view returns (uint256); - - /** - * @dev returns the redirected balance index. The redirected balance index is the - * index at the moment the last scaled redirected balance has been performed, and allows to calculate the actual redirected balance + * @dev transfer the amount of the underlying asset to the user * @param user address of the user + * @param amount the amount to transfer * @return the redirected balance index **/ - function getRedirectedBalanceIndex(address user) external view returns (uint256); - /** - * @dev transfers the underlying asset to the target. Used by the lendingpool to transfer - * assets in borrow(), redeem() and flashLoan() - * @param target the target of the transfer - * @param amount the amount to transfer - * @return the amount transferred - **/ - - function transferUnderlyingTo(address target, uint256 amount) external returns (uint256); + function transferUnderlyingTo(address user, uint256 amount) external returns (uint256); } diff --git a/test/atoken-transfer.spec.ts b/test/atoken-transfer.spec.ts index 84cfb6b2..f4b25065 100644 --- a/test/atoken-transfer.spec.ts +++ b/test/atoken-transfer.spec.ts @@ -47,50 +47,6 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { ); }); - it('User 1 redirects interest to user 2, transfers 500 DAI back to user 0', async () => { - const {users, aDai, dai} = testEnv; - await aDai.connect(users[1].signer).redirectInterestStream(users[2].address); - - const aDAIRedirected = await convertToCurrencyDecimals(dai.address, '1000'); - - const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '500'); - - const user2RedirectedBalanceBefore = await aDai.getRedirectedBalance(users[2].address); - expect(user2RedirectedBalanceBefore.toString()).to.be.equal( - aDAIRedirected, - INVALID_REDIRECTED_BALANCE_BEFORE_TRANSFER - ); - - await aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer); - - const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address); - const user1RedirectionAddress = await aDai.getInterestRedirectionAddress(users[1].address); - - expect(user2RedirectedBalanceAfter.toString()).to.be.equal( - aDAItoTransfer, - INVALID_REDIRECTED_BALANCE_BEFORE_TRANSFER - ); - expect(user1RedirectionAddress.toString()).to.be.equal( - users[2].address, - INVALID_REDIRECTION_ADDRESS - ); - }); - - it('User 0 transfers back to user 1', async () => { - const {users, aDai, dai, weth} = testEnv; - const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '500'); - - await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer); - - const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address); - - const user1BalanceAfter = await aDai.balanceOf(users[1].address); - - expect(user2RedirectedBalanceAfter.toString()).to.be.equal( - user1BalanceAfter.toString(), - INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER - ); - }); it('User 0 deposits 1 WETH and user 1 tries to borrow, but the aTokens received as a transfer are not available as collateral (revert expected)', async () => { const {users, pool, weth} = testEnv; @@ -124,79 +80,4 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { ).to.be.revertedWith(TRANSFER_NOT_ALLOWED); }); - it('User 1 repays the borrow, transfers aDAI back to user 0', async () => { - const {users, pool, aDai, dai, weth} = testEnv; - - await weth.connect(users[1].signer).mint(await convertToCurrencyDecimals(weth.address, '2')); - - await weth.connect(users[1].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - - await pool - .connect(users[1].signer) - .repay(weth.address, MAX_UINT_AMOUNT, RateMode.Stable, users[1].address); - - const aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '1000'); - - await aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer); - - const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address); - - const user1RedirectionAddress = await aDai.getInterestRedirectionAddress(users[1].address); - - expect(user2RedirectedBalanceAfter.toString()).to.be.equal( - '0', - INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER - ); - - expect(user1RedirectionAddress.toString()).to.be.equal( - ZERO_ADDRESS, - INVALID_REDIRECTION_ADDRESS - ); - }); - - it('User 0 redirects interest to user 2, transfers 500 aDAI to user 1. User 1 redirects to user 3. User 0 transfers another 100 aDAI', async () => { - const {users, pool, aDai, dai, weth} = testEnv; - - let aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '500'); - - await aDai.connect(users[0].signer).redirectInterestStream(users[2].address); - - await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer); - - await aDai.connect(users[1].signer).redirectInterestStream(users[3].address); - - aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '100'); - - await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer); - - const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address); - const user3RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[3].address); - - const expectedUser2Redirected = await convertToCurrencyDecimals(aDai.address, '400'); - const expectedUser3Redirected = await convertToCurrencyDecimals(aDai.address, '600'); - - expect(user2RedirectedBalanceAfter.toString()).to.be.equal( - expectedUser2Redirected, - INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER - ); - expect(user3RedirectedBalanceAfter.toString()).to.be.equal( - expectedUser3Redirected, - INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER - ); - }); - - it('User 1 transfers the whole amount to himself', async () => { - const {users, pool, aDai, dai} = testEnv; - - const user1BalanceBefore = await aDai.balanceOf(users[1].address); - - await aDai.connect(users[1].signer).transfer(users[1].address, user1BalanceBefore); - - const user1BalanceAfter = await aDai.balanceOf(users[1].address); - - expect(user1BalanceAfter.toString()).to.be.equal( - user1BalanceBefore, - INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER - ); - }); }); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 6e654349..6cf5364f 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -14,7 +14,6 @@ import { calcExpectedUserDataAfterStableRateRebalance, calcExpectedUserDataAfterSwapRateMode, calcExpectedUserDataAfterWithdraw, - calcExpectedUsersDataAfterRedirectInterest, } from './utils/calculations'; import {getReserveAddressFromSymbol, getReserveData, getUserData} from './utils/helpers'; @@ -652,156 +651,6 @@ export const rebalanceStableBorrowRate = async ( } }; -export const redirectInterestStream = async ( - reserveSymbol: string, - user: SignerWithAddress, - to: tEthereumAddress, - expectedResult: string, - testEnv: TestEnv, - revertMessage?: string -) => { - const { - aTokenInstance, - reserve, - userData: fromDataBefore, - reserveData: reserveDataBefore, - } = await getDataBeforeAction(reserveSymbol, user.address, testEnv); - - const {userData: toDataBefore} = await getContractsData(reserve, to, testEnv); - - if (expectedResult === 'success') { - const txResult = await waitForTx( - await aTokenInstance.connect(user.signer).redirectInterestStream(to) - ); - - const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - - const {userData: fromDataAfter} = await getContractsData(reserve, user.address, testEnv); - - const {userData: toDataAfter} = await getContractsData(reserve, to, testEnv); - - const [expectedFromData, expectedToData] = calcExpectedUsersDataAfterRedirectInterest( - reserveDataBefore, - fromDataBefore, - toDataBefore, - user.address, - to, - true, - txCost, - txTimestamp - ); - - console.log("Checking from data"); - - expectEqual(fromDataAfter, expectedFromData); - console.log("Checking to data"); - expectEqual(toDataAfter, expectedToData); - - // truffleAssert.eventEmitted(txResult, 'InterestStreamRedirected', (ev: any) => { - // const {_from, _to} = ev; - // return _from === user - // && _to === (to === user ? NIL_ADDRESS : to); - // }); - } else if (expectedResult === 'revert') { - await expect(aTokenInstance.connect(user.signer).redirectInterestStream(to), revertMessage).to - .be.reverted; - } -}; - -export const redirectInterestStreamOf = async ( - reserveSymbol: string, - user: SignerWithAddress, - from: tEthereumAddress, - to: tEthereumAddress, - expectedResult: string, - testEnv: TestEnv, - revertMessage?: string -) => { - const { - aTokenInstance, - reserve, - userData: fromDataBefore, - reserveData: reserveDataBefore, - } = await getDataBeforeAction(reserveSymbol, from, testEnv); - - const {userData: toDataBefore} = await getContractsData(reserve, user.address, testEnv); - - if (expectedResult === 'success') { - const txResult = await waitForTx( - await aTokenInstance.connect(user.signer).redirectInterestStreamOf(from, to) - ); - - const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - - const {userData: fromDataAfter} = await getContractsData(reserve, from, testEnv); - - const {userData: toDataAfter} = await getContractsData(reserve, to, testEnv); - - const [expectedFromData, exptectedToData] = calcExpectedUsersDataAfterRedirectInterest( - reserveDataBefore, - fromDataBefore, - toDataBefore, - from, - to, - from === user.address, - txCost, - txTimestamp - ); - - expectEqual(fromDataAfter, expectedFromData); - expectEqual(toDataAfter, exptectedToData); - - // truffleAssert.eventEmitted( - // txResult, - // "InterestStreamRedirected", - // (ev: any) => { - // const {_from, _to} = ev; - // return ( - // _from.toLowerCase() === from.toLowerCase() && - // _to.toLowerCase() === to.toLowerCase() - // ); - // } - // ); - } else if (expectedResult === 'revert') { - await expect( - aTokenInstance.connect(user.signer).redirectInterestStreamOf(from, to), - revertMessage - ).to.be.reverted; - } -}; - -export const allowInterestRedirectionTo = async ( - reserveSymbol: string, - user: SignerWithAddress, - to: tEthereumAddress, - expectedResult: string, - testEnv: TestEnv, - revertMessage?: string -) => { - const {aTokenInstance} = await getDataBeforeAction(reserveSymbol, user.address, testEnv); - - if (expectedResult === 'success') { - const txResult = await waitForTx( - await aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to) - ); - - // truffleAssert.eventEmitted( - // txResult, - // "InterestRedirectionAllowanceChanged", - // (ev: any) => { - // const {_from, _to} = ev; - // return ( - // _from.toLowerCase() === user.toLowerCase() && - // _to.toLowerCase() === to.toLowerCase() - // ); - // } - // ); - } else if (expectedResult === 'revert') { - await expect(aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to), revertMessage) - .to.be.reverted; - } -}; - const expectEqual = ( actual: UserReserveData | ReserveData, expected: UserReserveData | ReserveData diff --git a/test/helpers/scenario-engine.ts b/test/helpers/scenario-engine.ts index 735d3b84..11fe8f10 100644 --- a/test/helpers/scenario-engine.ts +++ b/test/helpers/scenario-engine.ts @@ -8,10 +8,7 @@ import { repay, setUseAsCollateral, swapBorrowRateMode, - rebalanceStableBorrowRate, - redirectInterestStream, - redirectInterestStreamOf, - allowInterestRedirectionTo, + rebalanceStableBorrowRate } from './actions'; import {RateMode} from '../../helpers/types'; @@ -185,71 +182,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv await rebalanceStableBorrowRate(reserve, user, target, expected, testEnv, revertMessage); } break; - - case 'redirectInterestStream': - { - const {to: toIndex} = action.args; - - if (!toIndex || toIndex === '') { - throw `A target must be selected when trying to redirect the interest`; - } - const toUser = users[parseInt(toIndex)]; - - await redirectInterestStream( - reserve, - user, - toUser.address, - expected, - testEnv, - revertMessage - ); - } - break; - - case 'redirectInterestStreamOf': - { - const {from: fromIndex, to: toIndex} = action.args; - - if (!fromIndex || fromIndex === '') { - throw `A from address must be specified when trying to redirect the interest`; - } - if (!toIndex || toIndex === '') { - throw `A target must be selected when trying to redirect the interest`; - } - const toUser = users[parseInt(toIndex)]; - const fromUser = users[parseInt(fromIndex)]; - - await redirectInterestStreamOf( - reserve, - user, - fromUser.address, - toUser.address, - expected, - testEnv, - revertMessage - ); - } - break; - - case 'allowInterestRedirectionTo': - { - const {to: toIndex} = action.args; - - if (!toIndex || toIndex === '') { - throw `A target must be selected when trying to redirect the interest`; - } - const toUser = users[parseInt(toIndex)]; - - await allowInterestRedirectionTo( - reserve, - user, - toUser.address, - expected, - testEnv, - revertMessage - ); - } - break; + default: throw `Invalid action requested: ${name}`; } diff --git a/test/helpers/scenarios/interest-redirection-negatives.json b/test/helpers/scenarios/interest-redirection-negatives.json deleted file mode 100644 index 91b6a56f..00000000 --- a/test/helpers/scenarios/interest-redirection-negatives.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "title": "AToken: interest rate redirection negative test cases", - "description": "Test cases for the aToken interest rate redirection.", - "stories": [ - { - "description": "User 0 deposits 1000 DAI, tries to give allowance to redirect interest to himself (revert expected)", - "actions": [ - { - "name": "mint", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - }, - { - "name": "approve", - "args": { - "reserve": "DAI", - "user": "0" - }, - "expected": "success" - }, - { - "name": "deposit", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - }, - { - "name": "allowInterestRedirectionTo", - "args": { - "reserve": "DAI", - "user": "0", - "to": "0" - }, - "expected": "revert", - "revertMessage": "User cannot give allowance to himself" - } - ] - }, - { - "description": "User 1 tries to redirect the interest of user 0 without allowance (revert expected)", - "actions": [ - { - "name": "redirectInterestStreamOf", - "args": { - "reserve": "DAI", - "user": "1", - "from": "0", - "to": "2" - }, - "expected": "revert", - "revertMessage": "Caller is not allowed to redirect the interest of the user" - } - ] - }, - { - "description": "User 0 tries to redirect the interest to user 2 twice (revert expected)", - "actions": [ - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "0", - "to": "2" - }, - "expected": "success" - }, - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "0", - "to": "2" - }, - "expected": "revert", - "revertMessage": "Interest is already redirected to the user" - } - ] - }, - { - "description": "User 3 with 0 balance tries to redirect the interest to user 2 (revert expected)", - "actions": [ - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "3", - "to": "2" - }, - "expected": "revert", - "revertMessage": "Interest stream can only be redirected if there is a valid balance" - } - ] - } - ] -} diff --git a/test/helpers/scenarios/interest-redirection.json b/test/helpers/scenarios/interest-redirection.json deleted file mode 100644 index 8ed00d1f..00000000 --- a/test/helpers/scenarios/interest-redirection.json +++ /dev/null @@ -1,405 +0,0 @@ -{ - "title": "AToken: interest rate redirection", - "description": "Test cases for the aToken interest rate redirection.", - "stories": [ - { - "description": "User 0 deposits 1000 DAI, redirects the interest to user 2", - "actions": [ - { - "name": "mint", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - }, - { - "name": "approve", - "args": { - "reserve": "DAI", - "user": "0" - }, - "expected": "success" - }, - { - "name": "deposit", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - }, - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "0", - "to": "2" - }, - "expected": "success" - } - ] - }, - { - "description": "User 1 deposits 1 ETH, borrows 100 DAI, repays after one year. Users 0 deposits another 1000 DAI. Redirected balance of user 2 is updated", - "actions": [ - { - "name": "mint", - "args": { - "reserve": "WETH", - "amount": "2", - "user": "1" - }, - "expected": "success" - }, - { - "name": "approve", - "args": { - "reserve": "WETH", - "user": "1" - }, - "expected": "success" - }, - { - "name": "deposit", - "args": { - "reserve": "WETH", - - "amount": "2", - "user": "1" - }, - "expected": "success" - }, - { - "name": "borrow", - "args": { - "reserve": "DAI", - "amount": "100", - "borrowRateMode": "stable", - "user": "1", - "timeTravel": "365" - }, - "expected": "success" - }, - { - "name": "mint", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - }, - { - "name": "deposit", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - } - ] - }, - { - "description": "User 1 borrows another 100 DAI, repay the whole amount. Users 0 and User 2 withdraw", - "actions": [ - { - "name": "borrow", - "args": { - "reserve": "DAI", - "amount": "100", - "borrowRateMode": "stable", - "user": "1", - "timeTravel": "365" - }, - "expected": "success" - }, - { - "name": "mint", - "args": { - "reserve": "DAI", - "amount": "100", - "user": "1" - }, - "expected": "success" - }, - { - "name": "approve", - "args": { - "reserve": "DAI", - "user": "1" - }, - "expected": "success" - }, - { - "name": "repay", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "1", - "onBehalfOf": "1", - "borrowRateMode": "stable" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "0" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "2" - }, - "expected": "success" - } - ] - }, - { - "description": "User 0 deposits 1000 DAI, redirects interest to user 2, user 1 borrows 100 DAI. After one year, user 0 redirects interest back to himself, user 1 borrows another 100 DAI and after another year repays the whole amount. Users 0 and User 2 withdraw", - "actions": [ - { - "name": "deposit", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - }, - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "0", - "to": "2" - }, - "expected": "success" - }, - { - "name": "borrow", - "args": { - "reserve": "DAI", - "amount": "100", - "borrowRateMode": "stable", - "user": "1", - "timeTravel": "365" - }, - "expected": "success" - }, - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "0", - "to": "0" - }, - "expected": "success" - }, - { - "name": "borrow", - "args": { - "reserve": "DAI", - "amount": "100", - "borrowRateMode": "stable", - "user": "1", - "timeTravel": "365" - }, - "expected": "success" - }, - { - "name": "mint", - "args": { - "reserve": "DAI", - "amount": "100", - "user": "1" - }, - "expected": "success" - }, - { - "name": "approve", - "args": { - "reserve": "DAI", - "user": "1" - }, - "expected": "success" - }, - { - "name": "repay", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "1", - "onBehalfOf": "1", - "borrowRateMode": "stable" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "0" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "2" - }, - "expected": "success" - } - ] - }, - { - "description": "User 0 deposits 1000 DAI, redirects interest to user 2, user 1 borrows 100 DAI. After one year, user 2 redirects interest to user 3. user 1 borrows another 100 DAI, user 0 deposits another 100 DAI. User 1 repays the whole amount. Users 0, 2 first 1 DAI, then everything. User 3 withdraws", - "actions": [ - { - "name": "deposit", - "args": { - "reserve": "DAI", - "amount": "1000", - "user": "0" - }, - "expected": "success" - }, - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "0", - "to": "2" - }, - "expected": "success" - }, - { - "name": "borrow", - "args": { - "reserve": "DAI", - "amount": "100", - "borrowRateMode": "stable", - "user": "1", - "timeTravel": "365" - }, - "expected": "success" - }, - { - "name": "redirectInterestStream", - "args": { - "reserve": "DAI", - "user": "2", - "to": "3" - }, - "expected": "success" - }, - { - "name": "borrow", - "args": { - "reserve": "DAI", - "amount": "100", - "borrowRateMode": "stable", - "user": "1", - "timeTravel": "365" - }, - "expected": "success" - }, - { - "name": "deposit", - "args": { - "reserve": "DAI", - "amount": "100", - "user": "0" - }, - "expected": "success" - }, - { - "name": "mint", - "args": { - "reserve": "DAI", - "amount": "100", - "user": "1" - }, - "expected": "success" - }, - { - "name": "approve", - "args": { - "reserve": "DAI", - "user": "1" - }, - "expected": "success" - }, - { - "name": "repay", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "1", - "onBehalfOf": "1", - "borrowRateMode": "stable" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "1", - "user": "0" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "1", - "user": "2" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "0" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "2" - }, - "expected": "success" - }, - { - "name": "withdraw", - "args": { - "reserve": "DAI", - "amount": "-1", - "user": "3" - }, - "expected": "success" - } - ] - } - ] -} diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index c8b3e1d4..502d93be 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -70,15 +70,6 @@ export const calcExpectedUserDataAfterDeposit = ( expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex; expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited); - expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; - expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; - expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.interestRedirectionIndex = - userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS - ? new BigNumber(0) - : reserveDataAfterAction.liquidityIndex; - - expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance( userDataBeforeAction, txTimestamp @@ -90,14 +81,6 @@ export const calcExpectedUserDataAfterDeposit = ( txTimestamp ); - expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance( - expectedUserData, - reserveDataAfterAction.liquidityIndex, - userDataBeforeAction.redirectionAddressScaledRedirectedBalance, - new BigNumber(amountDeposited), - new BigNumber(0) - ); - return expectedUserData; }; @@ -163,28 +146,6 @@ export const calcExpectedUserDataAfterWithdraw = ( } expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountWithdrawn); - expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; - expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; - - - if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.scaledRedirectedBalance.eq(0)) { - expectedUserData.interestRedirectionAddress = ZERO_ADDRESS; - expectedUserData.interestRedirectionIndex = new BigNumber(0); - } else { - expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.interestRedirectionIndex = - userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS - ? new BigNumber(0) - : reserveDataAfterAction.liquidityIndex; - } - - expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance( - expectedUserData, - reserveDataAfterAction.liquidityIndex, - userDataBeforeAction.redirectionAddressScaledRedirectedBalance, - new BigNumber(0), - new BigNumber(amountWithdrawn) - ); return expectedUserData; }; @@ -578,14 +539,7 @@ export const calcExpectedUserDataAfterBorrow = ( currentTimestamp ); expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; - expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; - expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; - expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex; - expectedUserData.redirectionAddressScaledRedirectedBalance = - userDataBeforeAction.redirectionAddressScaledRedirectedBalance; - expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex; - + expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed); return expectedUserData; @@ -669,14 +623,7 @@ export const calcExpectedUserDataAfterRepay = ( txTimestamp ); expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; - expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex; - expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance; - expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress; - expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex; - expectedUserData.redirectionAddressScaledRedirectedBalance = - userDataBeforeAction.redirectionAddressScaledRedirectedBalance; - expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex; - + if (user === onBehalfOf) { expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(totalRepaid); } else { @@ -961,78 +908,6 @@ export const calcExpectedUserDataAfterStableRateRebalance = ( return expectedUserData; }; -export const calcExpectedUsersDataAfterRedirectInterest = ( - reserveDataBeforeAction: ReserveData, - fromDataBeforeAction: UserReserveData, - toDataBeforeAction: UserReserveData, - fromAddress: string, - toAddress: string, - isFromExecutingTx: boolean, - txCost: BigNumber, - txTimestamp: BigNumber -): UserReserveData[] => { - const expectedFromData = { ...fromDataBeforeAction }; - const expectedToData = { ...toDataBeforeAction }; - - const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, txTimestamp); - - expectedFromData.currentStableDebt = calcExpectedStableDebtTokenBalance( - fromDataBeforeAction, - txTimestamp - ); - - expectedToData.currentVariableDebt = calcExpectedVariableDebtTokenBalance( - reserveDataBeforeAction, - toDataBeforeAction, - txTimestamp - ); - - expectedFromData.variableBorrowIndex = fromDataBeforeAction.variableBorrowIndex; - expectedToData.variableBorrowIndex = toDataBeforeAction.variableBorrowIndex; - - expectedFromData.stableBorrowRate = fromDataBeforeAction.stableBorrowRate; - expectedToData.stableBorrowRate = toDataBeforeAction.stableBorrowRate; - - expectedFromData.scaledATokenBalance = fromDataBeforeAction.scaledATokenBalance; - - expectedFromData.currentATokenBalance = calcExpectedATokenBalance( - reserveDataBeforeAction, - fromDataBeforeAction, - txTimestamp - ); - - - expectedToData.scaledRedirectedBalance = toDataBeforeAction.scaledRedirectedBalance.plus( - expectedFromData.scaledATokenBalance - ); - - expectedToData.redirectedBalanceIndex = index; - - if (fromAddress === toAddress) { - expectedFromData.interestRedirectionAddress = ZERO_ADDRESS; - expectedFromData.scaledRedirectedBalance = new BigNumber(0); - expectedFromData.redirectedBalanceIndex = new BigNumber(0); - - expectedFromData.redirectionAddressScaledRedirectedBalance = new BigNumber(0); - expectedToData.interestRedirectionAddress = ZERO_ADDRESS; - expectedToData.scaledRedirectedBalance = new BigNumber(0); - expectedToData.redirectionAddressScaledRedirectedBalance = new BigNumber(0); - } else { - expectedFromData.interestRedirectionAddress = toAddress; - expectedFromData.interestRedirectionIndex = index; - - expectedFromData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance( - expectedFromData, - index, - toDataBeforeAction.scaledRedirectedBalance, - expectedFromData.currentATokenBalance, - new BigNumber(0) - ); - } - - return [expectedFromData, expectedToData]; -}; - const calcExpectedScaledATokenBalance = ( userDataBeforeAction: UserReserveData, index: BigNumber, @@ -1052,27 +927,10 @@ const calcExpectedATokenBalance = ( const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp); const { - interestRedirectionAddress, - interestRedirectionIndex: redirectionIndexBeforeAction, - scaledRedirectedBalance, - redirectedBalanceIndex: redirectedBalanceIndexBeforeAction, scaledATokenBalance: scaledBalanceBeforeAction, } = userDataBeforeAction; - if (scaledBalanceBeforeAction.eq(0) && scaledRedirectedBalance.eq(0)) { - return new BigNumber(0); - } - - const actualRedirectedBalance = scaledRedirectedBalance.gt(0) ? scaledRedirectedBalance.rayMul(redirectedBalanceIndexBeforeAction) : new BigNumber(0); - - if (interestRedirectionAddress === ZERO_ADDRESS) { - return scaledBalanceBeforeAction.plus(scaledRedirectedBalance).rayMul(index).minus(actualRedirectedBalance); - } - - - const lastRedirectedBalance = scaledBalanceBeforeAction.rayMul(redirectionIndexBeforeAction); - - return lastRedirectedBalance.plus(scaledRedirectedBalance.rayMul(index).minus(actualRedirectedBalance)); + return scaledBalanceBeforeAction.rayMul(index); }; const calcExpectedRedirectedBalance = ( diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index 008b904e..f1a1d206 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -63,30 +63,17 @@ export const getUserData = async ( reserve: string, user: string ): Promise => { - const [userData, aTokenData] = await Promise.all([ + const [userData, scaledATokenBalance] = await Promise.all([ pool.getUserReserveData(reserve, user), getATokenUserData(reserve, user, pool), ]); - const [ - scaledRedirectedBalance, - redirectedBalanceIndex, - scaledATokenBalance, - redirectionAddressScaledRedirectedBalance, - interestRedirectionAddress, - interestRedirectionIndex, - ] = aTokenData; - + const token = await getMintableErc20(reserve); const walletBalance = new BigNumber((await token.balanceOf(user)).toString()); return { scaledATokenBalance: new BigNumber(scaledATokenBalance), - interestRedirectionAddress, - interestRedirectionIndex: new BigNumber(interestRedirectionIndex), - redirectionAddressScaledRedirectedBalance: new BigNumber(redirectionAddressScaledRedirectedBalance), - scaledRedirectedBalance: new BigNumber(scaledRedirectedBalance), - redirectedBalanceIndex: new BigNumber(redirectedBalanceIndex), currentATokenBalance: new BigNumber(userData.currentATokenBalance.toString()), currentStableDebt: new BigNumber(userData.currentStableDebt.toString()), currentVariableDebt: new BigNumber(userData.currentVariableDebt.toString()), @@ -116,31 +103,8 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo const aTokenAddress: string = (await pool.getReserveTokensAddresses(reserve)).aTokenAddress; const aToken = await getAToken(aTokenAddress); - const [ - interestRedirectionAddress, - scaledRedirectedBalance, - redirectedBalanceIndex, - scaledATokenBalance, - interestRedirectionIndex - ] = await Promise.all([ - aToken.getInterestRedirectionAddress(user), - aToken.getScaledRedirectedBalance(user), - aToken.getRedirectedBalanceIndex(user), - aToken.scaledBalanceOf(user), - aToken.getUserInterestRedirectionIndex(user) - ]); - const redirectionAddressScaledRedirectedBalance = - interestRedirectionAddress !== ZERO_ADDRESS - ? new BigNumber((await aToken.getScaledRedirectedBalance(interestRedirectionAddress)).toString()) - : new BigNumber('0'); + const scaledBalance = await aToken.scaledBalanceOf(user); + return scaledBalance.toString(); - return [ - scaledRedirectedBalance.toString(), - redirectedBalanceIndex.toString(), - scaledATokenBalance.toString(), - redirectionAddressScaledRedirectedBalance.toString(), - interestRedirectionAddress, - interestRedirectionIndex.toString() - ]; }; diff --git a/test/helpers/utils/interfaces/index.ts b/test/helpers/utils/interfaces/index.ts index 7aa89910..7042bbea 100644 --- a/test/helpers/utils/interfaces/index.ts +++ b/test/helpers/utils/interfaces/index.ts @@ -3,11 +3,6 @@ import BigNumber from 'bignumber.js'; export interface UserReserveData { scaledATokenBalance: BigNumber; currentATokenBalance: BigNumber; - interestRedirectionAddress: string; - interestRedirectionIndex: BigNumber; - redirectionAddressScaledRedirectedBalance: BigNumber; - scaledRedirectedBalance: BigNumber; - redirectedBalanceIndex: BigNumber; currentStableDebt: BigNumber; currentVariableDebt: BigNumber; principalStableDebt: BigNumber; diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index bc295986..5d449d76 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); const scenarioFolder = './test/helpers/scenarios/'; -const selectedScenarios: string[] = ['interest-redirection.json']; +const selectedScenarios: string[] = []; fs.readdirSync(scenarioFolder).forEach((file) => { if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return; From 113c481512dca429c12ee50be3a1d25f542f5830 Mon Sep 17 00:00:00 2001 From: David Racero Date: Wed, 9 Sep 2020 21:24:20 +0200 Subject: [PATCH 26/31] Fixed coverage random failing tests. Added coverage network and minimal config. --- .solcover.js | 2 +- buidler.config.ts | 3 +++ helpers/contracts-helpers.ts | 11 ++++------- package.json | 2 +- .../flash-liquidation-with-collateral.spec.ts | 19 +++++++++++-------- test/liquidation-atoken.spec.ts | 9 +++++---- test/liquidation-underlying.spec.ts | 17 ++++++++++------- 7 files changed, 35 insertions(+), 28 deletions(-) diff --git a/.solcover.js b/.solcover.js index b63d9c64..9235302a 100644 --- a/.solcover.js +++ b/.solcover.js @@ -2,7 +2,7 @@ const accounts = require(`./test-wallets.js`).accounts; module.exports = { client: require('ganache-cli'), - skipFiles: [], + skipFiles: ['./mocks', './interfaces'], mocha: { enableTimeouts: false, }, diff --git a/buidler.config.ts b/buidler.config.ts index d21f5293..be2d98f4 100644 --- a/buidler.config.ts +++ b/buidler.config.ts @@ -57,6 +57,9 @@ const config: any = { timeout: 0, }, networks: { + coverage: { + url: 'http://localhost:8555', + }, kovan: getCommonNetworkConfig(eEthereumNetwork.kovan, 42), ropsten: getCommonNetworkConfig(eEthereumNetwork.ropsten, 3), main: getCommonNetworkConfig(eEthereumNetwork.main, 1), diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 909676a1..4bebd442 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -31,11 +31,11 @@ import BigNumber from 'bignumber.js'; import {Ierc20Detailed} from '../types/Ierc20Detailed'; import {StableDebtToken} from '../types/StableDebtToken'; import {VariableDebtToken} from '../types/VariableDebtToken'; -import { MockSwapAdapter } from '../types/MockSwapAdapter'; +import {MockSwapAdapter} from '../types/MockSwapAdapter'; export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => { const currentNetwork = BRE.network.name; - if (currentNetwork !== 'buidlerevm' && currentNetwork !== 'soliditycoverage') { + if (currentNetwork !== 'buidlerevm' && !currentNetwork.includes('coverage')) { console.log(`*** ${contractId} ***\n`); console.log(`Network: ${currentNetwork}`); console.log(`tx: ${contractInstance.deployTransaction.hash}`); @@ -214,9 +214,7 @@ export const deployMockFlashLoanReceiver = async (addressesProvider: tEthereumAd ]); export const deployMockSwapAdapter = async (addressesProvider: tEthereumAddress) => - await deployContract(eContractid.MockSwapAdapter, [ - addressesProvider, - ]); + await deployContract(eContractid.MockSwapAdapter, [addressesProvider]); export const deployWalletBalancerProvider = async (addressesProvider: tEthereumAddress) => await deployContract(eContractid.WalletBalanceProvider, [ @@ -397,8 +395,7 @@ export const getMockSwapAdapter = async (address?: tEthereumAddress) => { return await getContract( eContractid.MockSwapAdapter, address || - (await getDb().get(`${eContractid.MockSwapAdapter}.${BRE.network.name}`).value()) - .address + (await getDb().get(`${eContractid.MockSwapAdapter}.${BRE.network.name}`).value()).address ); }; diff --git a/package.json b/package.json index 30e7d371..fd71a369 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts", "test-repay-with-collateral": "buidler test test/__setup.spec.ts test/repay-with-collateral.spec.ts", "test-liquidate-with-collateral": "buidler test test/__setup.spec.ts test/flash-liquidation-with-collateral.spec.ts", - "dev:coverage": "buidler coverage", + "dev:coverage": "buidler coverage --network coverage", "dev:deployment": "buidler dev-deployment", "dev:deployExample": "buidler deploy-Example", "dev:prettier": "prettier --write .", diff --git a/test/flash-liquidation-with-collateral.spec.ts b/test/flash-liquidation-with-collateral.spec.ts index 2ffaa936..f74e442f 100644 --- a/test/flash-liquidation-with-collateral.spec.ts +++ b/test/flash-liquidation-with-collateral.spec.ts @@ -8,10 +8,11 @@ import { } from './helpers/utils/calculations'; import {getContractsData} from './helpers/actions'; import {waitForTx} from './__setup.spec'; -import {timeLatest} from '../helpers/misc-utils'; +import {timeLatest, BRE, increaseTime} from '../helpers/misc-utils'; import {tEthereumAddress, ProtocolErrors} from '../helpers/types'; import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; import {formatUnits, formatEther} from 'ethers/lib/utils'; +import {buidlerArguments} from '@nomiclabs/buidler'; const {expect} = require('chai'); const {parseUnits, parseEther} = ethers.utils; @@ -467,7 +468,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn it('User 2 deposit WETH and borrows DAI at Variable', async () => { const {pool, weth, dai, users, oracle} = testEnv; const user = users[1]; - const amountToDeposit = ethers.utils.parseEther('1'); + const amountToDeposit = ethers.utils.parseEther('2'); await weth.connect(user.signer).mint(amountToDeposit); @@ -483,7 +484,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn dai.address, new BigNumber(userGlobalData.availableBorrowsETH.toString()) .div(daiPrice.toString()) - .multipliedBy(0.95) + .multipliedBy(0.9) .toFixed(0) ); @@ -591,7 +592,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn ); // First half - const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.dividedBy(2).toString(); + const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.multipliedBy(0.6).toString(); await mockSwapAdapter.setAmountToReturn(amountToRepay); await waitForTx( @@ -675,11 +676,12 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn testEnv ); + await increaseTime(1000); // Repay the remaining DAI const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.toString(); await mockSwapAdapter.setAmountToReturn(amountToRepay); - await waitForTx( + const receipt = await waitForTx( await pool .connect(user.signer) .repayWithCollateral( @@ -691,7 +693,8 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn '0x' ) ); - const repayWithCollateralTimestamp = await timeLatest(); + const repayWithCollateralTimestamp = (await BRE.ethers.provider.getBlock(receipt.blockNumber)) + .timestamp; const {userData: wethUserDataAfter} = await getContractsData( weth.address, @@ -733,11 +736,11 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn .toString() ); - expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + expect( new BigNumber(wethUserDataBefore.currentATokenBalance).minus( expectedCollateralLiquidated.toString() ) - ); + ).to.be.bignumber.equal(wethUserDataAfter.currentATokenBalance); }); it('Liquidator tries to repay 4 user a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { diff --git a/test/liquidation-atoken.spec.ts b/test/liquidation-atoken.spec.ts index 6e4af1e1..b21e20d9 100644 --- a/test/liquidation-atoken.spec.ts +++ b/test/liquidation-atoken.spec.ts @@ -192,7 +192,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => ); //the liquidity index of the principal reserve needs to be bigger than the index before - expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt( + expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte( daiReserveDataBefore.liquidityIndex.toString(), 'Invalid liquidity index' ); @@ -213,6 +213,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => const {users, pool, usdc, oracle, weth} = testEnv; const depositor = users[3]; const borrower = users[4]; + //mints USDC to depositor await usdc .connect(depositor.signer) @@ -246,7 +247,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => usdc.address, new BigNumber(userGlobalData.availableBorrowsETH.toString()) .div(usdcPrice.toString()) - .multipliedBy(0.95) + .multipliedBy(0.9502) .toFixed(0) ); @@ -274,7 +275,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => const ethReserveDataBefore = await pool.getReserveData(weth.address); const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString()) - .div(2) + .multipliedBy(0.5) .toFixed(0); await pool.liquidationCall( @@ -328,7 +329,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => ); //the liquidity index of the principal reserve needs to be bigger than the index before - expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt( + expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte( usdcReserveDataBefore.liquidityIndex.toString(), 'Invalid liquidity index' ); diff --git a/test/liquidation-underlying.spec.ts b/test/liquidation-underlying.spec.ts index 9a58965e..19c821f1 100644 --- a/test/liquidation-underlying.spec.ts +++ b/test/liquidation-underlying.spec.ts @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js'; -import {BRE} from '../helpers/misc-utils'; +import {BRE, increaseTime} from '../helpers/misc-utils'; import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants'; import {convertToCurrencyDecimals} from '../helpers/contracts-helpers'; import {makeSuite} from './helpers/make-suite'; @@ -114,6 +114,8 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2).toFixed(0); + await increaseTime(100); + const tx = await pool .connect(liquidator.signer) .liquidationCall(weth.address, dai.address, borrower.address, amountToLiquidate, false); @@ -161,7 +163,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', ); //the liquidity index of the principal reserve needs to be bigger than the index before - expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt( + expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte( daiReserveDataBefore.liquidityIndex.toString(), 'Invalid liquidity index' ); @@ -227,7 +229,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', usdc.address, new BigNumber(userGlobalData.availableBorrowsETH.toString()) .div(usdcPrice.toString()) - .multipliedBy(0.95) + .multipliedBy(0.9502) .toFixed(0) ); @@ -255,10 +257,11 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', const usdcReserveDataBefore = await pool.getReserveData(usdc.address); const ethReserveDataBefore = await pool.getReserveData(weth.address); - const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString()) + const amountToLiquidate = BRE.ethers.BigNumber.from( + userReserveDataBefore.currentStableDebt.toString() + ) .div(2) - .decimalPlaces(0, BigNumber.ROUND_DOWN) - .toFixed(0); + .toString(); await pool .connect(liquidator.signer) @@ -303,7 +306,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', ); //the liquidity index of the principal reserve needs to be bigger than the index before - expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt( + expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte( usdcReserveDataBefore.liquidityIndex.toString(), 'Invalid liquidity index' ); From e923324ea51332c63a4db6cbb3bcdee09eb43416 Mon Sep 17 00:00:00 2001 From: The3D Date: Thu, 10 Sep 2020 09:57:15 +0200 Subject: [PATCH 27/31] Removed duplicated comment --- contracts/tokenization/AToken.sol | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 265a6cbb..e969e5f3 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -199,10 +199,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return amount; } - - /** - * @notice ERC20 implementation internal function backing transfer() and transferFrom() - **/ function _transfer( address from, address to, @@ -222,10 +218,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { emit BalanceTransfer(from, to, amount, index); } - - /** - * @notice ERC20 implementation internal function backing transfer() and transferFrom() - **/ + function _transfer( address from, address to, From 831bc3d0ebafecac25a6dd641a1e300d51702103 Mon Sep 17 00:00:00 2001 From: David Racero Date: Thu, 10 Sep 2020 13:05:26 +0200 Subject: [PATCH 28/31] Added tests to check repay with collateral when is disabled --- .../flash-liquidation-with-collateral.spec.ts | 87 +++++++++++++- test/repay-with-collateral.spec.ts | 112 ++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/test/flash-liquidation-with-collateral.spec.ts b/test/flash-liquidation-with-collateral.spec.ts index 220921bf..87edf6d6 100644 --- a/test/flash-liquidation-with-collateral.spec.ts +++ b/test/flash-liquidation-with-collateral.spec.ts @@ -17,7 +17,7 @@ const {expect} = require('chai'); const {parseUnits, parseEther} = ethers.utils; makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEnv) => { - const {INVALID_HF} = ProtocolErrors; + const {INVALID_HF, COLLATERAL_CANNOT_BE_LIQUIDATED} = ProtocolErrors; it('User 1 provides some liquidity for others to borrow', async () => { const {pool, weth, dai, usdc} = testEnv; @@ -828,4 +828,89 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn // Resets DAI Price await oracle.setAssetPrice(dai.address, daiPrice); }); + + it('User 5 deposits WETH and DAI, then borrows USDC at Variable, then disables WETH as collateral', async () => { + const {pool, weth, dai, usdc, users} = testEnv; + const user = users[4]; + const amountWETHToDeposit = parseEther('10'); + const amountDAIToDeposit = parseEther('60'); + const amountToBorrow = parseUnits('65', 6); + + await weth.connect(user.signer).mint(amountWETHToDeposit); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, '0'); + + await dai.connect(user.signer).mint(amountDAIToDeposit); + await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0); + }); + + it('Liquidator tries to liquidates User 5 USDC loan by swapping his WETH collateral, should revert due WETH collateral disabled', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[4]; + const liquidator = users[5]; + + const amountToRepay = parseUnits('65', 6); + + // User 5 Disable WETH as collateral + await pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + expect(wethUserDataBefore.usageAsCollateralEnabled).to.be.false; + + // Liquidator should NOT be able to liquidate himself with WETH, even if is disabled + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await expect( + pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ).to.be.revertedWith(COLLATERAL_CANNOT_BE_LIQUIDATED); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .plus(expectedVariableDebtIncrease) + .toString(), + 'INVALID_DEBT_POSITION' + ); + + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false; + }); }); diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts index 9896b81c..2fbe66fc 100644 --- a/test/repay-with-collateral.spec.ts +++ b/test/repay-with-collateral.spec.ts @@ -10,6 +10,7 @@ import {getContractsData} from './helpers/actions'; import {waitForTx} from './__setup.spec'; import {timeLatest} from '../helpers/misc-utils'; import {tEthereumAddress} from '../helpers/types'; +import {parse} from 'path'; const {expect} = require('chai'); const {parseUnits, parseEther} = ethers.utils; @@ -525,4 +526,115 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false; }); + + it('User 5 deposits WETH and DAI, then borrows USDC at Variable, then disables WETH as collateral', async () => { + const {pool, weth, dai, usdc, users} = testEnv; + const user = users[4]; + const amountWETHToDeposit = parseEther('10'); + const amountDAIToDeposit = parseEther('120'); + const amountToBorrow = parseUnits('65', 6); + + await weth.connect(user.signer).mint(amountWETHToDeposit); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, '0'); + + await dai.connect(user.signer).mint(amountDAIToDeposit); + await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0); + }); + + it('User 5 tries to repay his USDC loan by swapping his WETH collateral, should not revert even with WETH collateral disabled', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[4]; + + const amountToRepay = parseUnits('65', 6); + + // Disable WETH as collateral + await pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + expect(wethUserDataBefore.usageAsCollateralEnabled).to.be.false; + + // User 5 should be able to liquidate himself with WETH, even if is disabled + await mockSwapAdapter.setAmountToReturn(amountToRepay); + expect( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString(), + 'INVALID_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false; + }); }); From 167f02533f858f74ea0f140839a21ec76ca9c1b6 Mon Sep 17 00:00:00 2001 From: David Racero <4266635-kartojal@users.noreply.gitlab.com> Date: Thu, 10 Sep 2020 13:30:57 +0000 Subject: [PATCH 29/31] Delete "if" condition due always will be true. Delete unreachable "else" code at repayWithCollateral. --- contracts/lendingpool/LendingPoolLiquidationManager.sol | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index f63e1c82..6c7b340b 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -361,14 +361,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable { ); } - if (msg.sender == user || vars.healthFactor < GenericLogic.HEALTH_FACTOR_CRITICAL_THRESHOLD) { - vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt); - } else { - vars.maxPrincipalAmountToLiquidate = vars - .userStableDebt - .add(vars.userVariableDebt) - .percentMul(LIQUIDATION_CLOSE_FACTOR_PERCENT); - } + vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt); vars.actualAmountToLiquidate = principalAmount > vars.maxPrincipalAmountToLiquidate ? vars.maxPrincipalAmountToLiquidate From 0d9a18813ff980f85fac099b2574c95e8f5e9fad Mon Sep 17 00:00:00 2001 From: The3D Date: Sat, 12 Sep 2020 13:18:17 +0200 Subject: [PATCH 30/31] Added optimization on mint and burn --- contracts/lendingpool/LendingPool.sol | 6 +- .../LendingPoolLiquidationManager.sol | 7 +- contracts/libraries/logic/ReserveLogic.sol | 20 ++--- contracts/tokenization/AToken.sol | 28 +++--- contracts/tokenization/interfaces/IAToken.sol | 7 +- deployed-contracts.json | 89 ++++++++++--------- test/atoken-modifiers.spec.ts | 4 +- 7 files changed, 80 insertions(+), 81 deletions(-) diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 517dc997..97723a41 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -109,7 +109,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { } //minting AToken to user 1:1 with the specific exchange rate - IAToken(aToken).mint(onBehalfOf, amount); + IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); //transfer to the aToken contract IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); @@ -155,7 +155,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { _usersConfig[msg.sender].setUsingAsCollateral(reserve.index, false); } - IAToken(aToken).burn(msg.sender, msg.sender, amountToWithdraw); + IAToken(aToken).burn(msg.sender, msg.sender, amountToWithdraw, reserve.liquidityIndex); emit Withdraw(asset, msg.sender, amount); } @@ -616,7 +616,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { reserve.currentVariableBorrowRate, reserve.currentStableBorrowRate, IStableDebtToken(reserve.stableDebtTokenAddress).getAverageStableRate(), - reserve.lastLiquidityIndex, + reserve.liquidityIndex, reserve.lastVariableBorrowIndex, reserve.lastUpdateTimestamp ); diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 6c7b340b..8640761b 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -271,7 +271,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable { ); //burn the equivalent amount of atoken - vars.collateralAtoken.burn(user, msg.sender, vars.maxCollateralToLiquidate); + vars.collateralAtoken.burn(user, msg.sender, vars.maxCollateralToLiquidate, collateralReserve.liquidityIndex); } //transfers the principal currency to the aToken @@ -388,8 +388,10 @@ contract LendingPoolLiquidationManager is VersionedInitializable { if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) { vars.actualAmountToLiquidate = vars.principalAmountNeeded; } + //updating collateral reserve indexes + collateralReserve.updateCumulativeIndexesAndTimestamp(); - vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate); + vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate, collateralReserve.liquidityIndex); if (vars.userCollateralBalance == vars.maxCollateralToLiquidate) { usersConfig[user].setUsingAsCollateral(collateralReserve.index, false); @@ -425,7 +427,6 @@ contract LendingPoolLiquidationManager is VersionedInitializable { } //updating collateral reserve - collateralReserve.updateCumulativeIndexesAndTimestamp(); collateralReserve.updateInterestRates( collateral, address(vars.collateralAtoken), diff --git a/contracts/libraries/logic/ReserveLogic.sol b/contracts/libraries/logic/ReserveLogic.sol index 06d08499..01684225 100644 --- a/contracts/libraries/logic/ReserveLogic.sol +++ b/contracts/libraries/logic/ReserveLogic.sol @@ -56,7 +56,7 @@ library ReserveLogic { address variableDebtTokenAddress; address interestRateStrategyAddress; //the liquidity index. Expressed in ray - uint128 lastLiquidityIndex; + uint128 liquidityIndex; //the current supply rate. Expressed in ray uint128 currentLiquidityRate; //the current variable borrow rate. Expressed in ray @@ -83,12 +83,12 @@ library ReserveLogic { //solium-disable-next-line if (timestamp == uint40(block.timestamp)) { //if the index was updated in the same block, no need to perform any calculation - return reserve.lastLiquidityIndex; + return reserve.liquidityIndex; } uint256 cumulated = MathUtils .calculateLinearInterest(reserve.currentLiquidityRate, timestamp) - .rayMul(reserve.lastLiquidityIndex); + .rayMul(reserve.liquidityIndex); return cumulated; } @@ -131,10 +131,10 @@ library ReserveLogic { currentLiquidityRate, lastUpdateTimestamp ); - uint256 index = cumulatedLiquidityInterest.rayMul(reserve.lastLiquidityIndex); + uint256 index = cumulatedLiquidityInterest.rayMul(reserve.liquidityIndex); require(index < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW); - reserve.lastLiquidityIndex = uint128(index); + 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 @@ -169,10 +169,10 @@ library ReserveLogic { uint256 result = amountToLiquidityRatio.add(WadRayMath.ray()); - result = result.rayMul(reserve.lastLiquidityIndex); + result = result.rayMul(reserve.liquidityIndex); require(result < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW); - reserve.lastLiquidityIndex = uint128(result); + reserve.liquidityIndex = uint128(result); } /** @@ -189,9 +189,9 @@ library ReserveLogic { address interestRateStrategyAddress ) external { require(reserve.aTokenAddress == address(0), Errors.RESERVE_ALREADY_INITIALIZED); - if (reserve.lastLiquidityIndex == 0) { + if (reserve.liquidityIndex == 0) { //if the reserve has not been initialized yet - reserve.lastLiquidityIndex = uint128(WadRayMath.ray()); + reserve.liquidityIndex = uint128(WadRayMath.ray()); } if (reserve.lastVariableBorrowIndex == 0) { @@ -259,7 +259,7 @@ library ReserveLogic { vars.newStableRate, vars.currentAvgStableRate, vars.newVariableRate, - reserve.lastLiquidityIndex, + reserve.liquidityIndex, reserve.lastVariableBorrowIndex ); } diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 2a21e629..862cdda1 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -25,16 +25,15 @@ contract AToken is VersionedInitializable, ERC20, IAToken { uint256 public constant UINT_MAX_VALUE = uint256(-1); address public immutable UNDERLYING_ASSET_ADDRESS; - + LendingPool public immutable POOL; mapping(address => uint256) private _scaledRedirectedBalances; - LendingPool private immutable _pool; uint256 public constant ATOKEN_REVISION = 0x1; modifier onlyLendingPool { - require(msg.sender == address(_pool), Errors.CALLER_MUST_BE_LENDING_POOL); + require(msg.sender == address(POOL), Errors.CALLER_MUST_BE_LENDING_POOL); _; } @@ -44,7 +43,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { string memory tokenName, string memory tokenSymbol ) public ERC20(tokenName, tokenSymbol, 18) { - _pool = pool; + POOL = pool; UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress; } @@ -70,15 +69,14 @@ contract AToken is VersionedInitializable, ERC20, IAToken { function burn( address user, address receiverOfUnderlying, - uint256 amount + uint256 amount, + uint256 index ) external override onlyLendingPool { uint256 currentBalance = balanceOf(user); require(amount <= currentBalance, Errors.INVALID_ATOKEN_BALANCE); - uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - uint256 scaledAmount = amount.rayDiv(index); _burn(user, scaledAmount); @@ -96,9 +94,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @param user the address receiving the minted tokens * @param amount the amount of tokens to mint */ - function mint(address user, uint256 amount) external override onlyLendingPool { + function mint(address user, uint256 amount, uint256 index) external override onlyLendingPool { - uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); uint256 scaledAmount = amount.rayDiv(index); @@ -132,11 +129,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @return the total balance of the user **/ function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) { - - uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); - - return super.balanceOf(user).rayMul(index); + return super.balanceOf(user).rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)); } @@ -165,9 +159,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return currentSupplyScaled - .wadToRay() - .rayMul(_pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)) - .rayToWad(); + .rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)); } /** @@ -177,7 +169,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @return true if the user can transfer amount, false otherwise **/ function isTransferAllowed(address user, uint256 amount) public override view returns (bool) { - return _pool.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount); + return POOL.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount); } /** @@ -207,7 +199,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED); } - uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + uint256 index = POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); uint256 scaledAmount = amount.rayDiv(index); diff --git a/contracts/tokenization/interfaces/IAToken.sol b/contracts/tokenization/interfaces/IAToken.sol index 5085474a..3aa5e83d 100644 --- a/contracts/tokenization/interfaces/IAToken.sol +++ b/contracts/tokenization/interfaces/IAToken.sol @@ -43,11 +43,13 @@ interface IAToken is IERC20 { * @dev burns the aTokens and sends the equivalent amount of underlying to the target. * only lending pools can call this function * @param amount the amount being burned + * @param index the liquidity index **/ function burn( address user, address underlyingTarget, - uint256 amount + uint256 amount, + uint256 index ) external; /** @@ -55,8 +57,9 @@ interface IAToken is IERC20 { * 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) external; + function mint(address user, uint256 amount, uint256 index) external; /** * @dev transfers tokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken diff --git a/deployed-contracts.json b/deployed-contracts.json index c2aadefe..b9d54d60 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -5,7 +5,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", + "address": "0x209bb253C2f894D3Cc53b9dC23d308Eb8593613A", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -15,7 +15,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xa4bcDF64Cdd5451b6ac3743B414124A6299B65FF", + "address": "0xa2eDbC6b9E7EBA4b66f6A0B8Af3065CaC5611A6E", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -25,7 +25,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F", + "address": "0x6424b49739C3fC1d390Cea7A6bafa5B32A7B47b8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -53,7 +53,7 @@ "address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8" }, "localhost": { - "address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8" + "address": "0x8Be07B1e05bCB4091344ff2356D40EBf7e0c9b6E" } }, "LendingPoolDataProvider": { @@ -66,7 +66,7 @@ "address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e" }, "localhost": { - "address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e" + "address": "0xB44f879C781DfFF5E07aF7d338449E767Aa1c1d2" } }, "PriceOracle": { @@ -75,7 +75,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1750499D05Ed1674d822430FB960d5F6731fDf64", + "address": "0x34c94f172B5eAcb53230AE62e41e1828B1a4B0F8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -85,7 +85,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xEC1C93A9f6a9e18E97784c76aC52053587FcDB89", + "address": "0x01C5292e57aB25E38152ceE4A45C2038f233D531", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -95,7 +95,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x7B6C3e5486D9e6959441ab554A889099eed76290", + "address": "0x3D6bB48D5988B0D8B1d920cef50f59ED7d527F8c", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -105,7 +105,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xD83D2773a7873ae2b5f8Fb92097e20a8C64F691E", + "address": "0xC414d0323C57aF313F570A09c1D6e691F3C1c195", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -115,7 +115,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x626FdE749F9d499d3777320CAf29484B624ab84a", + "address": "0xbEed026d89A715F28b32135A6C3e3c060234f40d", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -169,7 +169,7 @@ "address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA" }, "localhost": { - "address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA" + "address": "0x045Da1BcEB75D2E0064a66Da1Ad606350CD9730A" } }, "WalletBalanceProvider": { @@ -178,7 +178,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2", + "address": "0xd54dbF2a2D88aFeCA7E288D43e16150b57C6bcd9", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -188,7 +188,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F", + "address": "0xa17a59441D6c39D21F2Ff03e7b21f8d2BCAA6023", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -198,7 +198,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8858eeB3DfffA017D4BCE9801D340D36Cf895CCf", + "address": "0x36B6f7e34d651DC7fd7EeEc53bf26594209915A8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -208,7 +208,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x0078371BDeDE8aAc7DeBfFf451B74c5EDB385Af7", + "address": "0x099E8e561f4cfCe0bbDaD063d973eBcf1A1d92B5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -218,7 +218,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xf4e77E5Da47AC3125140c470c71cBca77B5c638c", + "address": "0x8473D688815861639F6e4442F4433047c2F5571b", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -228,7 +228,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x3619DbE27d7c1e7E91aA738697Ae7Bc5FC3eACA5", + "address": "0xBcf29d6fd8EB2d95b5Ad0Ffdd7ee5272cc49b26c", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -238,7 +238,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x038B86d9d8FAFdd0a02ebd1A476432877b0107C8", + "address": "0x6e77C7e0Fd33A53351335E768593ba56A6C43594", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -248,7 +248,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1A1FEe7EeD918BD762173e4dc5EfDB8a78C924A8", + "address": "0x67a87Be0A08F955EfC629b1cdaaE1eaC48043E6a", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -258,7 +258,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e", + "address": "0xEDf104A35B3293F4BdB987be9D57EFe3b69C19c7", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -268,7 +268,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xc4905364b78a742ccce7B890A89514061E47068D", + "address": "0xD8212F51E19A269B8fCc327BF91ede79e218EF17", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -278,7 +278,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe", + "address": "0x1712cE132Cc5E2A5b63e6AF4Ee551070f7Bc4487", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -288,7 +288,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8B5B7a6055E54a36fF574bbE40cf2eA68d5554b3", + "address": "0x6DC0873546006Ce00eC8AA9e97706125D75E3ab6", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -298,7 +298,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xEcc0a6dbC0bb4D51E4F84A315a9e5B0438cAD4f0", + "address": "0x133EA40EA9975d53D34417F9164a54A635110AE9", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -308,7 +308,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x20Ce94F404343aD2752A2D01b43fa407db9E0D00", + "address": "0xfe5E2ac37e1cf3f1dFA55De53780692846eD199A", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -318,7 +318,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1d80315fac6aBd3EfeEbE97dEc44461ba7556160", + "address": "0xcf33a1c13D1599399B3581c3f3cf39e943013A73", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -328,7 +328,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x2D8553F9ddA85A9B3259F6Bf26911364B85556F5", + "address": "0x6B4Fef015Ea5D2A23C5E5906b41f206c79E36316", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -338,7 +338,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x52d3b94181f8654db2530b0fEe1B19173f519C52", + "address": "0x556f80053f02Ee04a4f13820AE7a30f787A7A630", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -348,7 +348,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xd15468525c35BDBC1eD8F2e09A00F8a173437f2f", + "address": "0x222C21A948139f016EBbd1979250194049b28473", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -358,7 +358,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x7e35Eaf7e8FBd7887ad538D4A38Df5BbD073814a", + "address": "0xb0c6bAc77c65588a5c47d18545D3d265b0030B7e", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -368,7 +368,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5bcb88A0d20426e451332eE6C4324b0e663c50E0", + "address": "0x9197B2985256CD8a0B41796ab5794D502012766c", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -378,7 +378,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x3521eF8AaB0323004A6dD8b03CE890F4Ea3A13f5", + "address": "0x89E72D113048277a670222d9bcACF4FA2c7b20A6", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -388,7 +388,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x53369fd4680FfE3DfF39Fc6DDa9CfbfD43daeA2E", + "address": "0x6f4689b37FCC44f24e8dE9Cf2B61f81E71fB9dc0", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -398,7 +398,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xB00cC45B4a7d3e1FEE684cFc4417998A1c183e6d", + "address": "0x77183A4B7c0375bA9A5090Ae68c32A5C567d77c6", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -408,7 +408,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", + "address": "0x209bb253C2f894D3Cc53b9dC23d308Eb8593613A", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -417,7 +417,7 @@ "address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4" }, "localhost": { - "address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10" + "address": "0xc47cF1C70618CB94e6B1D218468D3E16AE35Fff4" } }, "StableDebtToken": { @@ -426,7 +426,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xB660Fdd109a95718cB9d20E3A89EE6cE342aDcB6", + "address": "0x3888B5ac0089C12cDF21DD8B0234029f80645324", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -436,13 +436,13 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d", + "address": "0xB76Ea4df0263F99daf33765541b1933AD5bB4410", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, "AToken": { "localhost": { - "address": "0xA0AB1cB92A4AF81f84dCd258155B5c25D247b54E", + "address": "0x4d39D68f5a2A43E79e7B3A859014cc4233D0EEA1", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "buidlerevm": { @@ -456,7 +456,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xF5E6E6B10E4F2f27DaC1fFdDE83367dE9525552a", + "address": "0x5efEaaE02a5E2BdA1aDAc7aad29D9B4bFFDD90E8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -466,7 +466,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xf784709d2317D872237C4bC22f867d1BAe2913AB", + "address": "0xd334C51Ad3167554876f19F9575394F1cfbc96AF", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -476,7 +476,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x7f23223A2FAf869962B38f5eC4aAB7f37454A45e", + "address": "0x59A442D1DbAE607fD3cd97859dc14Ff400F7C2ed", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -486,13 +486,16 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1203D1b97BF6E546c00C45Cda035D3010ACe1180", + "address": "0xE8F349DB32821021520BBe11b7927279BC3BEC6b", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, "MockSwapAdapter": { "buidlerevm": { "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2" + }, + "localhost": { + "address": "0xcB70821E9dDE40dc23E973280991A8cdBFD4EC2c" } } } \ No newline at end of file diff --git a/test/atoken-modifiers.spec.ts b/test/atoken-modifiers.spec.ts index 406d910a..7560970c 100644 --- a/test/atoken-modifiers.spec.ts +++ b/test/atoken-modifiers.spec.ts @@ -7,12 +7,12 @@ makeSuite('AToken: Modifiers', (testEnv: TestEnv) => { it('Tries to invoke mint not being the LendingPool', async () => { const {deployer, aDai} = testEnv; - await expect(aDai.mint(deployer.address, '1')).to.be.revertedWith(CALLER_MUST_BE_LENDING_POOL); + await expect(aDai.mint(deployer.address, '1', '1')).to.be.revertedWith(CALLER_MUST_BE_LENDING_POOL); }); it('Tries to invoke burn not being the LendingPool', async () => { const {deployer, aDai} = testEnv; - await expect(aDai.burn(deployer.address, deployer.address, '1')).to.be.revertedWith( + await expect(aDai.burn(deployer.address, deployer.address, '1', '1')).to.be.revertedWith( CALLER_MUST_BE_LENDING_POOL ); }); From 70e1f88ce4b8139b35dda42d4e20184dd08b8d39 Mon Sep 17 00:00:00 2001 From: The3D Date: Sat, 12 Sep 2020 13:19:41 +0200 Subject: [PATCH 31/31] removed comment --- contracts/lendingpool/LendingPool.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 97723a41..30a69084 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -108,7 +108,6 @@ contract LendingPool is VersionedInitializable, ILendingPool { _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.index, true); } - //minting AToken to user 1:1 with the specific exchange rate IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); //transfer to the aToken contract