200 lines
6.5 KiB
Solidity
200 lines
6.5 KiB
Solidity
/*
|
|
|
|
Copyright 2019 ZeroEx Intl.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
pragma solidity ^0.5.9;
|
|
|
|
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
|
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
|
import "../src/interfaces/IERC20Token.sol";
|
|
|
|
|
|
library LibERC20Token {
|
|
bytes constant private DECIMALS_CALL_DATA = hex"313ce567";
|
|
|
|
/// @dev Calls `IERC20Token(token).approve()`.
|
|
/// Reverts if `false` is returned or if the return
|
|
/// data length is nonzero and not 32 bytes.
|
|
/// @param token The address of the token contract.
|
|
/// @param spender The address that receives an allowance.
|
|
/// @param allowance The allowance to set.
|
|
function approve(
|
|
address token,
|
|
address spender,
|
|
uint256 allowance
|
|
)
|
|
internal
|
|
{
|
|
bytes memory callData = abi.encodeWithSelector(
|
|
IERC20Token(0).approve.selector,
|
|
spender,
|
|
allowance
|
|
);
|
|
_callWithOptionalBooleanResult(token, callData);
|
|
}
|
|
|
|
/// @dev Calls `IERC20Token(token).approve()` and sets the allowance to the
|
|
/// maximum if the current approval is not already >= an amount.
|
|
/// Reverts if `false` is returned or if the return
|
|
/// data length is nonzero and not 32 bytes.
|
|
/// @param token The address of the token contract.
|
|
/// @param spender The address that receives an allowance.
|
|
/// @param amount The minimum allowance needed.
|
|
function approveIfBelow(
|
|
address token,
|
|
address spender,
|
|
uint256 amount
|
|
)
|
|
internal
|
|
{
|
|
if (IERC20Token(token).allowance(address(this), spender) < amount) {
|
|
approve(token, spender, uint256(-1));
|
|
}
|
|
}
|
|
|
|
/// @dev Calls `IERC20Token(token).transfer()`.
|
|
/// Reverts if `false` is returned or if the return
|
|
/// data length is nonzero and not 32 bytes.
|
|
/// @param token The address of the token contract.
|
|
/// @param to The address that receives the tokens
|
|
/// @param amount Number of tokens to transfer.
|
|
function transfer(
|
|
address token,
|
|
address to,
|
|
uint256 amount
|
|
)
|
|
internal
|
|
{
|
|
bytes memory callData = abi.encodeWithSelector(
|
|
IERC20Token(0).transfer.selector,
|
|
to,
|
|
amount
|
|
);
|
|
_callWithOptionalBooleanResult(token, callData);
|
|
}
|
|
|
|
/// @dev Calls `IERC20Token(token).transferFrom()`.
|
|
/// Reverts if `false` is returned or if the return
|
|
/// data length is nonzero and not 32 bytes.
|
|
/// @param token The address of the token contract.
|
|
/// @param from The owner of the tokens.
|
|
/// @param to The address that receives the tokens
|
|
/// @param amount Number of tokens to transfer.
|
|
function transferFrom(
|
|
address token,
|
|
address from,
|
|
address to,
|
|
uint256 amount
|
|
)
|
|
internal
|
|
{
|
|
bytes memory callData = abi.encodeWithSelector(
|
|
IERC20Token(0).transferFrom.selector,
|
|
from,
|
|
to,
|
|
amount
|
|
);
|
|
_callWithOptionalBooleanResult(token, callData);
|
|
}
|
|
|
|
/// @dev Retrieves the number of decimals for a token.
|
|
/// Returns `18` if the call reverts.
|
|
/// @param token The address of the token contract.
|
|
/// @return tokenDecimals The number of decimals places for the token.
|
|
function decimals(address token)
|
|
internal
|
|
view
|
|
returns (uint8 tokenDecimals)
|
|
{
|
|
tokenDecimals = 18;
|
|
(bool didSucceed, bytes memory resultData) = token.staticcall(DECIMALS_CALL_DATA);
|
|
if (didSucceed && resultData.length == 32) {
|
|
tokenDecimals = uint8(LibBytes.readUint256(resultData, 0));
|
|
}
|
|
}
|
|
|
|
/// @dev Retrieves the allowance for a token, owner, and spender.
|
|
/// Returns `0` if the call reverts.
|
|
/// @param token The address of the token contract.
|
|
/// @param owner The owner of the tokens.
|
|
/// @param spender The address the spender.
|
|
/// @return allowance The allowance for a token, owner, and spender.
|
|
function allowance(address token, address owner, address spender)
|
|
internal
|
|
view
|
|
returns (uint256 allowance_)
|
|
{
|
|
(bool didSucceed, bytes memory resultData) = token.staticcall(
|
|
abi.encodeWithSelector(
|
|
IERC20Token(0).allowance.selector,
|
|
owner,
|
|
spender
|
|
)
|
|
);
|
|
if (didSucceed && resultData.length == 32) {
|
|
allowance_ = LibBytes.readUint256(resultData, 0);
|
|
}
|
|
}
|
|
|
|
/// @dev Retrieves the balance for a token owner.
|
|
/// Returns `0` if the call reverts.
|
|
/// @param token The address of the token contract.
|
|
/// @param owner The owner of the tokens.
|
|
/// @return balance The token balance of an owner.
|
|
function balanceOf(address token, address owner)
|
|
internal
|
|
view
|
|
returns (uint256 balance)
|
|
{
|
|
(bool didSucceed, bytes memory resultData) = token.staticcall(
|
|
abi.encodeWithSelector(
|
|
IERC20Token(0).balanceOf.selector,
|
|
owner
|
|
)
|
|
);
|
|
if (didSucceed && resultData.length == 32) {
|
|
balance = LibBytes.readUint256(resultData, 0);
|
|
}
|
|
}
|
|
|
|
/// @dev Executes a call on address `target` with calldata `callData`
|
|
/// and asserts that either nothing was returned or a single boolean
|
|
/// was returned equal to `true`.
|
|
/// @param target The call target.
|
|
/// @param callData The abi-encoded call data.
|
|
function _callWithOptionalBooleanResult(
|
|
address target,
|
|
bytes memory callData
|
|
)
|
|
private
|
|
{
|
|
(bool didSucceed, bytes memory resultData) = target.call(callData);
|
|
if (didSucceed) {
|
|
if (resultData.length == 0) {
|
|
return;
|
|
}
|
|
if (resultData.length == 32) {
|
|
uint256 result = LibBytes.readUint256(resultData, 0);
|
|
if (result == 1) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
LibRichErrors.rrevert(resultData);
|
|
}
|
|
}
|