idle_moloch/lib/swap-router-contracts/contracts/lens/TokenValidator.sol

160 lines
6.3 KiB
Solidity

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol';
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol';
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import '../libraries/UniswapV2Library.sol';
import '../interfaces/ISwapRouter02.sol';
import '../interfaces/ITokenValidator.sol';
import '../base/ImmutableState.sol';
/// @notice Validates tokens by flash borrowing from the token/<base token> pool on V2.
/// @notice Returns
/// Status.FOT if we detected a fee is taken on transfer.
/// Status.STF if transfer failed for the token.
/// Status.UNKN if we did not detect any issues with the token.
/// @notice A return value of Status.UNKN does not mean the token is definitely not a fee on transfer token
/// or definitely has no problems with its transfer. It just means we cant say for sure that it has any
/// issues.
/// @dev We can not guarantee the result of this lens is correct for a few reasons:
/// @dev 1/ Some tokens take fees or allow transfers under specific conditions, for example some have an allowlist
/// @dev of addresses that do/dont require fees. Therefore the result is not guaranteed to be correct
/// @dev in all circumstances.
/// @dev 2/ It is possible that the token does not have any pools on V2 therefore we are not able to perform
/// @dev a flashloan to test the token.
contract TokenValidator is ITokenValidator, IUniswapV2Callee, ImmutableState {
string internal constant FOT_REVERT_STRING = 'FOT';
// https://github.com/Uniswap/v2-core/blob/1136544ac842ff48ae0b1b939701436598d74075/contracts/UniswapV2Pair.sol#L46
string internal constant STF_REVERT_STRING_SUFFIX = 'TRANSFER_FAILED';
constructor(address _factoryV2, address _positionManager) ImmutableState(_factoryV2, _positionManager) {}
function batchValidate(
address[] calldata tokens,
address[] calldata baseTokens,
uint256 amountToBorrow
) public override returns (Status[] memory isFotResults) {
isFotResults = new Status[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
isFotResults[i] = validate(tokens[i], baseTokens, amountToBorrow);
}
}
function validate(
address token,
address[] calldata baseTokens,
uint256 amountToBorrow
) public override returns (Status) {
for (uint256 i = 0; i < baseTokens.length; i++) {
Status result = _validate(token, baseTokens[i], amountToBorrow);
if (result == Status.FOT || result == Status.STF) {
return result;
}
}
return Status.UNKN;
}
function _validate(
address token,
address baseToken,
uint256 amountToBorrow
) internal returns (Status) {
if (token == baseToken) {
return Status.UNKN;
}
address pairAddress = UniswapV2Library.pairFor(this.factoryV2(), token, baseToken);
// If the token/baseToken pair exists, get token0.
// Must do low level call as try/catch does not support case where contract does not exist.
(, bytes memory returnData) = address(pairAddress).call(abi.encodeWithSelector(IUniswapV2Pair.token0.selector));
if (returnData.length == 0) {
return Status.UNKN;
}
address token0Address = abi.decode(returnData, (address));
// Flash loan {amountToBorrow}
(uint256 amount0Out, uint256 amount1Out) =
token == token0Address ? (amountToBorrow, uint256(0)) : (uint256(0), amountToBorrow);
uint256 balanceBeforeLoan = IERC20(token).balanceOf(address(this));
IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
try
pair.swap(amount0Out, amount1Out, address(this), abi.encode(balanceBeforeLoan, amountToBorrow))
{} catch Error(string memory reason) {
if (isFotFailed(reason)) {
return Status.FOT;
}
if (isTransferFailed(reason)) {
return Status.STF;
}
return Status.UNKN;
}
// Swap always reverts so should never reach.
revert('Unexpected error');
}
function isFotFailed(string memory reason) internal pure returns (bool) {
return keccak256(bytes(reason)) == keccak256(bytes(FOT_REVERT_STRING));
}
function isTransferFailed(string memory reason) internal pure returns (bool) {
// We check the suffix of the revert string so we can support forks that
// may have modified the prefix.
string memory stf = STF_REVERT_STRING_SUFFIX;
uint256 reasonLength = bytes(reason).length;
uint256 suffixLength = bytes(stf).length;
if (reasonLength < suffixLength) {
return false;
}
uint256 ptr;
uint256 offset = 32 + reasonLength - suffixLength;
bool transferFailed;
assembly {
ptr := add(reason, offset)
let suffixPtr := add(stf, 32)
transferFailed := eq(keccak256(ptr, suffixLength), keccak256(suffixPtr, suffixLength))
}
return transferFailed;
}
function uniswapV2Call(
address,
uint256 amount0,
uint256,
bytes calldata data
) external view override {
IUniswapV2Pair pair = IUniswapV2Pair(msg.sender);
(address token0, address token1) = (pair.token0(), pair.token1());
IERC20 tokenBorrowed = IERC20(amount0 > 0 ? token0 : token1);
(uint256 balanceBeforeLoan, uint256 amountRequestedToBorrow) = abi.decode(data, (uint256, uint256));
uint256 amountBorrowed = tokenBorrowed.balanceOf(address(this)) - balanceBeforeLoan;
// If we received less token than we requested when we called swap, then a fee must have been taken
// by the token during transfer.
if (amountBorrowed != amountRequestedToBorrow) {
revert(FOT_REVERT_STRING);
}
// Note: If we do not revert here, we would end up reverting in the pair's swap method anyway
// since for a flash borrow we need to transfer back the amount we borrowed + 0.3% fee, and we don't
// have funds to cover the fee. Revert early here to save gas/time.
revert('Unknown');
}
}