2020-06-20 23:40:03 +00:00
|
|
|
// SPDX-License-Identifier: agpl-3.0
|
|
|
|
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';
|
2020-07-08 15:26:50 +00:00
|
|
|
import '@nomiclabs/buidler/console.sol';
|
2020-06-20 23:40:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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, ) = address(uint160(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)) {
|
|
|
|
require(msg.value >= amount, 'The amount and the value sent to deposit do not match');
|
|
|
|
if (returnExcess) {
|
|
|
|
// Return remainder if exist
|
2020-07-13 08:54:08 +00:00
|
|
|
(bool result, ) = msg.sender.call{value: msg.value.sub(amount), gas: DEFAULT_TRANSFER_GAS}(
|
|
|
|
''
|
|
|
|
);
|
2020-06-20 23:40:03 +00:00
|
|
|
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) {
|
2020-07-08 15:26:50 +00:00
|
|
|
return (address(token) == address(ETH_ADDRESS));
|
2020-06-20 23:40:03 +00:00
|
|
|
}
|
|
|
|
}
|