// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** * @title PoolDetector * @notice Efficient onchain pool detection for multiple DEX types * @dev Gas-optimized detection using assembly and batched calls */ contract PoolDetector { enum PoolType { Unknown, UniswapV2, UniswapV3, AlgebraV19, AlgebraIntegral, Balancer, Curve, Camelot, SushiswapV2, PancakeV3, KyberSwap } struct PoolInfo { address pool; PoolType poolType; address token0; address token1; uint256 fee; bool isValid; uint8 confidence; } /** * @notice Detect pool type for a single address * @param pool The pool address to identify * @return info The pool information */ function detectPool(address pool) external view returns (PoolInfo memory info) { info.pool = pool; // Check if contract exists uint256 codeSize; assembly { codeSize := extcodesize(pool) } if (codeSize == 0) { info.isValid = false; return info; } // Try to get token0 and token1 (common to all pools) (bool hasToken0, address token0) = _tryToken0(pool); (bool hasToken1, address token1) = _tryToken1(pool); if (!hasToken0 || !hasToken1) { info.isValid = false; return info; } info.token0 = token0; info.token1 = token1; info.isValid = true; // Try UniswapV3 specific methods if (_isUniswapV3(pool)) { info.poolType = PoolType.UniswapV3; info.confidence = 95; (bool hasFee, uint256 fee) = _tryFeeV3(pool); if (hasFee) info.fee = fee; return info; } // Try Algebra/Camelot (has globalState instead of slot0) if (_isAlgebra(pool)) { // Check for directional fees (Integral) if (_hasDirectionalFees(pool)) { info.poolType = PoolType.AlgebraIntegral; info.confidence = 90; } else { info.poolType = PoolType.AlgebraV19; info.confidence = 85; } return info; } // Try UniswapV2/Sushiswap if (_isUniswapV2(pool)) { // Distinguish by factory address factory = _getFactory(pool); if (factory == 0xc35DADB65012eC5796536bD9864eD8773aBc74C4) { info.poolType = PoolType.SushiswapV2; } else { info.poolType = PoolType.UniswapV2; } info.confidence = 80; return info; } // Unknown but valid pool info.poolType = PoolType.Unknown; info.confidence = 30; } /** * @notice Batch detect multiple pools * @param pools Array of pool addresses * @return infos Array of pool information */ function detectPools(address[] calldata pools) external view returns (PoolInfo[] memory infos) { infos = new PoolInfo[](pools.length); for (uint256 i = 0; i < pools.length; i++) { infos[i] = this.detectPool(pools[i]); } } // Internal detection functions function _isUniswapV3(address pool) private view returns (bool) { // Check for slot0, fee, tickSpacing, maxLiquidityPerTick (bool hasSlot0,) = _trySlot0(pool); (bool hasFee,) = _tryFeeV3(pool); (bool hasTickSpacing,) = _tryTickSpacing(pool); (bool hasMaxLiquidity,) = _tryMaxLiquidityPerTick(pool); return hasSlot0 && hasFee && hasTickSpacing && hasMaxLiquidity; } function _isAlgebra(address pool) private view returns (bool) { // Has globalState instead of slot0 (bool hasGlobalState,) = _tryGlobalState(pool); (bool hasSlot0,) = _trySlot0(pool); return hasGlobalState && !hasSlot0; } function _isUniswapV2(address pool) private view returns (bool) { // Has getReserves, no slot0 or globalState (bool hasReserves,) = _tryGetReserves(pool); (bool hasSlot0,) = _trySlot0(pool); (bool hasGlobalState,) = _tryGlobalState(pool); return hasReserves && !hasSlot0 && !hasGlobalState; } function _hasDirectionalFees(address pool) private view returns (bool) { // Check for feeZto0 and fee0to1 (Algebra Integral) (bool success,) = pool.staticcall( abi.encodeWithSignature("feeZto0()") ); return success; } // Low-level call helpers function _tryToken0(address pool) private view returns (bool success, address token) { bytes memory data; (success, data) = pool.staticcall( abi.encodeWithSignature("token0()") ); if (success && data.length >= 32) { token = abi.decode(data, (address)); } } function _tryToken1(address pool) private view returns (bool success, address token) { bytes memory data; (success, data) = pool.staticcall( abi.encodeWithSignature("token1()") ); if (success && data.length >= 32) { token = abi.decode(data, (address)); } } function _trySlot0(address pool) private view returns (bool success, bytes memory data) { (success, data) = pool.staticcall( abi.encodeWithSignature("slot0()") ); } function _tryGlobalState(address pool) private view returns (bool success, bytes memory data) { (success, data) = pool.staticcall( abi.encodeWithSignature("globalState()") ); } function _tryGetReserves(address pool) private view returns (bool success, bytes memory data) { (success, data) = pool.staticcall( abi.encodeWithSignature("getReserves()") ); } function _tryFeeV3(address pool) private view returns (bool success, uint256 fee) { bytes memory data; (success, data) = pool.staticcall( abi.encodeWithSignature("fee()") ); if (success && data.length >= 32) { fee = abi.decode(data, (uint256)); } } function _tryTickSpacing(address pool) private view returns (bool success, bytes memory data) { (success, data) = pool.staticcall( abi.encodeWithSignature("tickSpacing()") ); } function _tryMaxLiquidityPerTick(address pool) private view returns (bool success, bytes memory data) { (success, data) = pool.staticcall( abi.encodeWithSignature("maxLiquidityPerTick()") ); } function _getFactory(address pool) private view returns (address factory) { (bool success, bytes memory data) = pool.staticcall( abi.encodeWithSignature("factory()") ); if (success && data.length >= 32) { factory = abi.decode(data, (address)); } } /** * @notice Efficient batch detection using multicall pattern * @param pools Array of pools to check * @return results Packed results for gas efficiency */ function batchDetectEfficient(address[] calldata pools) external view returns (uint256[] memory results) { results = new uint256[](pools.length); for (uint256 i = 0; i < pools.length; i++) { // Pack result into single uint256: // bits 0-7: pool type // bits 8-15: confidence // bits 16-23: has token0 // bits 24-31: has token1 // bits 32+: fee (if applicable) PoolInfo memory info = this.detectPool(pools[i]); results[i] = uint256(info.poolType) | (uint256(info.confidence) << 8) | (info.token0 != address(0) ? 1 << 16 : 0) | (info.token1 != address(0) ? 1 << 24 : 0) | (info.fee << 32); } } }