diff --git a/contracts/fees/MockKyberProxy.sol b/contracts/fees/MockKyberProxy.sol index 3f8e4629..c630c890 100644 --- a/contracts/fees/MockKyberProxy.sol +++ b/contracts/fees/MockKyberProxy.sol @@ -2,9 +2,8 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; import "../mocks/tokens/MintableERC20.sol"; /// @title MockKyberProxy @@ -14,8 +13,8 @@ import "../mocks/tokens/MintableERC20.sol"; /// - Mints the tokenToBurn /// - Sends back the tokenToBurn contract MockKyberProxy { - using SafeERC20 for IERC20; - using SafeERC20 for MintableERC20; + using UniversalERC20 for IERC20; + using UniversalERC20 for MintableERC20; /// @notice The token which the msg.sender of tradeWithHint will burn MintableERC20 public tokenToBurn; @@ -31,15 +30,15 @@ contract MockKyberProxy { IERC20 _toToken, address _receiver, uint256 _maxAmount, - uint minConversionRate, + uint256 minConversionRate, address _referral, bytes calldata _filtering - ) external payable returns(uint256) { + ) external payable returns (uint256) { require(tokenToBurn.mint(1 ether), "TRADE_WITH_HINT. Reverted mint()"); - if (address(_fromToken) != EthAddressLib.ethAddress()) { - _fromToken.safeTransferFrom(msg.sender, address(this), _amount); + if (!_fromToken.isETH()) { + _fromToken.universalTransferFromSenderToThis(_amount, true); } - tokenToBurn.safeTransfer(msg.sender, 1 ether); + tokenToBurn.universalTransfer(msg.sender, 1 ether); return 1 ether; } -} \ No newline at end of file +} diff --git a/contracts/fees/MockOneSplit.sol b/contracts/fees/MockOneSplit.sol index 51e4a02b..eecc89e7 100644 --- a/contracts/fees/MockOneSplit.sol +++ b/contracts/fees/MockOneSplit.sol @@ -2,16 +2,15 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "../libraries/EthAddressLib.sol"; import "../mocks/tokens/MintableERC20.sol"; import "../interfaces/IOneSplit.sol"; +import "../libraries/UniversalERC20.sol"; contract MockOneSplit is IOneSplit { - using SafeERC20 for IERC20; - using SafeERC20 for MintableERC20; + using UniversalERC20 for MintableERC20; + using UniversalERC20 for IERC20; MintableERC20 public tokenToBurn; @@ -55,9 +54,9 @@ contract MockOneSplit is IOneSplit { uint256 disableFlags ) public override payable { require(tokenToBurn.mint(10000 ether), "TRADE_WITH_HINT. Reverted mint()"); - if (address(fromToken) != EthAddressLib.ethAddress()) { - fromToken.safeTransferFrom(msg.sender, address(this), amount); + if (!fromToken.isETH()) { + fromToken.universalTransferFromSenderToThis(amount, true); } - tokenToBurn.safeTransfer(msg.sender, 10000 ether); + tokenToBurn.universalTransfer(msg.sender, 10000 ether); } -} \ No newline at end of file +} diff --git a/contracts/fees/OneSplitAdapter.sol b/contracts/fees/OneSplitAdapter.sol index b4773ac3..2b7363c0 100644 --- a/contracts/fees/OneSplitAdapter.sol +++ b/contracts/fees/OneSplitAdapter.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IOneSplit.sol"; import "../interfaces/IPriceOracleGetter.sol"; import "../interfaces/IExchangeAdapter.sol"; - +import "../libraries/UniversalERC20.sol"; /// @title OneSplitAdapter /// @author Aave /// @notice Implements the logic to exchange assets through 1Split @@ -26,6 +26,7 @@ import "../interfaces/IExchangeAdapter.sol"; contract OneSplitAdapter is IExchangeAdapter { using SafeMath for uint256; + using UniversalERC20 for IERC20; event OneSplitAdapterSetup(address oneSplit, address priceOracle, uint256 splitParts); @@ -37,20 +38,21 @@ contract OneSplitAdapter is IExchangeAdapter { /// @param _tokens the list of token addresses to approve function approveExchange(IERC20[] calldata _tokens) external override { for (uint256 i = 0; i < _tokens.length; i++) { - if (address(_tokens[i]) != EthAddressLib.ethAddress()) { - _tokens[i].safeApprove(0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476, UintConstants.maxUintMinus1()); - } + _tokens[i].universalApprove( + 0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476, + UintConstants.maxUintMinus1() + ); } } /// @notice Exchanges _amount of _from token (or ETH) to _to token (or ETH) - /// - Uses EthAddressLib.ethAddress() as the reference on 1Split of ETH + /// - Uses UniversalERC20.isETH() as the reference on 1Split of ETH /// @param _from The asset to exchange from /// @param _to The asset to exchange to /// @param _amount The amount to exchange /// @param _maxSlippage Max slippage acceptable, taken into account after the goodSwap() function exchange(address _from, address _to, uint256 _amount, uint256 _maxSlippage) external override returns(uint256) { - uint256 _value = (_from == EthAddressLib.ethAddress()) ? _amount : 0; + uint256 _value = IERC20(_from).isETH() ? _amount : 0; uint256 _fromAssetPriceInWei = IPriceOracleGetter(0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4).getAssetPrice(_from); uint256 _toAssetPriceInWei = IPriceOracleGetter(0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4).getAssetPrice(_to); @@ -76,4 +78,4 @@ contract OneSplitAdapter is IExchangeAdapter { emit Exchange(_from, _to, 0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476, _amount, _toReceivedAmount); return _toReceivedAmount; } -} \ No newline at end of file +} diff --git a/contracts/fees/TokenDistributor.sol b/contracts/fees/TokenDistributor.sol index 0c6f6bbd..3368e4e6 100644 --- a/contracts/fees/TokenDistributor.sol +++ b/contracts/fees/TokenDistributor.sol @@ -3,14 +3,13 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; import "../interfaces/IKyberNetworkProxyInterface.sol"; import "../interfaces/IExchangeAdapter.sol"; -import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; /// @title TokenDistributor @@ -25,7 +24,7 @@ import "../libraries/EthAddressLib.sol"; /// and burn it (sending to address(0) the tokenToBurn) contract TokenDistributor is ReentrancyGuard, VersionedInitializable { using SafeMath for uint256; - using SafeERC20 for IERC20; + using UniversalERC20 for IERC20; struct Distribution { address[] receivers; @@ -104,9 +103,8 @@ contract TokenDistributor is ReentrancyGuard, VersionedInitializable { /// @param _tokens list of ERC20 tokens to distribute function distribute(IERC20[] memory _tokens) public { for (uint256 i = 0; i < _tokens.length; i++) { - uint256 _balanceToDistribute = (address(_tokens[i]) != EthAddressLib.ethAddress()) - ? _tokens[i].balanceOf(address(this)) - : address(this).balance; + uint256 _balanceToDistribute = _tokens[i].universalBalanceOf(address(this)); + if (_balanceToDistribute <= 0) { continue; } @@ -129,9 +127,8 @@ contract TokenDistributor is ReentrancyGuard, VersionedInitializable { /// @param _percentages list of percentages to distribute per token function distributeWithPercentages(IERC20[] memory _tokens, uint256[] memory _percentages) public { for (uint256 i = 0; i < _tokens.length; i++) { - uint256 _amountToDistribute = (address(_tokens[i]) != EthAddressLib.ethAddress()) - ? _tokens[i].balanceOf(address(this)).mul(_percentages[i]).div(100) - : address(this).balance.mul(_percentages[i]).div(100); + uint256 _amountToDistribute = _tokens[i].universalBalanceOf(address(this)).mul(_percentages[i]).div(100); + if (_amountToDistribute <= 0) { continue; } @@ -166,13 +163,7 @@ contract TokenDistributor is ReentrancyGuard, VersionedInitializable { } if (_distribution.receivers[j] != address(0)) { - if (_tokenAddress != EthAddressLib.ethAddress()) { - _token.safeTransfer(_distribution.receivers[j], _amount); - } else { - //solium-disable-next-line - (bool _success,) = _distribution.receivers[j].call{value: _amount}(""); - require(_success, "Reverted ETH transfer"); - } + _token.universalTransfer(_distribution.receivers[j], _amount); emit Distributed(_distribution.receivers[j], _distribution.percentages[j], _amount); } else { uint256 _amountToBurn = _amount; @@ -215,4 +206,4 @@ contract TokenDistributor is ReentrancyGuard, VersionedInitializable { return IMPLEMENTATION_REVISION; } -} \ No newline at end of file +} diff --git a/contracts/flashloan/base/FlashLoanReceiverBase.sol b/contracts/flashloan/base/FlashLoanReceiverBase.sol index b94b8976..95ce9229 100644 --- a/contracts/flashloan/base/FlashLoanReceiverBase.sol +++ b/contracts/flashloan/base/FlashLoanReceiverBase.sol @@ -3,14 +3,13 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "../interfaces/IFlashLoanReceiver.sol"; import "../../interfaces/ILendingPoolAddressesProvider.sol"; -import "../../libraries/EthAddressLib.sol"; +import "../../libraries/UniversalERC20.sol"; abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { - using SafeERC20 for IERC20; + using UniversalERC20 for IERC20; using SafeMath for uint256; ILendingPoolAddressesProvider public addressesProvider; @@ -22,31 +21,9 @@ abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { receive() external payable {} function transferFundsBackToPoolInternal(address _reserve, uint256 _amount) internal { - - address payable core = addressesProvider.getLendingPoolCore(); - - transferInternal(core,_reserve, _amount); + IERC20(_reserve).universalTransfer( + addressesProvider.getLendingPoolCore(), // lending-pool core address + _amount + ); } - - function transferInternal(address payable _destination, address _reserve, uint256 _amount) internal { - if(_reserve == EthAddressLib.ethAddress()) { - //solium-disable-next-line - _destination.call{value: _amount}(""); - return; - } - - IERC20(_reserve).safeTransfer(_destination, _amount); - - - } - - function getBalanceInternal(address _target, address _reserve) internal view returns(uint256) { - if(_reserve == EthAddressLib.ethAddress()) { - - return _target.balance; - } - - return IERC20(_reserve).balanceOf(_target); - - } -} \ No newline at end of file +} diff --git a/contracts/interfaces/IExchangeAdapter.sol b/contracts/interfaces/IExchangeAdapter.sol index 54343d58..b5bd8fc3 100644 --- a/contracts/interfaces/IExchangeAdapter.sol +++ b/contracts/interfaces/IExchangeAdapter.sol @@ -2,14 +2,10 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "../libraries/EthAddressLib.sol"; import "../libraries/UintConstants.sol"; interface IExchangeAdapter { - using SafeERC20 for IERC20; - event Exchange( address indexed from, address indexed to, @@ -21,4 +17,4 @@ interface IExchangeAdapter { function approveExchange(IERC20[] calldata _tokens) external; function exchange(address _from, address _to, uint256 _amount, uint256 _maxSlippage) external returns(uint256); -} \ No newline at end of file +} diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index dc5ce58a..6945d2b0 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -17,7 +17,7 @@ import "../flashloan/interfaces/IFlashLoanReceiver.sol"; import "./LendingPoolCore.sol"; import "./LendingPoolDataProvider.sol"; import "./LendingPoolLiquidationManager.sol"; -import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; /** * @title LendingPool contract @@ -29,6 +29,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { using SafeMath for uint256; using WadRayMath for uint256; using Address for address; + using UniversalERC20 for IERC20; LendingPoolAddressesProvider public addressesProvider; LendingPoolCore public core; @@ -314,8 +315,14 @@ 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 - core.transferToReserve{value: msg.value}(_reserve, msg.sender, _amount); + //transfer to the pool + if (!IERC20(_reserve).isETH()) { //TODO: review needed, most probably we can remove it + require( + msg.value == 0, + "User is sending ETH along with the ERC20 transfer." + ); + } + IERC20(_reserve).universalTransferFromSenderToThis(_amount, true); //solium-disable-next-line emit Deposit(_reserve, msg.sender, _amount, _referralCode, block.timestamp); @@ -341,7 +348,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { onlyActiveReserve(_reserve) onlyAmountGreaterThanZero(_amount) { - uint256 currentAvailableLiquidity = core.getReserveAvailableLiquidity(_reserve); + uint256 currentAvailableLiquidity = IERC20(_reserve).universalBalanceOf(address(this)); require( currentAvailableLiquidity >= _amount, "There is not enough liquidity available to redeem" @@ -349,7 +356,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { core.updateStateOnRedeem(_reserve, _user, _amount, _aTokenBalanceAfterRedeem == 0); - core.transferToUser(_reserve, _user, _amount); + IERC20(_reserve).universalTransfer(_user, _amount); //solium-disable-next-line emit RedeemUnderlying(_reserve, _user, _amount, block.timestamp); @@ -414,7 +421,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { vars.rateMode = CoreLibrary.InterestRateMode(_interestRateMode); //check that the amount is available in the reserve - vars.availableLiquidity = core.getReserveAvailableLiquidity(_reserve); + vars.availableLiquidity = IERC20(_reserve).universalBalanceOf(address(this)); // TODO: review needed, most probably useless require( vars.availableLiquidity >= _amount, @@ -495,7 +502,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { ); //if we reached this point, we can transfer - core.transferToUser(_reserve, msg.sender, _amount); + IERC20(_reserve).universalTransfer(msg.sender, _amount); emit Borrow( _reserve, @@ -548,7 +555,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { ) = core.getUserBorrowBalances(_reserve, _onBehalfOf); vars.originationFee = core.getUserOriginationFee(_reserve, _onBehalfOf); - vars.isETH = EthAddressLib.ethAddress() == _reserve; + vars.isETH = IERC20(_reserve).isETH(); require(vars.compoundedBorrowBalance > 0, "The user does not have any borrow pending"); @@ -565,7 +572,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { } require( - !vars.isETH || msg.value >= vars.paybackAmount, + (!vars.isETH && msg.value == 0) || msg.value >= vars.paybackAmount, "Invalid msg.value sent for the repayment" ); @@ -580,11 +587,11 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { false ); - core.transferToFeeCollectionAddress{ value: vars.isETH ? vars.paybackAmount : 0 }( - _reserve, + IERC20(_reserve).universalTransferFrom( _onBehalfOf, + addressesProvider.getTokenDistributor(), vars.paybackAmount, - addressesProvider.getTokenDistributor() + false ); emit Repay( @@ -612,23 +619,26 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { ); //if the user didn't repay the origination fee, transfer the fee to the fee collection address - if(vars.originationFee > 0) { - core.transferToFeeCollectionAddress{ value: vars.isETH ? vars.originationFee : 0 }( - _reserve, + if (vars.originationFee > 0) { + IERC20(_reserve).universalTransferFrom( _onBehalfOf, + addressesProvider.getTokenDistributor(), vars.originationFee, - addressesProvider.getTokenDistributor() + false ); } - //sending the total msg.value if the transfer is ETH. - //the transferToReserve() function will take care of sending the - //excess ETH back to the caller - core.transferToReserve{ value: vars.isETH ? msg.value.sub(vars.originationFee) : 0 }( - _reserve, - msg.sender, - vars.paybackAmountMinusFees - ); + IERC20(_reserve).universalTransferFromSenderToThis(vars.paybackAmountMinusFees, false); + + if (vars.isETH) { + uint256 exceedAmount = msg.value + .sub(vars.originationFee) + .sub(vars.paybackAmountMinusFees); + //send excess ETH back to the caller if needed + if (exceedAmount > 0) { + IERC20(_reserve).universalTransfer(msg.sender, exceedAmount); + } + } emit Repay( _reserve, @@ -849,9 +859,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { { //check that the reserve has enough available liquidity //we avoid using the getAvailableLiquidity() function in LendingPoolCore to save gas - uint256 availableLiquidityBefore = _reserve == EthAddressLib.ethAddress() - ? address(core).balance - : IERC20(_reserve).balanceOf(address(core)); + uint256 availableLiquidityBefore = IERC20(_reserve).universalBalanceOf(address(core)); require( availableLiquidityBefore >= _amount, @@ -873,24 +881,25 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { //get the FlashLoanReceiver instance IFlashLoanReceiver receiver = IFlashLoanReceiver(_receiver); - address payable userPayable = payable(_receiver); - //transfer funds to the receiver - core.transferToUser(_reserve, userPayable, _amount); + IERC20(_reserve).universalTransfer(_receiver, _amount); //execute action of the receiver receiver.executeOperation(_reserve, _amount, amountFee, _params); //check that the actual balance of the core contract includes the returned amount - uint256 availableLiquidityAfter = _reserve == EthAddressLib.ethAddress() - ? address(core).balance - : IERC20(_reserve).balanceOf(address(core)); + uint256 availableLiquidityAfter = IERC20(_reserve).universalBalanceOf(address(core)); require( availableLiquidityAfter == availableLiquidityBefore.add(amountFee), "The actual balance of the protocol is inconsistent" ); + // transfer protocol fee to the Distributor contract + IERC20(_reserve).universalTransfer( + addressesProvider.getTokenDistributor(), + protocolFee + ); core.updateStateOnFlashLoan( _reserve, availableLiquidityBefore, diff --git a/contracts/lendingpool/LendingPoolCore.sol b/contracts/lendingpool/LendingPoolCore.sol index 7a65e788..b2ad2f65 100644 --- a/contracts/lendingpool/LendingPoolCore.sol +++ b/contracts/lendingpool/LendingPoolCore.sol @@ -2,7 +2,6 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; @@ -13,7 +12,7 @@ import "../interfaces/ILendingRateOracle.sol"; import "../interfaces/IReserveInterestRateStrategy.sol"; import "../libraries/WadRayMath.sol"; import "../tokenization/AToken.sol"; -import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; /** * @title LendingPoolCore contract @@ -29,10 +28,9 @@ contract LendingPoolCore is VersionedInitializable { using WadRayMath for uint256; using CoreLibrary for CoreLibrary.ReserveData; using CoreLibrary for CoreLibrary.UserReserveData; - using SafeERC20 for IERC20; + using UniversalERC20 for IERC20; using Address for address payable; - /** * @dev DEPRECATED: This event was used in previous LendingPoolCore implementations, and it has been replaced by ReserveDataUpdated() * @param reserve the address of the reserve @@ -51,7 +49,6 @@ contract LendingPoolCore is VersionedInitializable { uint256 variableBorrowIndex ); - /** * @dev Emitted when the state of a reserve is updated * @dev NOTE: This event replaces the Deprecated ReserveUpdated() event, which didn't emit the average stable borrow rate @@ -177,8 +174,6 @@ contract LendingPoolCore is VersionedInitializable { uint256 _income, uint256 _protocolFee ) external onlyLendingPool { - transferFlashLoanProtocolFeeInternal(_reserve, _protocolFee); - //compounding the cumulated interest reserves[_reserve].updateCumulativeIndexes(); @@ -362,7 +357,6 @@ contract LendingPoolCore is VersionedInitializable { _collateralToLiquidate.add(_liquidatedCollateralForFee) ); } - } /** @@ -412,109 +406,6 @@ contract LendingPoolCore is VersionedInitializable { } - /** - * @dev transfers to the user a specific amount from the reserve. - * @param _reserve the address of the reserve where the transfer is happening - * @param _user the address of the user receiving the transfer - * @param _amount the amount being transferred - **/ - function transferToUser(address _reserve, address payable _user, uint256 _amount) - external - onlyLendingPool - { - if (_reserve != EthAddressLib.ethAddress()) { - IERC20(_reserve).safeTransfer(_user, _amount); - } else { - //solium-disable-next-line - (bool result, ) = _user.call{value: _amount, gas: 50000}(""); - require(result, "Transfer of ETH failed"); - } - } - - /** - * @dev transfers the protocol fees to the fees collection address - * @param _token the address of the token being transferred - * @param _user the address of the user from where the transfer is happening - * @param _amount the amount being transferred - * @param _destination the fee receiver address - **/ - - function transferToFeeCollectionAddress( - address _token, - address _user, - uint256 _amount, - address _destination - ) external payable onlyLendingPool { - address payable feeAddress = address(uint160(_destination)); //cast the address to payable - - if (_token != EthAddressLib.ethAddress()) { - require( - msg.value == 0, - "User is sending ETH along with the ERC20 transfer. Check the value attribute of the transaction" - ); - IERC20(_token).safeTransferFrom(_user, feeAddress, _amount); - } else { - require(msg.value >= _amount, "The amount and the value sent to deposit do not match"); - //solium-disable-next-line - (bool result, ) = feeAddress.call{ value: _amount, gas: 50000}(""); - require(result, "Transfer of ETH failed"); - } - } - - /** - * @dev transfers the fees to the fees collection address in the case of liquidation - * @param _token the address of the token being transferred - * @param _amount the amount being transferred - * @param _destination the fee receiver address - **/ - function liquidateFee( - address _token, - uint256 _amount, - address _destination - ) external payable onlyLendingPool { - address payable feeAddress = address(uint160(_destination)); //cast the address to payable - require( - msg.value == 0, - "Fee liquidation does not require any transfer of value" - ); - - if (_token != EthAddressLib.ethAddress()) { - IERC20(_token).safeTransfer(feeAddress, _amount); - } else { - //solium-disable-next-line - (bool result, ) = feeAddress.call{ value: _amount, gas: 50000}(""); - require(result, "Transfer of ETH failed"); - } - } - - /** - * @dev transfers an amount from a user to the destination reserve - * @param _reserve the address of the reserve where the amount is being transferred - * @param _user the address of the user from where the transfer is happening - * @param _amount the amount being transferred - **/ - function transferToReserve(address _reserve, address payable _user, uint256 _amount) - external - payable - onlyLendingPool - { - if (_reserve != EthAddressLib.ethAddress()) { - require(msg.value == 0, "User is sending ETH along with the ERC20 transfer."); - IERC20(_reserve).safeTransferFrom(_user, address(this), _amount); - - } else { - require(msg.value >= _amount, "The amount and the value sent to deposit do not match"); - - if (msg.value > _amount) { - //send back excess ETH - uint256 excessAmount = msg.value.sub(_amount); - //solium-disable-next-line - (bool result, ) = _user.call{ value: _amount, gas: 50000}(""); - require(result, "Transfer of ETH failed"); - } - } - } - /** * @notice data access functions **/ @@ -610,22 +501,6 @@ contract LendingPoolCore is VersionedInitializable { return reserve.aTokenAddress; } - /** - * @dev gets the available liquidity in the reserve. The available liquidity is the balance of the core contract - * @param _reserve the reserve address - * @return the available liquidity - **/ - function getReserveAvailableLiquidity(address _reserve) public view returns (uint256) { - uint256 balance = 0; - - if (_reserve == EthAddressLib.ethAddress()) { - balance = address(this).balance; - } else { - balance = IERC20(_reserve).balanceOf(address(this)); - } - return balance; - } - /** * @dev gets the total liquidity in the reserve. The total liquidity is the balance of the core contract + total borrows * @param _reserve the reserve address @@ -633,7 +508,10 @@ contract LendingPoolCore is VersionedInitializable { **/ function getReserveTotalLiquidity(address _reserve) public view returns (uint256) { CoreLibrary.ReserveData storage reserve = reserves[_reserve]; - return getReserveAvailableLiquidity(_reserve).add(reserve.getTotalBorrows()); + + return IERC20(_reserve) + .universalBalanceOf(addressesProvider.getLendingPool()) + .add(reserve.getTotalBorrows()); } /** @@ -900,8 +778,9 @@ contract LendingPoolCore is VersionedInitializable { return 0; } - uint256 availableLiquidity = getReserveAvailableLiquidity(_reserve); - + uint256 availableLiquidity = IERC20(_reserve).universalBalanceOf( + addressesProvider.getLendingPool() + ); return totalBorrows.rayDiv(availableLiquidity.add(totalBorrows)); } @@ -1443,7 +1322,6 @@ contract LendingPoolCore is VersionedInitializable { //solium-disable-next-line user.lastUpdateTimestamp = uint40(block.timestamp); - } /** @@ -1731,14 +1609,15 @@ contract LendingPoolCore is VersionedInitializable { CoreLibrary.ReserveData storage reserve = reserves[_reserve]; uint256 currentAvgStableRate = reserve.currentAverageStableBorrowRate; + uint256 avialableLiquidity = IERC20(_reserve).universalBalanceOf(addressesProvider.getLendingPool()) + .add(_liquidityAdded) + .sub(_liquidityTaken); (uint256 newLiquidityRate, uint256 newStableRate, uint256 newVariableRate) = IReserveInterestRateStrategy( - reserve - .interestRateStrategyAddress - ) - .calculateInterestRates( + reserve.interestRateStrategyAddress + ).calculateInterestRates( _reserve, - getReserveAvailableLiquidity(_reserve).add(_liquidityAdded).sub(_liquidityTaken), + avialableLiquidity, reserve.totalBorrowsStable, reserve.totalBorrowsVariable, currentAvgStableRate @@ -1762,24 +1641,6 @@ contract LendingPoolCore is VersionedInitializable { ); } - /** - * @dev transfers to the protocol fees of a flashloan to the fees collection address - * @param _token the address of the token being transferred - * @param _amount the amount being transferred - **/ - - function transferFlashLoanProtocolFeeInternal(address _token, uint256 _amount) internal { - address payable receiver = payable(addressesProvider.getTokenDistributor()); - - if (_token != EthAddressLib.ethAddress()) { - IERC20(_token).safeTransfer(receiver, _amount); - } else { - //solium-disable-next-line - (bool result, ) = receiver.call{ value: _amount }(""); - require(result, "Transfer to token distributor failed"); - } - } - /** * @dev updates the internal configuration of the core **/ diff --git a/contracts/lendingpool/LendingPoolDataProvider.sol b/contracts/lendingpool/LendingPoolDataProvider.sol index d1c7318b..4cef745d 100644 --- a/contracts/lendingpool/LendingPoolDataProvider.sol +++ b/contracts/lendingpool/LendingPoolDataProvider.sol @@ -2,6 +2,7 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; import "../libraries/CoreLibrary.sol"; @@ -10,6 +11,7 @@ import "../libraries/WadRayMath.sol"; import "../interfaces/IPriceOracleGetter.sol"; import "../interfaces/IFeeProvider.sol"; import "../tokenization/AToken.sol"; +import "../libraries/UniversalERC20.sol"; import "./LendingPoolCore.sol"; @@ -22,6 +24,7 @@ import "./LendingPoolCore.sol"; contract LendingPoolDataProvider is VersionedInitializable { using SafeMath for uint256; using WadRayMath for uint256; + using UniversalERC20 for IERC20; LendingPoolCore public core; LendingPoolAddressesProvider public addressesProvider; @@ -395,7 +398,7 @@ contract LendingPoolDataProvider is VersionedInitializable { ) { totalLiquidity = core.getReserveTotalLiquidity(_reserve); - availableLiquidity = core.getReserveAvailableLiquidity(_reserve); + availableLiquidity = IERC20(_reserve).universalBalanceOf(addressesProvider.getLendingPool()); totalBorrowsStable = core.getReserveTotalBorrowsStable(_reserve); totalBorrowsVariable = core.getReserveTotalBorrowsVariable(_reserve); liquidityRate = core.getReserveCurrentLiquidityRate(_reserve); diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index a5f9bea7..6876ddeb 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -2,6 +2,7 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; @@ -15,6 +16,7 @@ import "../libraries/WadRayMath.sol"; import "./LendingPoolCore.sol"; import "./LendingPoolDataProvider.sol"; import "../interfaces/IPriceOracleGetter.sol"; +import "../libraries/UniversalERC20.sol"; /** * @title LendingPoolLiquidationManager contract @@ -25,6 +27,7 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl using SafeMath for uint256; using WadRayMath for uint256; using Address for address; + using UniversalERC20 for IERC20; LendingPoolAddressesProvider public addressesProvider; LendingPoolCore core; @@ -216,7 +219,7 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl //if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve if (!_receiveAToken) { - uint256 currentAvailableCollateral = core.getReserveAvailableLiquidity(_collateral); + uint256 currentAvailableCollateral = IERC20(_collateral).universalBalanceOf(address(this)); if (currentAvailableCollateral < maxCollateralToLiquidate) { return ( uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY), @@ -246,11 +249,15 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl //otherwise receives the underlying asset //burn the equivalent amount of atoken collateralAtoken.burnOnLiquidation(_user, maxCollateralToLiquidate); - core.transferToUser(_collateral, msg.sender, maxCollateralToLiquidate); + // because liquidate function executed as delegated call this will be LendingPool contract address + // and funds will be transferred from there + IERC20(_collateral).universalTransfer(msg.sender, maxCollateralToLiquidate); } //transfers the principal currency to the pool - core.transferToReserve{value: msg.value}(_reserve, msg.sender, vars.actualAmountToLiquidate); + IERC20(_reserve).universalTransferFromSenderToThis( + vars.actualAmountToLiquidate, true + ); if (vars.feeLiquidated > 0) { //if there is enough collateral to liquidate the fee, first transfer burn an equivalent amount of @@ -258,10 +265,9 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl collateralAtoken.burnOnLiquidation(_user, vars.liquidatedCollateralForFee); //then liquidate the fee by transferring it to the fee collection address - core.liquidateFee( - _collateral, - vars.liquidatedCollateralForFee, - addressesProvider.getTokenDistributor() + IERC20(_collateral).universalTransfer( + addressesProvider.getTokenDistributor(), + vars.liquidatedCollateralForFee ); emit OriginationFeeLiquidated( diff --git a/contracts/libraries/EthAddressLib.sol b/contracts/libraries/EthAddressLib.sol deleted file mode 100644 index 07d485f9..00000000 --- a/contracts/libraries/EthAddressLib.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.6.8; - -library EthAddressLib { - - /** - * @dev returns the address used within the protocol to identify ETH - * @return the address assigned to ETH - */ - function ethAddress() internal pure returns(address) { - return 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - } -} \ No newline at end of file diff --git a/contracts/libraries/UniversalERC20.sol b/contracts/libraries/UniversalERC20.sol new file mode 100644 index 00000000..03236026 --- /dev/null +++ b/contracts/libraries/UniversalERC20.sol @@ -0,0 +1,191 @@ +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; + +/** +* @title UniversalERC20 library +* @author Aave inspired by @k06a (Anton Bukov) +* original version: https://github.com/CryptoManiacsZone/1inchProtocol/blob/master/contracts/UniversalERC20.sol +* @dev Provides unified interface for ERC20 and native ETH operations +**/ +library UniversalERC20 { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + IERC20 private constant ZERO_ADDRESS = IERC20( + 0x0000000000000000000000000000000000000000 + ); + // @notice mock address of ETH + IERC20 private constant ETH_ADDRESS = IERC20( + 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE + ); + + uint256 private constant DEFAULT_TRANSFER_GAS = 50000; + + /** + * @dev Moves amount of asset from caller to recipient + * @param token underlying asset address + * @param to asset recipient + * @param amount to move + **/ + function universalTransfer( + IERC20 token, + address to, + uint256 amount + ) internal { + if (amount == 0) { + return; + } + + if (isETH(token)) { + (bool result, ) = payable(to).call{ + value: amount, + gas: DEFAULT_TRANSFER_GAS + }(""); + require(result, "ETH_TRANSFER_FAILED"); + } else { + token.safeTransfer(to, amount); + } + } + + /** + * @dev Moves amount of asset from sender to recipient + * in terms of ETH it redirects amount in transaction to recipient + * @param token underlying asset address + * @param from asset sender + * @param to asset recipient + * @param amount to move + * @param returnExcess if true returns exceeded amount to sender + **/ + function universalTransferFrom( + IERC20 token, + address from, + address to, + uint256 amount, + bool returnExcess + ) internal { + if (amount == 0) { + return; + } + + if (isETH(token)) { + require( + msg.value >= amount, + "Wrong usage of ETH.universalTransferFrom()" + ); // TODO: think one more time from == msg.sender + if (to != address(this)) { + (bool result, ) = payable(to).call{ + value: amount, + gas: DEFAULT_TRANSFER_GAS + }(""); + require(result, "ETH_TRANSFER_FAILED"); + } + if (returnExcess && msg.value > amount) { + (bool result, ) = payable(from).call{ + value: msg.value.sub(amount), + gas: DEFAULT_TRANSFER_GAS + }(""); + require(result, "ETH_TRANSFER_FAILED"); + } + } else { + token.safeTransferFrom(from, to, amount); + } + } + + /** + * @dev Moves amount of asset from caller to this contract + * @param token underlying asset address + * @param amount to move + **/ + function universalTransferFromSenderToThis(IERC20 token, uint256 amount, bool returnExcess) + internal + { + if (amount == 0) { + return; + } + + if (isETH(token)) { + if (msg.value > amount && returnExcess) { + // Return remainder if exist + (bool result, ) = msg.sender.call{ + value: msg.value.sub(amount), + gas: 50000 + }(""); + require(result, "ETH_TRANSFER_FAILED"); + } + } else { + token.safeTransferFrom(msg.sender, address(this), amount); + } + } + + /** + * @dev Sets the allowance over the caller's tokens to recipient address. + * @param token underlying asset address + * @param to allowance recipient + * @param amount of the allowance + **/ + function universalApprove( + IERC20 token, + address to, + uint256 amount + ) internal { + if (!isETH(token)) { + if (amount > 0 && token.allowance(address(this), to) > 0) { + token.safeApprove(to, 0); + } + token.safeApprove(to, amount); + } + } + + /** + * @dev Returns the amount of underlying asset owned by address + * @param token underlying asset address + * @param who address to check + * @return balance of the who address + **/ + function universalBalanceOf(IERC20 token, address who) + internal + view + returns (uint256) + { + if (isETH(token)) { + return who.balance; + } else { + return token.balanceOf(who); + } + } + + /** + * @dev Returns decimals of underlying asset + * @param token underlying asset address + * @return decimals + **/ + function universalDecimals(IERC20 token) internal view returns (uint256) { + if (isETH(token)) { + return 18; + } + + (bool success, bytes memory data) = address(token).staticcall{ + gas: 10000 + }(abi.encodeWithSignature("decimals()")); + if (!success || data.length == 0) { + (success, data) = address(token).staticcall{gas: 10000}( + abi.encodeWithSignature("DECIMALS()") + ); + } + + return (success && data.length > 0) ? abi.decode(data, (uint256)) : 18; + } + + /** + * @dev Checks is underlying asset ETH or not + * @param token underlying asset address + * @return boolean + **/ + function isETH(IERC20 token) internal pure returns (bool) { + return (address(token) == address(ZERO_ADDRESS) || + address(token) == address(ETH_ADDRESS)); + } +} diff --git a/contracts/misc/ChainlinkProxyPriceProvider.sol b/contracts/misc/ChainlinkProxyPriceProvider.sol index e2c6d8fa..3695496c 100644 --- a/contracts/misc/ChainlinkProxyPriceProvider.sol +++ b/contracts/misc/ChainlinkProxyPriceProvider.sol @@ -2,10 +2,12 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + import "../interfaces/IPriceOracleGetter.sol"; import "../interfaces/IChainlinkAggregator.sol"; -import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; /// @title ChainlinkProxyPriceProvider /// @author Aave @@ -15,6 +17,7 @@ import "../libraries/EthAddressLib.sol"; /// - Owned by the Aave governance system, allowed to add sources for assets, replace them /// and change the fallbackOracle contract ChainlinkProxyPriceProvider is IPriceOracleGetter, Ownable { + using UniversalERC20 for IERC20; event AssetSourceUpdated(address indexed asset, address indexed source); event FallbackOracleUpdated(address indexed fallbackOracle); @@ -68,7 +71,7 @@ contract ChainlinkProxyPriceProvider is IPriceOracleGetter, Ownable { /// @param _asset The asset address function getAssetPrice(address _asset) public override view returns(uint256) { IChainlinkAggregator source = assetsSources[_asset]; - if (_asset == EthAddressLib.ethAddress()) { + if (IERC20(_asset).isETH()) { return 1 ether; } else { // If there is no registered source for the asset, call the fallbackOracle @@ -107,4 +110,4 @@ contract ChainlinkProxyPriceProvider is IPriceOracleGetter, Ownable { function getFallbackOracle() external view returns(address) { return address(fallbackOracle); } -} \ No newline at end of file +} diff --git a/contracts/misc/WalletBalanceProvider.sol b/contracts/misc/WalletBalanceProvider.sol index 36c28bb4..73b8624e 100644 --- a/contracts/misc/WalletBalanceProvider.sol +++ b/contracts/misc/WalletBalanceProvider.sol @@ -6,7 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../configuration/LendingPoolAddressesProvider.sol"; import "../lendingpool/LendingPoolCore.sol"; -import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; /** @@ -19,6 +19,7 @@ import "../libraries/EthAddressLib.sol"; contract WalletBalanceProvider { using Address for address; + using UniversalERC20 for IERC20; LendingPoolAddressesProvider provider; @@ -59,7 +60,7 @@ contract WalletBalanceProvider { for (uint256 i = 0; i < _users.length; i++) { for (uint256 j = 0; j < _tokens.length; j++) { uint256 _offset = i * _tokens.length; - if (_tokens[j] == EthAddressLib.ethAddress()) { + if (IERC20(_tokens[j]).isETH()) { balances[_offset + j] = _users[i].balance; // ETH balance } else { if (!_tokens[j].isContract()) { @@ -91,7 +92,7 @@ contract WalletBalanceProvider { balances[j] = 0; continue; } - if (reserves[j] != EthAddressLib.ethAddress()) { + if (!IERC20(reserves[j]).isETH()) { balances[j] = balanceOf(_user, reserves[j]); } else { balances[j] = _user.balance; // ETH balance @@ -100,4 +101,4 @@ contract WalletBalanceProvider { return (reserves, balances); } -} \ No newline at end of file +} diff --git a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol index 688d507f..203439d2 100644 --- a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol +++ b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol @@ -2,13 +2,17 @@ pragma solidity ^0.6.8; import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../flashloan/base/FlashLoanReceiverBase.sol"; import "../tokens/MintableERC20.sol"; +import "../../libraries/UniversalERC20.sol"; contract MockFlashLoanReceiver is FlashLoanReceiverBase { using SafeMath for uint256; + using UniversalERC20 for IERC20; + event ExecutedWithFail(address _reserve, uint256 _amount, uint256 _fee); event ExecutedWithSuccess(address _reserve, uint256 _amount, uint256 _fee); @@ -32,7 +36,10 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { //check the contract has the specified balance - require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance for the contract"); + require( + _amount <= IERC20(_reserve).universalBalanceOf(address(this)), + "Invalid balance for the contract" + ); if(failExecution) { emit ExecutedWithFail(_reserve, _amount, _fee); @@ -42,11 +49,11 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase { //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 - if(_reserve != EthAddressLib.ethAddress()) { + if(!IERC20(_reserve).isETH()) { token.mint(_fee); } //returning amount + fee to the destination transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); emit ExecutedWithSuccess(_reserve, _amount, _fee); } -} \ No newline at end of file +}