From fea677a6076d67fd7b4a1dd7f70944a44ca99df2 Mon Sep 17 00:00:00 2001 From: andyk Date: Tue, 2 Jun 2020 16:49:24 +0300 Subject: [PATCH] migration to 1inch UniversalERC20 --- contracts/fees/MockKyberProxy.sol | 10 +- contracts/fees/MockOneSplit.sol | 4 +- contracts/fees/TokenDistributor.sol | 20 +-- .../flashloan/base/FlashLoanReceiverBase.sol | 22 +-- contracts/lendingpool/LendingPool.sol | 10 +- contracts/lendingpool/LendingPoolCore.sol | 72 ++++----- contracts/libraries/UniversalERC20.sol | 137 ++++++++++++++++++ 7 files changed, 188 insertions(+), 87 deletions(-) create mode 100644 contracts/libraries/UniversalERC20.sol diff --git a/contracts/fees/MockKyberProxy.sol b/contracts/fees/MockKyberProxy.sol index 3f8e4629..354b84a3 100644 --- a/contracts/fees/MockKyberProxy.sol +++ b/contracts/fees/MockKyberProxy.sol @@ -5,6 +5,7 @@ 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 @@ -16,6 +17,7 @@ import "../mocks/tokens/MintableERC20.sol"; contract MockKyberProxy { using SafeERC20 for IERC20; using SafeERC20 for MintableERC20; + using UniversalERC20 for IERC20; /// @notice The token which the msg.sender of tradeWithHint will burn MintableERC20 public tokenToBurn; @@ -31,15 +33,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()) { + if (!_fromToken.isETH()) { _fromToken.safeTransferFrom(msg.sender, address(this), _amount); } tokenToBurn.safeTransfer(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..f6e29d68 100644 --- a/contracts/fees/MockOneSplit.sol +++ b/contracts/fees/MockOneSplit.sol @@ -8,10 +8,12 @@ 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 IERC20; MintableERC20 public tokenToBurn; @@ -55,7 +57,7 @@ contract MockOneSplit is IOneSplit { uint256 disableFlags ) public override payable { require(tokenToBurn.mint(10000 ether), "TRADE_WITH_HINT. Reverted mint()"); - if (address(fromToken) != EthAddressLib.ethAddress()) { + if (!fromToken.isETH()) { fromToken.safeTransferFrom(msg.sender, address(this), amount); } tokenToBurn.safeTransfer(msg.sender, 10000 ether); diff --git a/contracts/fees/TokenDistributor.sol b/contracts/fees/TokenDistributor.sol index 0c6f6bbd..3db76462 100644 --- a/contracts/fees/TokenDistributor.sol +++ b/contracts/fees/TokenDistributor.sol @@ -11,6 +11,7 @@ import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; import "../interfaces/IKyberNetworkProxyInterface.sol"; import "../interfaces/IExchangeAdapter.sol"; import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; /// @title TokenDistributor @@ -26,6 +27,7 @@ import "../libraries/EthAddressLib.sol"; contract TokenDistributor is ReentrancyGuard, VersionedInitializable { using SafeMath for uint256; using SafeERC20 for IERC20; + using UniversalERC20 for IERC20; struct Distribution { address[] receivers; @@ -104,9 +106,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 +130,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 +166,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; diff --git a/contracts/flashloan/base/FlashLoanReceiverBase.sol b/contracts/flashloan/base/FlashLoanReceiverBase.sol index b94b8976..19bc847a 100644 --- a/contracts/flashloan/base/FlashLoanReceiverBase.sol +++ b/contracts/flashloan/base/FlashLoanReceiverBase.sol @@ -7,10 +7,12 @@ 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; @@ -25,28 +27,14 @@ abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { address payable core = addressesProvider.getLendingPoolCore(); - transferInternal(core,_reserve, _amount); + transferInternal(core, _reserve, _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); - - + IERC20(_reserve).universalTransfer(_destination, _amount); } function getBalanceInternal(address _target, address _reserve) internal view returns(uint256) { - if(_reserve == EthAddressLib.ethAddress()) { - - return _target.balance; - } - - return IERC20(_reserve).balanceOf(_target); - + return IERC20(_reserve).universalBalanceOf(_target); } } \ No newline at end of file diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index dc5ce58a..1c0b7bb6 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -18,6 +18,7 @@ import "./LendingPoolCore.sol"; import "./LendingPoolDataProvider.sol"; import "./LendingPoolLiquidationManager.sol"; import "../libraries/EthAddressLib.sol"; +import "../libraries/UniversalERC20.sol"; /** * @title LendingPool contract @@ -29,6 +30,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; @@ -849,9 +851,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, @@ -882,9 +882,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable { 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), diff --git a/contracts/lendingpool/LendingPoolCore.sol b/contracts/lendingpool/LendingPoolCore.sol index 7a65e788..8831fa7e 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"; @@ -14,6 +13,7 @@ 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 +29,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 +50,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 @@ -362,7 +360,6 @@ contract LendingPoolCore is VersionedInitializable { _collateralToLiquidate.add(_liquidatedCollateralForFee) ); } - } /** @@ -422,13 +419,7 @@ contract LendingPoolCore is VersionedInitializable { 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"); - } + IERC20(_reserve).universalTransfer(_user, _amount); } /** @@ -443,22 +434,27 @@ contract LendingPoolCore is VersionedInitializable { address _token, address _user, uint256 _amount, - address _destination + address _feeAddress ) external payable onlyLendingPool { - address payable feeAddress = address(uint160(_destination)); //cast the address to payable + IERC20 token = IERC20(_token); - if (_token != EthAddressLib.ethAddress()) { + if (!token.isETH()) { 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"); } + IERC20(_token).universalTransferFrom( + _user, + _feeAddress, + _amount, + false + ); } /** @@ -470,21 +466,14 @@ contract LendingPoolCore is VersionedInitializable { function liquidateFee( address _token, uint256 _amount, - address _destination + address _feeAddress ) 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"); - } + IERC20(_token).universalTransfer(_feeAddress, _amount); } /** @@ -498,21 +487,20 @@ contract LendingPoolCore is VersionedInitializable { 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); + IERC20 reserve = IERC20(_reserve); + if (!reserve.isETH()) { + require( + msg.value == 0, + "User is sending ETH along with the ERC20 transfer." + ); } 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"); - } + require( + msg.value >= _amount, + "The amount and the value sent to deposit do not match" + ); } + reserve.universalTransferFrom(_user, address(this), _amount, true); } /** @@ -616,14 +604,7 @@ contract LendingPoolCore is VersionedInitializable { * @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; + return IERC20(_reserve).universalBalanceOf(address(this)); } /** @@ -1443,7 +1424,6 @@ contract LendingPoolCore is VersionedInitializable { //solium-disable-next-line user.lastUpdateTimestamp = uint40(block.timestamp); - } /** diff --git a/contracts/libraries/UniversalERC20.sol b/contracts/libraries/UniversalERC20.sol new file mode 100644 index 00000000..1cfc822b --- /dev/null +++ b/contracts/libraries/UniversalERC20.sol @@ -0,0 +1,137 @@ +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"; + +library UniversalERC20 { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + IERC20 private constant ZERO_ADDRESS = IERC20( + 0x0000000000000000000000000000000000000000 + ); + IERC20 private constant ETH_ADDRESS = IERC20( + 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE + ); + + function universalTransfer( + IERC20 token, + address to, + uint256 amount + ) internal returns (bool) { + if (amount == 0) { + return true; + } + + if (isETH(token)) { + (bool result, ) = payable(to).call{value: amount, gas: 50000}(""); + require(result, "Transfer of ETH failed"); + } else { + token.safeTransfer(to, amount); + } + return true; + } + + 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: 50000}( + "" + ); + require(result, "Transfer of ETH failed"); + } + if (returnExcess && msg.value > amount) { + (bool result, ) = msg.sender.call{ + value: msg.value.sub(amount), + gas: 50000 + }(""); + require(result, "Transfer of ETH failed"); + } + } else { + token.safeTransferFrom(from, to, amount); + } + } + + function universalTransferFromSenderToThis(IERC20 token, uint256 amount) + internal + { + if (amount == 0) { + return; + } + + if (isETH(token)) { + if (msg.value > amount) { + // Return remainder if exist + (bool result, ) = msg.sender.call{ + value: msg.value.sub(amount), + gas: 50000 + }(""); + require(result, "Transfer of ETH failed"); + } + } else { + token.safeTransferFrom(msg.sender, address(this), amount); + } + } + + 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); + } + } + + function universalBalanceOf(IERC20 token, address who) + internal + view + returns (uint256) + { + if (isETH(token)) { + return who.balance; + } else { + return token.balanceOf(who); + } + } + + 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; + } + + function isETH(IERC20 token) internal pure returns (bool) { + return (address(token) == address(ZERO_ADDRESS) || + address(token) == address(ETH_ADDRESS)); + } +}