diff --git a/contracts/flashloan/base/FlashLoanReceiverBase.sol b/contracts/flashloan/base/FlashLoanReceiverBase.sol index 39a7c948..229a45d2 100644 --- a/contracts/flashloan/base/FlashLoanReceiverBase.sol +++ b/contracts/flashloan/base/FlashLoanReceiverBase.sol @@ -6,6 +6,7 @@ import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '../interfaces/IFlashLoanReceiver.sol'; import '../../interfaces/ILendingPoolAddressesProvider.sol'; import '../../libraries/UniversalERC20.sol'; +import '@nomiclabs/buidler/console.sol'; abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { using UniversalERC20 for IERC20; @@ -19,10 +20,12 @@ abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { receive() external payable {} - function transferFundsBackToPoolInternal(address _reserve, uint256 _amount) internal { - address payable pool = payable(addressesProvider.getLendingPool()); - - transferInternal(pool, _reserve, _amount); + function transferFundsBackInternal( + address _reserve, + address _destination, + uint256 _amount + ) internal { + transferInternal(payable(_destination), _reserve, _amount); } function transferInternal( @@ -30,12 +33,6 @@ abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { address _reserve, uint256 _amount ) internal { - if (IERC20(_reserve).isETH()) { - //solium-disable-next-line - _destination.call{value: _amount}(''); - return; - } - IERC20(_reserve).universalTransfer(_destination, _amount); } diff --git a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol index 93a6a911..4392c348 100644 --- a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol +++ b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol @@ -10,6 +10,7 @@ pragma solidity ^0.6.8; interface IFlashLoanReceiver { function executeOperation( address _reserve, + address _destination, uint256 _amount, uint256 _fee, bytes calldata _params diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 4e016220..3588117a 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -262,7 +262,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { ValidationLogic.validateDeposit(reserve, _amount); - AToken aToken = AToken(reserve.aTokenAddress); + AToken aToken = AToken(payable(reserve.aTokenAddress)); bool isFirstDeposit = aToken.balanceOf(msg.sender) == 0; @@ -276,8 +276,8 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { //minting AToken to user 1:1 with the specific exchange rate aToken.mintOnDeposit(msg.sender, _amount); - //transfer to the core contract - IERC20(_reserve).universalTransferFromSenderToThis(_amount, true); + //transfer to the aToken contract + IERC20(_reserve).universalTransferFrom(msg.sender, address(aToken), _amount, true); //solium-disable-next-line emit Deposit(_reserve, msg.sender, _amount, _referralCode, block.timestamp); @@ -299,16 +299,19 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { ReserveLogic.ReserveData storage reserve = reserves[_reserve]; UserLogic.UserReserveData storage user = usersReserveData[_user][_reserve]; + AToken aToken = AToken(payable(reserve.aTokenAddress)); + ValidationLogic.validateRedeem(reserve, _reserve, _amount); + reserve.updateCumulativeIndexesAndTimestamp(); + + reserve.updateInterestRates(_reserve, 0, _amount); + if (_aTokenBalanceAfterRedeem == 0) { user.useAsCollateral = false; } - reserve.updateCumulativeIndexesAndTimestamp(); - reserve.updateInterestRates(_reserve, 0, _amount); - - IERC20(_reserve).universalTransfer(_user, _amount); + AToken(reserve.aTokenAddress).transferUnderlyingTo(_user, _amount); //solium-disable-next-line emit RedeemUnderlying(_reserve, _user, _amount, block.timestamp); @@ -368,7 +371,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { reserve.updateInterestRates(_reserve, 0, _amount); //if we reached this point, we can transfer - IERC20(_reserve).universalTransfer(msg.sender, _amount); + AToken(reserve.aTokenAddress).transferUnderlyingTo(msg.sender, _amount); emit Borrow( _reserve, @@ -446,7 +449,12 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { reserve.updateInterestRates(_reserve, vars.paybackAmount, 0); - IERC20(_reserve).universalTransferFromSenderToThis(vars.paybackAmount, false); + IERC20(_reserve).universalTransferFrom( + msg.sender, + reserve.aTokenAddress, + vars.paybackAmount, + false + ); if (IERC20(_reserve).isETH()) { //send excess ETH back to the caller if needed @@ -635,6 +643,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { uint256 protocolFeeBips; uint256 amountFee; uint256 protocolFee; + address payable aTokenAddress; } /** @@ -655,8 +664,10 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { ReserveLogic.ReserveData storage reserve = reserves[_reserve]; + vars.aTokenAddress = payable(reserve.aTokenAddress); + //check that the reserve has enough available liquidity - vars.availableLiquidityBefore = IERC20(_reserve).universalBalanceOf(address(this)); + vars.availableLiquidityBefore = IERC20(_reserve).universalBalanceOf(vars.aTokenAddress); //calculate amount fee vars.amountFee = _amount.mul(FLASHLOAN_FEE_TOTAL).div(10000); @@ -679,13 +690,13 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { address payable userPayable = address(uint160(_receiver)); //transfer funds to the receiver - IERC20(_reserve).universalTransfer(userPayable, _amount); + AToken(vars.aTokenAddress).transferUnderlyingTo(userPayable, _amount); //execute action of the receiver - receiver.executeOperation(_reserve, _amount, vars.amountFee, _params); + receiver.executeOperation(_reserve, vars.aTokenAddress, _amount, vars.amountFee, _params); //check that the actual balance of the core contract includes the returned amount - uint256 availableLiquidityAfter = IERC20(_reserve).universalBalanceOf(address(this)); + uint256 availableLiquidityAfter = IERC20(_reserve).universalBalanceOf(vars.aTokenAddress); require( availableLiquidityAfter == vars.availableLiquidityBefore.add(vars.amountFee), @@ -699,7 +710,11 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { vars.protocolFee ); - IERC20(_reserve).universalTransfer(addressesProvider.getTokenDistributor(), vars.protocolFee); + //transfer funds to the receiver + AToken(vars.aTokenAddress).transferUnderlyingTo( + addressesProvider.getTokenDistributor(), + vars.protocolFee + ); //solium-disable-next-line emit FlashLoan(_receiver, _reserve, _amount, vars.amountFee, vars.protocolFee, block.timestamp); @@ -777,7 +792,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { { ReserveLogic.ReserveData memory reserve = reserves[_reserve]; return ( - IERC20(_reserve).universalBalanceOf(address(this)), + IERC20(_reserve).universalBalanceOf(reserve.aTokenAddress), IERC20(reserve.stableDebtTokenAddress).totalSupply(), IERC20(reserve.variableDebtTokenAddress).totalSupply(), reserve.currentLiquidityRate, diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 7b9f6935..967a0087 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -201,13 +201,21 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl //of _collateral to cover the actual amount that is being liquidated, hence we liquidate //a smaller amount + vars.collateralAtoken = AToken(payable(collateralReserve.aTokenAddress)); + + //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; } //if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve if (!_receiveAToken) { - uint256 currentAvailableCollateral = IERC20(_collateral).universalBalanceOf(address(this)); + uint256 currentAvailableCollateral = IERC20(_collateral).universalBalanceOf( + address(vars.collateralAtoken) + ); if (currentAvailableCollateral < vars.maxCollateralToLiquidate) { return ( uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY), @@ -216,7 +224,6 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl } } - //TODO Burn debt tokens if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn( _user, @@ -233,8 +240,6 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl ); } - vars.collateralAtoken = AToken(collateralReserve.aTokenAddress); - //if liquidator reclaims the aToken, he receives the equivalent atoken amount if (_receiveAToken) { vars.collateralAtoken.transferOnLiquidation(_user, msg.sender, vars.maxCollateralToLiquidate); @@ -242,12 +247,16 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl //otherwise receives the underlying asset //burn the equivalent amount of atoken vars.collateralAtoken.burnOnLiquidation(_user, vars.maxCollateralToLiquidate); - - IERC20(_collateral).universalTransfer(msg.sender, vars.maxCollateralToLiquidate); + vars.collateralAtoken.transferUnderlyingTo(msg.sender, vars.maxCollateralToLiquidate); } - //transfers the principal currency to the pool - IERC20(_reserve).universalTransferFromSenderToThis(vars.actualAmountToLiquidate, true); + //transfers the principal currency to the aToken + IERC20(_reserve).universalTransferFrom( + msg.sender, + principalReserve.aTokenAddress, + vars.actualAmountToLiquidate, + true + ); emit LiquidationCall( _collateral, diff --git a/contracts/libraries/ReserveLogic.sol b/contracts/libraries/ReserveLogic.sol index da0e7a6c..0c9ed550 100644 --- a/contracts/libraries/ReserveLogic.sol +++ b/contracts/libraries/ReserveLogic.sol @@ -15,6 +15,7 @@ import '../interfaces/ILendingRateOracle.sol'; import '../interfaces/IReserveInterestRateStrategy.sol'; import '../tokenization/AToken.sol'; import './WadRayMath.sol'; +import '@nomiclabs/buidler/console.sol'; /** * @title ReserveLogic library @@ -56,7 +57,7 @@ library ReserveLogic { /** * @dev address of the aToken representing the asset **/ - address aTokenAddress; + address payable aTokenAddress; address stableDebtTokenAddress; address variableDebtTokenAddress; /** @@ -185,7 +186,7 @@ library ReserveLogic { _self.lastVariableBorrowCumulativeIndex = WadRayMath.ray(); } - _self.aTokenAddress = _aTokenAddress; + _self.aTokenAddress = payable(_aTokenAddress); _self.stableDebtTokenAddress = _stableDebtAddress; _self.variableDebtTokenAddress = _variableDebtAddress; _self.decimals = _decimals; @@ -348,12 +349,7 @@ library ReserveLogic { uint256 currentAvgStableRate = IStableDebtToken(_reserve.stableDebtTokenAddress) .getAverageStableRate(); - uint256 balance = IERC20(_reserveAddress).universalBalanceOf(address(this)); - - //if the reserve is ETH, the msg.value has already been cumulated to the balance of the reserve - if (IERC20(_reserveAddress).isETH()) { - balance = balance.sub(msg.value); - } + uint256 balance = IERC20(_reserveAddress).universalBalanceOf(_reserve.aTokenAddress); ( uint256 newLiquidityRate, diff --git a/contracts/libraries/ValidationLogic.sol b/contracts/libraries/ValidationLogic.sol index 23c81040..b72dd8b6 100644 --- a/contracts/libraries/ValidationLogic.sol +++ b/contracts/libraries/ValidationLogic.sol @@ -53,7 +53,10 @@ library ValidationLogic { require(msg.sender == _reserve.aTokenAddress, '31'); - uint256 currentAvailableLiquidity = IERC20(_reserveAddress).universalBalanceOf(address(this)); + uint256 currentAvailableLiquidity = IERC20(_reserveAddress).universalBalanceOf( + address(_reserve.aTokenAddress) + ); + require(currentAvailableLiquidity >= _amount, '4'); } @@ -117,7 +120,9 @@ library ValidationLogic { ); //check that the amount is available in the reserve - vars.availableLiquidity = IERC20(_reserveAddress).universalBalanceOf(address(this)); + vars.availableLiquidity = IERC20(_reserveAddress).universalBalanceOf( + address(_reserve.aTokenAddress) + ); require(vars.availableLiquidity >= _amount, '7'); diff --git a/contracts/misc/AaveProtocolTestHelpers.sol b/contracts/misc/AaveProtocolTestHelpers.sol index c3a5e185..235e2a06 100644 --- a/contracts/misc/AaveProtocolTestHelpers.sol +++ b/contracts/misc/AaveProtocolTestHelpers.sol @@ -39,7 +39,10 @@ contract AaveProtocolTestHelpers { TokenData[] memory aTokens = new TokenData[](reserves.length); for (uint256 i = 0; i < reserves.length; i++) { (address aTokenAddress, , ) = pool.getReserveTokensAddresses(reserves[i]); - aTokens[i] = TokenData({symbol: AToken(aTokenAddress).symbol(), tokenAddress: aTokenAddress}); + aTokens[i] = TokenData({ + symbol: AToken(payable(aTokenAddress)).symbol(), + tokenAddress: aTokenAddress + }); } return aTokens; } diff --git a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol index 387684c0..53379d92 100644 --- a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol +++ b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol @@ -25,6 +25,7 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { function executeOperation( address _reserve, + address _destination, uint256 _amount, uint256 _fee, bytes memory _params @@ -34,7 +35,7 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { //check the contract has the specified balance require( - _amount <= IERC20(_reserve).universalBalanceOf(address(this)), + _amount <= getBalanceInternal(address(this), _reserve), 'Invalid balance for the contract' ); @@ -49,8 +50,10 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { if (!IERC20(_reserve).isETH()) { token.mint(_fee); } + //returning amount + fee to the destination - transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); + transferFundsBackInternal(_reserve, _destination, _amount.add(_fee)); + emit ExecutedWithSuccess(_reserve, _amount, _fee); } } diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index ac3360c5..d1ddc4f0 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.6.8; -import './ERC20.sol'; -import '../configuration/LendingPoolAddressesProvider.sol'; -import '../lendingpool/LendingPool.sol'; -import '../libraries/WadRayMath.sol'; +import {ERC20} from './ERC20.sol'; +import {LendingPoolAddressesProvider} from '../configuration/LendingPoolAddressesProvider.sol'; +import {LendingPool} from '../lendingpool/LendingPool.sol'; +import {WadRayMath} from '../libraries/WadRayMath.sol'; +import {UniversalERC20} from '../libraries/UniversalERC20.sol'; +import '@nomiclabs/buidler/console.sol'; /** * @title Aave ERC20 AToken @@ -14,6 +16,7 @@ import '../libraries/WadRayMath.sol'; */ contract AToken is ERC20 { using WadRayMath for uint256; + using UniversalERC20 for ERC20; uint256 public constant UINT_MAX_VALUE = uint256(-1); @@ -636,4 +639,31 @@ contract AToken is ERC20 { return false; } } + + /** + * @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 + onlyLendingPool + returns (uint256) + { + ERC20(underlyingAssetAddress).universalTransfer(_target, _amount); + return _amount; + } + + /** + * @dev receive() function for aTokens who hold ETH as the underlying asset + **/ + receive() external payable { + require( + ERC20(underlyingAssetAddress).isETH(), + 'Transfers are only allowed if the underlying asset is ETH' + ); + } } diff --git a/deployed-contracts.json b/deployed-contracts.json index 9eb264aa..aaa46968 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -438,4 +438,4 @@ "deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6" } } -} +} \ No newline at end of file diff --git a/test/atoken-modifiers.spec.ts b/test/atoken-modifiers.spec.ts index 7a258d54..c3f54a51 100644 --- a/test/atoken-modifiers.spec.ts +++ b/test/atoken-modifiers.spec.ts @@ -25,4 +25,11 @@ makeSuite('AToken: Modifiers', (testEnv: TestEnv) => { aDai.transferOnLiquidation(deployer.address, users[0].address, '1') ).to.be.revertedWith(INVALID_POOL_CALLER_MSG_1); }); + + it('Tries to invoke transferUnderlyingTo not being the LendingPool', async () => { + const {deployer, users, aDai} = testEnv; + await expect(aDai.transferUnderlyingTo(deployer.address, '1')).to.be.revertedWith( + INVALID_POOL_CALLER_MSG_1 + ); + }); });