210 lines
7.1 KiB
Solidity
210 lines
7.1 KiB
Solidity
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
|
|
Copyright 2020 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.6.5;
|
|
|
|
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
|
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
|
import "./IERC20TokenV06.sol";
|
|
|
|
|
|
library LibERC20TokenV06 {
|
|
bytes constant private DECIMALS_CALL_DATA = hex"313ce567";
|
|
|
|
/// @dev Calls `IERC20TokenV06(token).approve()`.
|
|
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
|
/// @param token The address of the token contract.
|
|
/// @param spender The address that receives an allowance.
|
|
/// @param allowance The allowance to set.
|
|
function compatApprove(
|
|
IERC20TokenV06 token,
|
|
address spender,
|
|
uint256 allowance
|
|
)
|
|
internal
|
|
{
|
|
bytes memory callData = abi.encodeWithSelector(
|
|
token.approve.selector,
|
|
spender,
|
|
allowance
|
|
);
|
|
_callWithOptionalBooleanResult(address(token), callData);
|
|
}
|
|
|
|
/// @dev Calls `IERC20TokenV06(token).approve()` and sets the allowance to the
|
|
/// maximum if the current approval is not already >= an amount.
|
|
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
|
/// @param token The address of the token contract.
|
|
/// @param spender The address that receives an allowance.
|
|
/// @param amount The minimum allowance needed.
|
|
function approveIfBelow(
|
|
IERC20TokenV06 token,
|
|
address spender,
|
|
uint256 amount
|
|
)
|
|
internal
|
|
{
|
|
if (token.allowance(address(this), spender) < amount) {
|
|
compatApprove(token, spender, uint256(-1));
|
|
}
|
|
}
|
|
|
|
/// @dev Calls `IERC20TokenV06(token).transfer()`.
|
|
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
|
/// @param token The address of the token contract.
|
|
/// @param to The address that receives the tokens
|
|
/// @param amount Number of tokens to transfer.
|
|
function compatTransfer(
|
|
IERC20TokenV06 token,
|
|
address to,
|
|
uint256 amount
|
|
)
|
|
internal
|
|
{
|
|
bytes memory callData = abi.encodeWithSelector(
|
|
token.transfer.selector,
|
|
to,
|
|
amount
|
|
);
|
|
_callWithOptionalBooleanResult(address(token), callData);
|
|
}
|
|
|
|
/// @dev Calls `IERC20TokenV06(token).transferFrom()`.
|
|
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
|
/// @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 compatTransferFrom(
|
|
IERC20TokenV06 token,
|
|
address from,
|
|
address to,
|
|
uint256 amount
|
|
)
|
|
internal
|
|
{
|
|
bytes memory callData = abi.encodeWithSelector(
|
|
token.transferFrom.selector,
|
|
from,
|
|
to,
|
|
amount
|
|
);
|
|
_callWithOptionalBooleanResult(address(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 compatDecimals(IERC20TokenV06 token)
|
|
internal
|
|
view
|
|
returns (uint8 tokenDecimals)
|
|
{
|
|
tokenDecimals = 18;
|
|
(bool didSucceed, bytes memory resultData) = address(token).staticcall(DECIMALS_CALL_DATA);
|
|
if (didSucceed && resultData.length >= 32) {
|
|
tokenDecimals = uint8(LibBytesV06.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 compatAllowance(IERC20TokenV06 token, address owner, address spender)
|
|
internal
|
|
view
|
|
returns (uint256 allowance_)
|
|
{
|
|
(bool didSucceed, bytes memory resultData) = address(token).staticcall(
|
|
abi.encodeWithSelector(
|
|
token.allowance.selector,
|
|
owner,
|
|
spender
|
|
)
|
|
);
|
|
if (didSucceed && resultData.length >= 32) {
|
|
allowance_ = LibBytesV06.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 compatBalanceOf(IERC20TokenV06 token, address owner)
|
|
internal
|
|
view
|
|
returns (uint256 balance)
|
|
{
|
|
(bool didSucceed, bytes memory resultData) = address(token).staticcall(
|
|
abi.encodeWithSelector(
|
|
token.balanceOf.selector,
|
|
owner
|
|
)
|
|
);
|
|
if (didSucceed && resultData.length >= 32) {
|
|
balance = LibBytesV06.readUint256(resultData, 0);
|
|
}
|
|
}
|
|
|
|
/// @dev Check if the data returned by a non-static call to an ERC20 token
|
|
/// is a successful result. Supported functions are `transfer()`,
|
|
/// `transferFrom()`, and `approve()`.
|
|
/// @param resultData The raw data returned by a non-static call to the ERC20 token.
|
|
/// @return isSuccessful Whether the result data indicates success.
|
|
function isSuccessfulResult(bytes memory resultData)
|
|
internal
|
|
pure
|
|
returns (bool isSuccessful)
|
|
{
|
|
if (resultData.length == 0) {
|
|
return true;
|
|
}
|
|
if (resultData.length >= 32) {
|
|
uint256 result = LibBytesV06.readUint256(resultData, 0);
|
|
if (result == 1) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @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 && isSuccessfulResult(resultData)) {
|
|
return;
|
|
}
|
|
LibRichErrorsV06.rrevert(resultData);
|
|
}
|
|
}
|