diff --git a/contracts/flashloan/base/FlashLoanReceiverBase.sol b/contracts/flashloan/base/FlashLoanReceiverBase.sol index c4aaecd6..f96609d2 100644 --- a/contracts/flashloan/base/FlashLoanReceiverBase.sol +++ b/contracts/flashloan/base/FlashLoanReceiverBase.sol @@ -12,27 +12,12 @@ abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { using SafeERC20 for IERC20; using SafeMath for uint256; - ILendingPoolAddressesProvider public addressesProvider; + ILendingPoolAddressesProvider internal _addressesProvider; constructor(ILendingPoolAddressesProvider provider) public { - addressesProvider = provider; + _addressesProvider = provider; } receive() external payable {} - function _transferFundsBack( - address reserve, - address destination, - uint256 amount - ) internal { - transferInternal(destination, reserve, amount); - } - - function transferInternal( - address destination, - address reserve, - uint256 amount - ) internal { - IERC20(reserve).safeTransfer(destination, amount); - } } diff --git a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol index 95fe6f3d..e3c2636c 100644 --- a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol +++ b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol @@ -10,7 +10,6 @@ pragma solidity ^0.6.8; interface IFlashLoanReceiver { function executeOperation( address reserve, - address destination, uint256 amount, uint256 fee, bytes calldata params diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 43bfb554..a7a5e1ca 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -63,7 +63,7 @@ interface ILendingPool { * @param reserve the address of the reserve * @param user the address of the user executing the swap **/ - event Swap(address indexed reserve, address indexed user, uint256 timestamp); + event Swap(address indexed reserve, address indexed user); /** * @dev emitted when a user enables a reserve as collateral @@ -90,13 +90,15 @@ interface ILendingPool { * @param target the address of the flashLoanReceiver * @param reserve the address of the reserve * @param amount the amount requested - * @param totalFee the total fee on the amount + * @param totalPremium the total fee on the amount + * @param referralCode the referral code of the caller **/ event FlashLoan( address indexed target, address indexed reserve, uint256 amount, - uint256 totalFee + uint256 totalPremium, + uint16 referralCode ); /** * @dev these events are not emitted directly by the LendingPool @@ -105,21 +107,6 @@ interface ILendingPool { * This allows to have the events in the generated ABI for LendingPool. **/ - /** - * @dev emitted when a borrow fee is liquidated - * @param collateral the address of the collateral being liquidated - * @param reserve the address of the reserve - * @param user the address of the user being liquidated - * @param feeLiquidated the total fee liquidated - * @param liquidatedCollateralForFee the amount of collateral received by the protocol in exchange for the fee - **/ - event OriginationFeeLiquidated( - address indexed collateral, - address indexed reserve, - address indexed user, - uint256 feeLiquidated, - uint256 liquidatedCollateralForFee - ); /** * @dev emitted when a borrower is liquidated * @param collateral the address of the collateral being liquidated @@ -238,12 +225,16 @@ interface ILendingPool { * @param receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. * @param reserve the address of the principal reserve * @param amount the amount requested for this flashloan + * @param params a bytes array to be sent to the flashloan executor + * @param referralCode the referral code of the caller **/ function flashLoan( address receiver, address reserve, uint256 amount, - bytes calldata params + uint256 debtType, + bytes calldata params, + uint16 referralCode ) external; /** diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index e38ee13a..1cd4bc55 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -3,7 +3,6 @@ pragma solidity ^0.6.8; pragma experimental ABIEncoderV2; import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol'; -import {ReentrancyGuard} from '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import { VersionedInitializable @@ -32,7 +31,7 @@ import {ILendingPool} from '../interfaces/ILendingPool.sol'; * @author Aave **/ -contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { +contract LendingPool is VersionedInitializable, ILendingPool { using SafeMath for uint256; using WadRayMath for uint256; using ReserveLogic for ReserveLogic.ReserveData; @@ -43,7 +42,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { //main configuration parameters uint256 public constant REBALANCE_DOWN_RATE_DELTA = (1e27) / 5; uint256 public constant MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 25; - uint256 public constant FLASHLOAN_FEE_TOTAL = 9; + uint256 public constant FLASHLOAN_PREMIUM_TOTAL = 9; ILendingPoolAddressesProvider internal _addressesProvider; @@ -91,7 +90,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { address asset, uint256 amount, uint16 referralCode - ) external override nonReentrant { + ) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateDeposit(reserve, amount); @@ -112,7 +111,6 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { //transfer to the aToken contract IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); - //solium-disable-next-line emit Deposit(asset, msg.sender, amount, referralCode); } @@ -121,7 +119,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param amount the underlying amount to be redeemed **/ - function withdraw(address asset, uint256 amount) external override nonReentrant { + function withdraw(address asset, uint256 amount) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; address aToken = reserve.aTokenAddress; @@ -156,7 +154,6 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { IAToken(aToken).burn(msg.sender, msg.sender, amountToWithdraw); - //solium-disable-next-line emit Withdraw(asset, msg.sender, amount); } @@ -166,65 +163,24 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param amount the amount to be borrowed * @param interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE) + * @param referralCode a referral code for integrators **/ function borrow( address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode - ) external override nonReentrant { - ReserveLogic.ReserveData storage reserve = _reserves[asset]; - UserConfiguration.Map storage userConfig = _usersConfig[msg.sender]; - - uint256 amountInETH = IPriceOracleGetter(_addressesProvider.getPriceOracle()) - .getAssetPrice(asset) - .mul(amount) - .div(10**reserve.configuration.getDecimals()); //price is in ether - - ValidationLogic.validateBorrow( - reserve, - asset, - amount, - amountInETH, - interestRateMode, - MAX_STABLE_RATE_BORROW_SIZE_PERCENT, - _reserves, - _usersConfig[msg.sender], - _reservesList, - _addressesProvider.getPriceOracle() - ); - - //caching the current stable borrow rate - uint256 userStableRate = reserve.currentStableBorrowRate; - - reserve.updateCumulativeIndexesAndTimestamp(); - - if (ReserveLogic.InterestRateMode(interestRateMode) == ReserveLogic.InterestRateMode.STABLE) { - IStableDebtToken(reserve.stableDebtTokenAddress).mint(msg.sender, amount, userStableRate); - } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).mint(msg.sender, amount); - } - - address aToken = reserve.aTokenAddress; - reserve.updateInterestRates(asset, aToken, 0, amount); - - uint256 reserveIndex = reserve.index; - if (!userConfig.isBorrowing(reserveIndex)) { - userConfig.setBorrowing(reserveIndex, true); - } - - //if we reached this point, we can transfer - IAToken(aToken).transferUnderlyingTo(msg.sender, amount); - - emit Borrow( - asset, - msg.sender, - amount, - interestRateMode, - ReserveLogic.InterestRateMode(interestRateMode) == ReserveLogic.InterestRateMode.STABLE - ? userStableRate - : reserve.currentVariableBorrowRate, - referralCode + ) external override { + _executeBorrow( + ExecuteBorrowParams( + asset, + msg.sender, + amount, + interestRateMode, + _reserves[asset].aTokenAddress, + referralCode, + true + ) ); } @@ -241,7 +197,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { uint256 amount, uint256 rateMode, address onBehalfOf - ) external override nonReentrant { + ) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve); @@ -292,7 +248,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { * @param asset the address of the reserve on which the user borrowed * @param rateMode the rate mode that the user wants to swap **/ - function swapBorrowRateMode(address asset, uint256 rateMode) external override nonReentrant { + function swapBorrowRateMode(address asset, uint256 rateMode) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve); @@ -325,12 +281,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { reserve.updateInterestRates(asset, reserve.aTokenAddress, 0, 0); - emit Swap( - asset, - msg.sender, - //solium-disable-next-line - block.timestamp - ); + emit Swap(asset, msg.sender); } /** @@ -340,7 +291,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param user the address of the user to be rebalanced **/ - function rebalanceStableBorrowRate(address asset, address user) external override nonReentrant { + function rebalanceStableBorrowRate(address asset, address user) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; IStableDebtToken stableDebtToken = IStableDebtToken(reserve.stableDebtTokenAddress); @@ -385,11 +336,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise. **/ - function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) - external - override - nonReentrant - { + function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateSetUseReserveAsCollateral( @@ -425,7 +372,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { address user, uint256 purchaseAmount, bool receiveAToken - ) external override nonReentrant { + ) external override { address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager(); //solium-disable-next-line @@ -449,65 +396,83 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { } } + struct FlashLoanLocalVars { + uint256 premium; + uint256 amountPlusPremium; + uint256 amountPlusPremiumInETH; + uint256 receiverBalance; + uint256 receiverAllowance; + uint256 availableBalance; + uint256 assetPrice; + IFlashLoanReceiver receiver; + address aTokenAddress; + address oracle; + } + /** - * @dev allows smartcontracts to access the liquidity of the pool within one transaction, + * @dev allows smart contracts 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 * that must be kept into consideration. For further details please visit https://developers.aave.com * @param receiverAddress The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. - * @param asset the address of the principal reserve - * @param amount the amount requested for this flashloan + * @param asset The address of the principal reserve + * @param amount The amount requested for this flashloan + * @param mode Type of the debt to open if the flash loan is not returned. 0 -> Don't open any debt, just revert, 1 -> stable, 2 -> variable + * @param params Variadic packed params to pass to the receiver as extra information + * @param referralCode Referral code of the flash loan **/ function flashLoan( address receiverAddress, address asset, uint256 amount, - bytes calldata params - ) external override nonReentrant { + uint256 mode, + bytes calldata params, + uint16 referralCode + ) external override { ReserveLogic.ReserveData storage reserve = _reserves[asset]; + FlashLoanLocalVars memory vars; - address aTokenAddress = reserve.aTokenAddress; + vars.aTokenAddress = reserve.aTokenAddress; - //check that the reserve has enough available liquidity - uint256 availableLiquidityBefore = IERC20(asset).balanceOf(aTokenAddress); + vars.premium = amount.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000); - //calculate amount fee - uint256 amountFee = amount.mul(FLASHLOAN_FEE_TOTAL).div(10000); + ValidationLogic.validateFlashloan(mode, vars.premium); - require(availableLiquidityBefore >= amount, Errors.NOT_ENOUGH_LIQUIDITY_TO_BORROW); - require(amountFee > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL); + ReserveLogic.InterestRateMode debtMode = ReserveLogic.InterestRateMode(mode); - //get the FlashLoanReceiver instance - IFlashLoanReceiver receiver = IFlashLoanReceiver(receiverAddress); + vars.receiver = IFlashLoanReceiver(receiverAddress); //transfer funds to the receiver - IAToken(aTokenAddress).transferUnderlyingTo(receiverAddress, amount); + IAToken(vars.aTokenAddress).transferUnderlyingTo(receiverAddress, amount); //execute action of the receiver - receiver.executeOperation(asset, aTokenAddress, amount, amountFee, params); + vars.receiver.executeOperation(asset, amount, vars.premium, params); - //check that the actual balance of the core contract includes the returned amount - uint256 availableLiquidityAfter = IERC20(asset).balanceOf(aTokenAddress); + vars.amountPlusPremium = amount.add(vars.premium); - require( - availableLiquidityAfter == availableLiquidityBefore.add(amountFee), - Errors.INCONSISTENT_PROTOCOL_ACTUAL_BALANCE - ); + 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); - //compounding the cumulated interest - reserve.updateCumulativeIndexesAndTimestamp(); - - uint256 totalLiquidityBefore = availableLiquidityBefore - .add(IERC20(reserve.variableDebtTokenAddress).totalSupply()) - .add(IERC20(reserve.stableDebtTokenAddress).totalSupply()); - - //compounding the received fee into the reserve - reserve.cumulateToLiquidityIndex(totalLiquidityBefore, amountFee); - - //refresh interest rates - reserve.updateInterestRates(asset, aTokenAddress, amountFee, 0); - - //solium-disable-next-line - emit FlashLoan(receiverAddress, asset, amount, amountFee); + } else { + // If the transfer didn't succeed, the receiver either didn't return the funds, or didn't approve the transfer. + _executeBorrow( + ExecuteBorrowParams( + asset, + msg.sender, + vars.amountPlusPremium.sub(vars.availableBalance), + mode, + vars.aTokenAddress, + referralCode, + false + ) + ); + } } /** @@ -724,9 +689,89 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable, ILendingPool { return _reserves[asset].configuration; } + // internal functions + + struct ExecuteBorrowParams { + address asset; + address user; + uint256 amount; + uint256 interestRateMode; + address aTokenAddress; + uint16 referralCode; + bool releaseUnderlying; + } + /** - * @notice internal functions + * @dev Internal function to execute a borrowing action, allowing to transfer or not the underlying + * @param vars Input struct for the borrowing action, in order to avoid STD errors **/ + function _executeBorrow(ExecuteBorrowParams memory vars) internal { + ReserveLogic.ReserveData storage reserve = _reserves[vars.asset]; + UserConfiguration.Map storage userConfig = _usersConfig[msg.sender]; + + address oracle = _addressesProvider.getPriceOracle(); + + uint256 amountInETH = IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div( + 10**reserve.configuration.getDecimals() + ); + + ValidationLogic.validateBorrow( + reserve, + vars.asset, + vars.amount, + amountInETH, + vars.interestRateMode, + MAX_STABLE_RATE_BORROW_SIZE_PERCENT, + _reserves, + userConfig, + _reservesList, + oracle + ); + + + uint256 reserveIndex = reserve.index; + if (!userConfig.isBorrowing(reserveIndex)) { + userConfig.setBorrowing(reserveIndex, true); + } + + + reserve.updateCumulativeIndexesAndTimestamp(); + + //caching the current stable borrow rate + uint256 currentStableRate = 0; + + if ( + ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE + ) { + currentStableRate = reserve.currentStableBorrowRate; + + IStableDebtToken(reserve.stableDebtTokenAddress).mint( + vars.user, + vars.amount, + currentStableRate + ); + } else { + 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); + } + + + emit Borrow( + vars.asset, + msg.sender, + vars.amount, + vars.interestRateMode, + ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE + ? currentStableRate + : reserve.currentVariableBorrowRate, + vars.referralCode + ); + } /** * @dev adds a reserve to the array of the _reserves address diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 34cb6f38..12ecf777 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -3,8 +3,6 @@ pragma solidity ^0.6.8; import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol'; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import {ReentrancyGuard} from '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; -import {ReentrancyGuard} from '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; import { VersionedInitializable } from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol'; @@ -28,7 +26,7 @@ import {Errors} from '../libraries/helpers/Errors.sol'; * @author Aave * @notice Implements the liquidation function. **/ -contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializable { +contract LendingPoolLiquidationManager is VersionedInitializable { using SafeERC20 for IERC20; using SafeMath for uint256; using WadRayMath for uint256; diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index 079d1b9f..a01caa85 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -62,4 +62,5 @@ library Errors { string public constant SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER = '40'; // 'User did not borrow the specified currency' string public constant NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE = '41'; // "There isn't enough liquidity available to liquidate" string public constant NO_ERRORS = '42'; // 'No errors' + string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected } diff --git a/contracts/libraries/logic/ValidationLogic.sol b/contracts/libraries/logic/ValidationLogic.sol index 4541f489..7a640458 100644 --- a/contracts/libraries/logic/ValidationLogic.sol +++ b/contracts/libraries/logic/ValidationLogic.sol @@ -60,10 +60,6 @@ library ValidationLogic { ) external view { require(amount > 0, Errors.AMOUNT_NOT_GREATER_THAN_0); - uint256 currentAvailableLiquidity = IERC20(reserveAddress).balanceOf(address(aTokenAddress)); - - require(currentAvailableLiquidity >= amount, Errors.CURRENT_AVAILABLE_LIQUIDITY_NOT_ENOUGH); - require(amount <= userBalance, Errors.NOT_ENOUGH_AVAILABLE_USER_BALANCE); require( @@ -148,11 +144,6 @@ library ValidationLogic { Errors.INVALID_INTEREST_RATE_MODE_SELECTED ); - //check that the amount is available in the reserve - vars.availableLiquidity = IERC20(reserveAddress).balanceOf(address(reserve.aTokenAddress)); - - require(vars.availableLiquidity >= amount, Errors.CURRENT_AVAILABLE_LIQUIDITY_NOT_ENOUGH); - ( vars.userCollateralBalanceETH, vars.userBorrowBalanceETH, @@ -328,4 +319,14 @@ library ValidationLogic { Errors.DEPOSIT_ALREADY_IN_USE ); } + + /** + * @dev validates a flashloan action + * @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan) + * @param premium the premium paid on the flashloan + **/ + function validateFlashloan(uint256 mode, uint256 premium) internal pure { + require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL); + require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE); + } } diff --git a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol index b1bfc8b2..112084a7 100644 --- a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol +++ b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol @@ -13,46 +13,54 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { using SafeMath for uint256; using SafeERC20 for IERC20; + ILendingPoolAddressesProvider internal _provider; + event ExecutedWithFail(address _reserve, uint256 _amount, uint256 _fee); event ExecutedWithSuccess(address _reserve, uint256 _amount, uint256 _fee); - bool failExecution = false; + bool _failExecution; + uint256 _amountToApprove; - constructor(ILendingPoolAddressesProvider _provider) public FlashLoanReceiverBase(_provider) {} + constructor(ILendingPoolAddressesProvider provider) public FlashLoanReceiverBase(provider) {} - function setFailExecutionTransfer(bool _fail) public { - failExecution = _fail; + function setFailExecutionTransfer(bool fail) public { + _failExecution = fail; + } + + function setAmountToApprove(uint256 amountToApprove) public { + _amountToApprove = amountToApprove; + } + + function amountToApprove() public view returns (uint256) { + return _amountToApprove; } function executeOperation( - address _reserve, - address _destination, - uint256 _amount, - uint256 _fee, - bytes memory _params + address reserve, + uint256 amount, + uint256 fee, + bytes memory params ) public override { //mint to this contract the specific amount - MintableERC20 token = MintableERC20(_reserve); + MintableERC20 token = MintableERC20(reserve); //check the contract has the specified balance - require( - _amount <= IERC20(_reserve).balanceOf(address(this)), - 'Invalid balance for the contract' - ); + require(amount <= IERC20(reserve).balanceOf(address(this)), 'Invalid balance for the contract'); - if (failExecution) { - emit ExecutedWithFail(_reserve, _amount, _fee); + uint256 amountToReturn = (_amountToApprove != 0) ? _amountToApprove : amount.add(fee); + + if (_failExecution) { + emit ExecutedWithFail(reserve, amount, fee); return; } //execution does not fail - mint tokens and return them to the _destination //note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation - token.mint(_fee); + token.mint(fee); - //returning amount + fee to the destination - _transferFundsBack(_reserve, _destination, _amount.add(_fee)); + IERC20(reserve).approve(_addressesProvider.getLendingPool(), amountToReturn); - emit ExecutedWithSuccess(_reserve, _amount, _fee); + emit ExecutedWithSuccess(reserve, amount, fee); } } diff --git a/deployed-contracts.json b/deployed-contracts.json index 8286b8f5..74b88aa4 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -5,7 +5,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", + "address": "0xf8c6eB390cDc5C08717bC2268aa0c1169A9B5deE", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -15,7 +15,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xa4bcDF64Cdd5451b6ac3743B414124A6299B65FF", + "address": "0x4a716924Dad0c0d0E558844F304548814e7089F1", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -25,7 +25,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F", + "address": "0x798c5b4b62b1eA9D64955D6751B03075A003F123", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -53,7 +53,7 @@ "address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8" }, "localhost": { - "address": "0x9EC0480CF106d6dc1c7849BA141a56F874170F97" + "address": "0x193101EA4C68eb894aeb922D4aC9C612a464c735" } }, "LendingPoolDataProvider": { @@ -66,7 +66,7 @@ "address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e" }, "localhost": { - "address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8" + "address": "0xf9cD0476CFC1E983e9feA9366A2C08e10eFc9e25" } }, "PriceOracle": { @@ -75,7 +75,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x099d9fF8F818290C8b5B7Db5bFca84CEebd2714c", + "address": "0x18C3df59BEb7babb81BC20f61c5C175D0Cb7603d", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -85,7 +85,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xAF6BA11790D1942625C0c2dA07da19AB63845cfF", + "address": "0x39ed2aE701B56AD229A19E628Bf5A515795F0AA3", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -95,7 +95,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xD83D2773a7873ae2b5f8Fb92097e20a8C64F691E", + "address": "0x9434029990cF00118c28a06E014F0d7d879f28CE", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -105,7 +105,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xf91aC1098F3b154671Ce83290114aaE45ac0225f", + "address": "0xccd7A2534fd4FD5119De8E368615b226e23F8F37", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -115,7 +115,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d", + "address": "0x4c010BA8A40e5c13Acc1E32c025c2b2aea405Dbb", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -169,7 +169,7 @@ "address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA" }, "localhost": { - "address": "0x3bDA11B584dDff7F66E0cFe1da1562c92B45db60" + "address": "0x2aE520a05B31f170a18C425a1e8626aB7Ef71984" } }, "WalletBalanceProvider": { @@ -178,7 +178,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x392E5355a0e88Bd394F717227c752670fb3a8020", + "address": "0xBD2244f43f7BA73eB35A64302A6D8DBf17BdF2F1", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -188,7 +188,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F", + "address": "0x11df1AF606b85226Ab9a8B1FDa90395298e7494F", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -198,7 +198,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8858eeB3DfffA017D4BCE9801D340D36Cf895CCf", + "address": "0x8f9A92c125FFEb83d8eC808Cd9f8cb80084c1E37", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -208,7 +208,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x0078371BDeDE8aAc7DeBfFf451B74c5EDB385Af7", + "address": "0xc4007844AE6bBe168cE8D692C86a7A4414FBcD26", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -218,7 +218,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xf4e77E5Da47AC3125140c470c71cBca77B5c638c", + "address": "0xAb768C858C33DfcB6651d1174AFb750433a87Be0", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -228,7 +228,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x3619DbE27d7c1e7E91aA738697Ae7Bc5FC3eACA5", + "address": "0xA089557D64DAE4b4FcB65aB7C8A520AABb213e37", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -238,7 +238,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x038B86d9d8FAFdd0a02ebd1A476432877b0107C8", + "address": "0x20FAE2042b362E3FaB2806820b9A43CC116e2846", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -248,7 +248,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1A1FEe7EeD918BD762173e4dc5EfDB8a78C924A8", + "address": "0x8880F314112f15C2AfF674c3B27f9a44Ca86e4d0", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -258,7 +258,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e", + "address": "0xDcb10C2e15110Db4B02C0a1df459768E680ce245", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -268,7 +268,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xc4905364b78a742ccce7B890A89514061E47068D", + "address": "0xfD408ec64Da574b1859814F810564f73ea2Ff003", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -278,7 +278,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe", + "address": "0x0006F7c3542BEE76Dd887f54eD22405Ac4ae905a", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -288,7 +288,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8B5B7a6055E54a36fF574bbE40cf2eA68d5554b3", + "address": "0x6ca94a51c644eca3F9CA315bcC41CbA6940A66Eb", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -298,7 +298,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xEcc0a6dbC0bb4D51E4F84A315a9e5B0438cAD4f0", + "address": "0x6765291Cab755B980F377445eFd0F9F945CDA6C4", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -308,7 +308,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x20Ce94F404343aD2752A2D01b43fa407db9E0D00", + "address": "0xa7dB4d25Fc525d19Fbda4E74AAF447B88420FbcB", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -318,7 +318,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1d80315fac6aBd3EfeEbE97dEc44461ba7556160", + "address": "0x273D60904A8DBa3Ae6B20505c59902644124fF0E", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -328,7 +328,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x2D8553F9ddA85A9B3259F6Bf26911364B85556F5", + "address": "0xfc37dE87C1Ee39cc856782BF96fEdcB6FA5c5A7f", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -338,7 +338,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x52d3b94181f8654db2530b0fEe1B19173f519C52", + "address": "0x049228dFFEdf91ff224e9F96247aEBA700e3590c", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -348,7 +348,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xd15468525c35BDBC1eD8F2e09A00F8a173437f2f", + "address": "0xA410D1f3fEAF300842142Cd7AA1709D84944DCb7", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -358,7 +358,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x7e35Eaf7e8FBd7887ad538D4A38Df5BbD073814a", + "address": "0x835973768750b3ED2D5c3EF5AdcD5eDb44d12aD4", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -368,7 +368,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5bcb88A0d20426e451332eE6C4324b0e663c50E0", + "address": "0x1181FC27dbF04B5105243E60BB1936c002e9d5C8", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -378,7 +378,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x3521eF8AaB0323004A6dD8b03CE890F4Ea3A13f5", + "address": "0x6F96975e2a0e1380b6e2e406BB33Ae96e4b6DB65", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -388,7 +388,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x53369fd4680FfE3DfF39Fc6DDa9CfbfD43daeA2E", + "address": "0xc032930653da193EDE295B4DcE3DD093a695c3b3", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -398,7 +398,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xB00cC45B4a7d3e1FEE684cFc4417998A1c183e6d", + "address": "0xb3363f4349b1160DbA55ec4D82fDe874A4123A2a", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -408,7 +408,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22", + "address": "0xf8c6eB390cDc5C08717bC2268aa0c1169A9B5deE", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -417,7 +417,7 @@ "address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10" }, "localhost": { - "address": "0x3b050AFb4ac4ACE646b31fF3639C1CD43aC31460" + "address": "0x18c3e48a45839B3BbC998c70A2fD41fB8D93a35D" } }, "StableDebtToken": { @@ -426,7 +426,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xA0AB1cB92A4AF81f84dCd258155B5c25D247b54E", + "address": "0x2ca7Aa6CcCdb5D77F1c1d3E6a21fF0F7ac24C825", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -436,13 +436,13 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x5f7134cd38C826a7649f9Cc47dda24d834DD2967", + "address": "0x527a346011Cd6c71973f653426Ce609fa53dd59E", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, "AToken": { "localhost": { - "address": "0xE91bBe8ee03560E3dda2786f95335F5399813Ca0", + "address": "0x3035D5D127487Ee5Df5FD951D9624a8b877A8497", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "buidlerevm": { @@ -456,7 +456,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x7f23223A2FAf869962B38f5eC4aAB7f37454A45e", + "address": "0xAA6DfC2A802857Fadb75726B6166484e2c011cf5", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -466,7 +466,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0xf784709d2317D872237C4bC22f867d1BAe2913AB", + "address": "0x2cc20bE530F92865c2ed8CeD0b020a11bFe62Fe7", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -476,7 +476,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x1203D1b97BF6E546c00C45Cda035D3010ACe1180", + "address": "0xFd23fD3d937ae73a7b545B8Bfeb218395bDe9b8f", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } }, @@ -486,7 +486,7 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" }, "localhost": { - "address": "0x8733AfE8174BA7c04c6CD694bD673294079b7E10", + "address": "0xEe821582b591CE5e4a9B7fFc4E2DAD47D3759C08", "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } } diff --git a/helpers/types.ts b/helpers/types.ts index f17d5e9f..7757bc45 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -99,6 +99,7 @@ export enum ProtocolErrors { SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER = '40', // 'User did not borrow the specified currency' NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE = '41', // "There isn't enough liquidity available to liquidate" NO_ERRORS = '42', // 'No errors' + INVALID_FLASHLOAN_MODE = '43', //Invalid flashloan mode // old @@ -109,6 +110,7 @@ export enum ProtocolErrors { INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER = 'Invalid redirected balance after transfer', INVALID_REDIRECTION_ADDRESS = 'Invalid redirection address', INVALID_HF = 'Invalid health factor', + TRANSFER_AMOUNT_EXCEEDS_BALANCE = 'ERC20: transfer amount exceeds balance' } export type tEthereumAddress = string; diff --git a/package.json b/package.json index 34e56bfa..606aaaf9 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-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", diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 223ea890..8ef3f4e8 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -1,19 +1,26 @@ import {TestEnv, makeSuite} from './helpers/make-suite'; import {APPROVAL_AMOUNT_LENDING_POOL, oneRay} from '../helpers/constants'; -import {convertToCurrencyDecimals, getMockFlashLoanReceiver} from '../helpers/contracts-helpers'; +import { + convertToCurrencyDecimals, + getMockFlashLoanReceiver, + getContract, +} from '../helpers/contracts-helpers'; import {ethers} from 'ethers'; import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver'; -import {ProtocolErrors} from '../helpers/types'; +import {ProtocolErrors, eContractid} from '../helpers/types'; import BigNumber from 'bignumber.js'; +import {VariableDebtToken} from '../types/VariableDebtToken'; +import {StableDebtToken} from '../types/StableDebtToken'; const {expect} = require('chai'); makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver; const { - INCONSISTENT_PROTOCOL_ACTUAL_BALANCE, + COLLATERAL_BALANCE_IS_0, REQUESTED_AMOUNT_TOO_SMALL, - NOT_ENOUGH_LIQUIDITY_TO_BORROW, + TRANSFER_AMOUNT_EXCEEDS_BALANCE, + INVALID_FLASHLOAN_MODE } = ProtocolErrors; before(async () => { @@ -31,14 +38,16 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { await pool.deposit(weth.address, amountToDeposit, '0'); }); - it('Takes ETH flashloan, returns the funds correctly', async () => { + it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => { const {pool, deployer, weth} = testEnv; await pool.flashLoan( _mockFlashLoanReceiver.address, weth.address, ethers.utils.parseEther('0.8'), - '0x10' + 0, + '0x10', + '0' ); ethers.utils.parseUnits('10000'); @@ -57,18 +66,17 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { expect(currentLiquidityIndex.toString()).to.be.equal('1000720000000000000000000000'); }); - it('Takes an ETH flashloan as big as the available liquidity', async () => { + it('Takes an ETH flashloan with mode = 0 as big as the available liquidity', async () => { const {pool, weth} = testEnv; const reserveDataBefore = await pool.getReserveData(weth.address); - - console.log('Total liquidity is ', reserveDataBefore.availableLiquidity.toString()); - const txResult = await pool.flashLoan( _mockFlashLoanReceiver.address, weth.address, '1000720000000000000', - '0x10' + 0, + '0x10', + '0' ); const reserveData = await pool.getReserveData(weth.address); @@ -85,21 +93,79 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { expect(currentLiquidityIndex.toString()).to.be.equal('1001620648000000000000000000'); }); - it('Takes WETH flashloan, does not return the funds (revert expected)', async () => { - const {pool, deployer, weth} = testEnv; - - // move funds to the MockFlashLoanReceiver contract to pay the fee - + it('Takes WETH flashloan, does not return the funds with mode = 0. (revert expected)', async () => { + const {pool, weth, users} = testEnv; + const caller = users[1]; await _mockFlashLoanReceiver.setFailExecutionTransfer(true); await expect( - pool.flashLoan( + pool + .connect(caller.signer) + .flashLoan( + _mockFlashLoanReceiver.address, + weth.address, + ethers.utils.parseEther('0.8'), + 0, + '0x10', + '0' + ) + ).to.be.revertedWith(TRANSFER_AMOUNT_EXCEEDS_BALANCE); + }); + + it('Takes a WETH flashloan with an invalid mode. (revert expected)', async () => { + const {pool, weth, users} = testEnv; + const caller = users[1]; + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + await expect( + pool + .connect(caller.signer) + .flashLoan( + _mockFlashLoanReceiver.address, + weth.address, + ethers.utils.parseEther('0.8'), + 4, + '0x10', + '0' + ) + ).to.be.revertedWith(INVALID_FLASHLOAN_MODE); + }); + + it('Caller deposits 1000 DAI as collateral, Takes WETH flashloan with mode = 2, does not return the funds. A variable loan for caller is created', async () => { + const {dai, pool, weth, users} = testEnv; + + const caller = users[1]; + + await dai.connect(caller.signer).mint(await convertToCurrencyDecimals(dai.address, '1000')); + + await dai.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0'); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + await pool + .connect(caller.signer) + .flashLoan( _mockFlashLoanReceiver.address, weth.address, ethers.utils.parseEther('0.8'), - '0x10' - ) - ).to.be.revertedWith(INCONSISTENT_PROTOCOL_ACTUAL_BALANCE); + 2, + '0x10', + '0' + ); + const {variableDebtTokenAddress} = await pool.getReserveTokensAddresses(weth.address); + + const wethDebtToken = await getContract( + eContractid.VariableDebtToken, + variableDebtTokenAddress + ); + + const callerDebt = await wethDebtToken.balanceOf(caller.address); + + expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt'); }); it('tries to take a very small flashloan, which would result in 0 fees (revert expected)', async () => { @@ -110,7 +176,9 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, weth.address, '1', //1 wei loan - '0x10' + 2, + '0x10', + '0' ) ).to.be.revertedWith(REQUESTED_AMOUNT_TOO_SMALL); }); @@ -123,45 +191,52 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver.address, weth.address, '1004415000000000000', //slightly higher than the available liquidity - '0x10' + 2, + '0x10', + '0' ), - NOT_ENOUGH_LIQUIDITY_TO_BORROW - ).to.be.revertedWith(NOT_ENOUGH_LIQUIDITY_TO_BORROW); + TRANSFER_AMOUNT_EXCEEDS_BALANCE + ).to.be.revertedWith(TRANSFER_AMOUNT_EXCEEDS_BALANCE); }); it('tries to take a flashloan using a non contract address as receiver (revert expected)', async () => { const {pool, deployer, weth} = testEnv; - await expect(pool.flashLoan(deployer.address, weth.address, '1000000000000000000', '0x10')).to - .be.reverted; + await expect( + pool.flashLoan(deployer.address, weth.address, '1000000000000000000', 2, '0x10', '0') + ).to.be.reverted; }); - it('Deposits DAI into the reserve', async () => { - const {dai, pool} = testEnv; + it('Deposits USDC into the reserve', async () => { + const {usdc, pool} = testEnv; - await dai.mint(await convertToCurrencyDecimals(dai.address, '1000')); + await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000')); - await dai.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await usdc.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); - const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + const amountToDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); - await pool.deposit(dai.address, amountToDeposit, '0'); + await pool.deposit(usdc.address, amountToDeposit, '0'); }); - it('Takes out a 500 DAI flashloan, returns the funds correctly', async () => { - const {dai, pool, deployer: depositor} = testEnv; + it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => { + const {usdc, pool, deployer: depositor} = testEnv; await _mockFlashLoanReceiver.setFailExecutionTransfer(false); + const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500'); + await pool.flashLoan( _mockFlashLoanReceiver.address, - dai.address, - ethers.utils.parseEther('500'), - '0x10' + usdc.address, + flashloanAmount, + 0, + '0x10', + '0' ); - const reserveData = await pool.getReserveData(dai.address); - const userData = await pool.getUserReserveData(dai.address, depositor.address); + const reserveData = await pool.getReserveData(usdc.address); + const userData = await pool.getUserReserveData(usdc.address, depositor.address); const totalLiquidity = reserveData.availableLiquidity .add(reserveData.totalBorrowsStable) @@ -171,7 +246,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { const currentLiquidityIndex = reserveData.liquidityIndex.toString(); const currentUserBalance = userData.currentATokenBalance.toString(); - const expectedLiquidity = ethers.utils.parseEther('1000.450'); + const expectedLiquidity = await convertToCurrencyDecimals(usdc.address, '1000.450'); expect(totalLiquidity).to.be.equal(expectedLiquidity, 'Invalid total liquidity'); expect(currentLiqudityRate).to.be.equal('0', 'Invalid liquidity rate'); @@ -182,19 +257,101 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { expect(currentUserBalance.toString()).to.be.equal(expectedLiquidity, 'Invalid user balance'); }); - it('Takes out a 500 DAI flashloan, does not return the funds (revert expected)', async () => { - const {dai, pool} = testEnv; + it('Takes out a 500 USDC flashloan with mode = 0, does not return the funds. (revert expected)', async () => { + const {usdc, pool, users} = testEnv; + const caller = users[2]; + + const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500'); await _mockFlashLoanReceiver.setFailExecutionTransfer(true); await expect( - pool.flashLoan( - _mockFlashLoanReceiver.address, - dai.address, - ethers.utils.parseEther('500'), - '0x10' - ), - INCONSISTENT_PROTOCOL_ACTUAL_BALANCE - ).to.be.revertedWith(INCONSISTENT_PROTOCOL_ACTUAL_BALANCE); + pool + .connect(caller.signer) + .flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0') + ).to.be.revertedWith(COLLATERAL_BALANCE_IS_0); + }); + + it('Caller deposits 5 WETH as collateral, Takes a USDC flashloan with mode = 2, does not return the funds. A loan for caller is created', async () => { + const {usdc, pool, weth, users} = testEnv; + + const caller = users[2]; + + await weth.connect(caller.signer).mint(await convertToCurrencyDecimals(weth.address, '5')); + + await weth.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + const amountToDeposit = await convertToCurrencyDecimals(weth.address, '5'); + + await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, '0'); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + const flashloanAmount = await convertToCurrencyDecimals(usdc.address, '500'); + + await pool + .connect(caller.signer) + .flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0'); + const {variableDebtTokenAddress} = await pool.getReserveTokensAddresses(usdc.address); + + const usdcDebtToken = await getContract( + eContractid.VariableDebtToken, + variableDebtTokenAddress + ); + + const callerDebt = await usdcDebtToken.balanceOf(caller.address); + + expect(callerDebt.toString()).to.be.equal('500450000', 'Invalid user debt'); + }); + + 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')); + + await dai.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0'); + + const flashAmount = ethers.utils.parseEther('0.8'); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(false); + await _mockFlashLoanReceiver.setAmountToApprove(flashAmount.div(2)); + + await expect( + pool + .connect(caller.signer) + .flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 0, '0x10', '0') + ).to.be.revertedWith('ERC20: transfer amount exceeds allowance'); + }); + + it('Caller takes a WETH flashloan with mode = 1', async () => { + const {dai, pool, weth, users} = testEnv; + + const caller = users[3]; + + const flashAmount = ethers.utils.parseEther('0.8'); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + await pool + .connect(caller.signer) + .flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0'); + + const {stableDebtTokenAddress} = await pool.getReserveTokensAddresses(weth.address); + + const wethDebtToken = await getContract( + eContractid.VariableDebtToken, + stableDebtTokenAddress + ); + + const callerDebt = await wethDebtToken.balanceOf(caller.address); + + expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt'); + }); });