* Strip erc20 package of legacy nonsense and add foundry basics * Make foundry build * Remove obsoleted test/UntransferrableDummyERC20Token.sol contract * Remove obsoleted ERC20 lib variant contracts * Remove obsoleted DummyMultipleReturnERC20Token and DummyNoReturnERC20Token contracts * Move test contract to dedicated folder and remove obsoleted TypeScript contract wrappers * Remove src/interfaces/IEtherToken.sol only used in v3 staking which is being obsoleted [skip ci] * Add foundry test for token * Migrate ZRX token tests to foundry * Fix paths to erc20 contracts * Remove obsoleted references * Pin erc20-contracts package on treasury * Ignore foundry imports in link checker * Run only forge tests for erc20 contracts * Remove DummyERC20Token and its dependencies * Merge IERC20TokenV06 and IERC20TokenV08 into range pragma to cover solidity 0.6.5 to 0.8.x * Merge IEtherTokenV06 and IEtherTokenV08 into range pragma to cover solidity 0.6.5 to 0.8.x * Migrate weth9 tests to foundry * Upload code coverage for erc20 package * Update changelog * Fix review comments Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com> --------- Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>
154 lines
6.7 KiB
Solidity
154 lines
6.7 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 "../IERC20Token.sol";
|
|
|
|
library LibERC20TokenV06 {
|
|
bytes private constant DECIMALS_CALL_DATA = hex"313ce567";
|
|
|
|
/// @dev Calls `IERC20Token(token).approve()`.
|
|
/// Reverts if the return data is invalid 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(IERC20Token token, address spender, uint256 allowance) internal {
|
|
bytes memory callData = abi.encodeWithSelector(token.approve.selector, spender, allowance);
|
|
_callWithOptionalBooleanResult(address(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 the return data is invalid 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(IERC20Token token, address spender, uint256 amount) internal {
|
|
if (token.allowance(address(this), spender) < amount) {
|
|
compatApprove(token, spender, uint256(-1));
|
|
}
|
|
}
|
|
|
|
/// @dev Calls `IERC20Token(token).transfer()`.
|
|
/// Reverts if the return data is invalid 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(IERC20Token token, address to, uint256 amount) internal {
|
|
bytes memory callData = abi.encodeWithSelector(token.transfer.selector, to, amount);
|
|
_callWithOptionalBooleanResult(address(token), callData);
|
|
}
|
|
|
|
/// @dev Calls `IERC20Token(token).transferFrom()`.
|
|
/// Reverts if the return data is invalid 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(IERC20Token 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(IERC20Token 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(
|
|
IERC20Token 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(IERC20Token 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 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);
|
|
// Revert if the call reverted.
|
|
if (!didSucceed) {
|
|
LibRichErrorsV06.rrevert(resultData);
|
|
}
|
|
// If we get back 0 returndata, this may be a non-standard ERC-20 that
|
|
// does not return a boolean. Check that it at least contains code.
|
|
if (resultData.length == 0) {
|
|
uint256 size;
|
|
assembly {
|
|
size := extcodesize(target)
|
|
}
|
|
require(size > 0, "invalid token address, contains no code");
|
|
return;
|
|
}
|
|
// If we get back at least 32 bytes, we know the target address
|
|
// contains code, and we assume it is a token that returned a boolean
|
|
// success value, which must be true.
|
|
if (resultData.length >= 32) {
|
|
uint256 result = LibBytesV06.readUint256(resultData, 0);
|
|
if (result == 1) {
|
|
return;
|
|
} else {
|
|
LibRichErrorsV06.rrevert(resultData);
|
|
}
|
|
}
|
|
// If 0 < returndatasize < 32, the target is a contract, but not a
|
|
// valid token.
|
|
LibRichErrorsV06.rrevert(resultData);
|
|
}
|
|
}
|