Reputation: 412
I am implementing a token that takes fees on buy/sell but not on transfer. I rely on checking the 'from' and 'to' for being uniswap pairs (V2 or V3) to detect it as a buy/sell transaction. For V2, it's fairly simply as I can use factory function to get the pair address. But for V3 pools, there are more combinations and more fees may be added in the future. So I want to programmatically check if the 'from' or 'to' address has a 'token0' funtion using address.staticcall().
The problem is staticcall() just fails with revert if the 'token0' function does not exists, any suggestions would be greatly appreciated. Anyway to help detect that address is a pair/pool without reverting would work.
Here is the code I am using (from github) - this code is calling symbol() function
This call may revert if the function called is not existing in the contract Here I am calling symbol(), but potentially can call token0() or any other function
function _isUniswapV2Pair(address target) internal view returns (bool) {
address token0;
address token1;
string memory targetSymbol = _callAndParseStringReturn(
target,
hex"95d89b41" // symbol()
);
if (bytes(targetSymbol).length == 0) {
return false;
}
if (_compare(targetSymbol, "UNI-V2")) {
IUniswapV2Pair pairContract = IUniswapV2Pair(target);
try pairContract.token0() returns (address _token0) {
token0 = _token0;
} catch Error(string memory) {
return false;
} catch (bytes memory) {
return false;
}
try pairContract.token1() returns (address _token1) {
token1 = _token1;
} catch Error(string memory) {
return false;
} catch (bytes memory) {
return false;
}
} else {
return false;
}
return target == _dexFactoryV2.getPair(token0, token1); }
function _callAndParseStringReturn(address token, bytes4 selector)
internal
view
returns (string memory)
{
(bool success, bytes memory data) = token.staticcall(
abi.encodeWithSelector(selector)
);
// if not implemented, or returns empty data, return empty string
if (!success || data.length == 0) {
return "";
}
// bytes32 data always has length 32
if (data.length == 32) {
bytes32 decoded = abi.decode(data, (bytes32));
return _bytes32ToString(decoded);
} else if (data.length > 64) {
return abi.decode(data, (string));
}
return ""; }
function _bytes32ToString(bytes32 x) internal pure returns (string memory) {
bytes memory bytesString = new bytes(32);
uint256 charCount = 0;
for (uint256 j = 0; j < 32; j++) {
bytes1 char = x[j];
if (char != 0) {
bytesString[charCount] = char;
charCount++;
}
}
bytes memory bytesStringTrimmed = new bytes(charCount);
for (uint256 j = 0; j < charCount; j++) {
bytesStringTrimmed[j] = bytesString[j];
}
return string(bytesStringTrimmed); }
I have studied the solidity documentation and explored using assembly functions to detect contract name, but looks like that's not possible.
Also tried several approaches to avoid revert from staticcall if the called function is not existing. There are several contracts that get passed to transfer in case of uniswap swaps - it could be SwapRouter, or NFPosManager or custom tokens or any other contract.
FYI I do check if the address is a contract and not a wallet address before making this above call.
Upvotes: 3
Views: 1729
Reputation: 43491
To my knowledge, there's no 100% accurate onchcain check whether a specific contract implements a specific function.
You could validate if its bytecode contains the function selector but there are false positives - i.e. the same chunk of bytes is present in the bytecode even though it's not a function selector.
Plus some contracts implement the fallback() function that gets invoked each time you're trying to invoke a function that is not defined in the contract. So in some cases, the fallback()
might be acceptable by the external validation logic - and in some cases not.
Since your question seems to be specifically about checking Uniswap pairs, possibly this answer might be helpful. It's checking the calls to token0()
and token1()
with try
/ catch
, and then further validating against the router contract if the pair address is registered within Uniswap.
Upvotes: 3