// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/Address.sol"; interface IUniswapV3Pool { function flash( address recipient, uint256 amount0, uint256 amount1, bytes calldata data ) external; function token0() external view returns (address); function token1() external view returns (address); function fee() external view returns (uint24); } interface IUniswapV3Router { struct ExactInputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; } function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); } interface ICamelotRouter { function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, address referrer, uint deadline ) external returns (uint[] memory amounts); function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); } /** * @title ProductionArbitrageExecutor * @dev PRODUCTION-GRADE arbitrage executor for profitable MEV extraction * @notice This contract executes flash swap arbitrage between DEXes on Arbitrum */ contract ProductionArbitrageExecutor is ReentrancyGuard, AccessControl, Pausable { using SafeERC20 for IERC20; using Address for address; // Role definitions for access control bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant EMERGENCY_ROLE = keccak256("EMERGENCY_ROLE"); // Factory address for pool validation address public constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; // Maximum slippage tolerance (5% = 500 basis points) uint256 public constant MAX_SLIPPAGE_BPS = 500; // Mapping to track authorized pools for flash loans mapping(address => bool) public authorizedPools; // Circuit breaker for emergency stops bool public emergencyStop = false; // Router addresses on Arbitrum IUniswapV3Router public constant UNISWAP_V3_ROUTER = IUniswapV3Router(0xE592427A0AEce92De3Edee1F18E0157C05861564); ICamelotRouter public constant CAMELOT_ROUTER = ICamelotRouter(0xc873fEcbd354f5A56E00E710B90EF4201db2448d); // Common token addresses on Arbitrum address public constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; address public constant USDC = 0xaF88d065e77c8cC2239327C5EDb3A432268e5831; address public constant USDT = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; address public constant ARB = 0x912CE59144191C1204E64559FE8253a0e49E6548; // Minimum profit threshold (in wei) uint256 public minProfitThreshold = 0.005 ether; // 0.005 ETH minimum profit // Maximum gas price for profitable execution uint256 public maxGasPrice = 5 gwei; // 5 gwei max // Events for tracking profitable arbitrage event ArbitrageExecuted( address indexed tokenA, address indexed tokenB, uint256 amountIn, uint256 profit, uint256 gasUsed, address indexed executor ); event ProfitWithdrawn(address indexed token, uint256 amount, address indexed recipient); event PoolAuthorized(address indexed pool, bool authorized); event EmergencyStopToggled(bool stopped); event SlippageExceeded(address indexed pool, uint256 expectedAmount, uint256 actualAmount); struct ArbitrageParams { address tokenA; address tokenB; uint256 amountIn; uint24 uniswapFee; address[] camelotPath; uint256 minProfit; uint256 maxSlippageBps; // Maximum allowed slippage in basis points bool buyOnUniswap; // true = buy on Uniswap, sell on Camelot uint256 deadline; // Transaction deadline for additional safety } /** * @dev Constructor sets up roles and initial configuration */ constructor(address admin, address executor) { require(admin != address(0) && executor != address(0), "Invalid addresses"); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(ADMIN_ROLE, admin); _grantRole(EXECUTOR_ROLE, executor); _grantRole(EMERGENCY_ROLE, admin); // Set role admin relationships _setRoleAdmin(EXECUTOR_ROLE, ADMIN_ROLE); _setRoleAdmin(EMERGENCY_ROLE, ADMIN_ROLE); } /** * @dev Authorize a pool for flash loans */ function authorizePool(address pool) external onlyRole(ADMIN_ROLE) { require(pool != address(0), "Invalid pool address"); require(pool.isContract(), "Pool must be a contract"); authorizedPools[pool] = true; emit PoolAuthorized(pool, true); } /** * @dev Deauthorize a pool for flash loans */ function deauthorizePool(address pool) external onlyRole(ADMIN_ROLE) { authorizedPools[pool] = false; emit PoolAuthorized(pool, false); } /** * @dev Emergency stop toggle */ function toggleEmergencyStop() external onlyRole(EMERGENCY_ROLE) { emergencyStop = !emergencyStop; emit EmergencyStopToggled(emergencyStop); } /** * @dev Execute profitable arbitrage using flash swap with comprehensive security checks * @param pool Uniswap V3 pool to flash swap from * @param params Arbitrage parameters encoded as bytes */ function executeArbitrage(address pool, bytes calldata params) external onlyRole(EXECUTOR_ROLE) nonReentrant whenNotPaused { require(!emergencyStop, "Emergency stop activated"); require(authorizedPools[pool], "Pool not authorized"); require(tx.gasprice <= maxGasPrice, "Gas price too high for profit"); require(pool.isContract(), "Invalid pool address"); ArbitrageParams memory arbParams = abi.decode(params, (ArbitrageParams)); // Comprehensive parameter validation _validateArbitrageParams(arbParams); // Validate minimum profit potential uint256 estimatedProfit = estimateProfit(arbParams); require(estimatedProfit >= minProfitThreshold, "Insufficient profit potential"); require(estimatedProfit >= arbParams.minProfit, "Below user-specified minimum profit"); // Ensure deadline hasn't passed require(block.timestamp <= arbParams.deadline, "Transaction deadline exceeded"); // Calculate optimal flash amount with safety checks uint256 flashAmount = calculateOptimalAmount(arbParams); require(flashAmount > 0 && flashAmount <= arbParams.amountIn * 2, "Invalid flash amount"); // Prepare flash swap data with additional validation data bytes memory flashData = abi.encode(arbParams, block.timestamp, msg.sender); // Execute flash swap with proper token validation require(_isValidPoolTokens(pool, arbParams.tokenA, arbParams.tokenB), "Invalid pool tokens"); if (arbParams.tokenA == IUniswapV3Pool(pool).token0()) { IUniswapV3Pool(pool).flash(address(this), flashAmount, 0, flashData); } else { IUniswapV3Pool(pool).flash(address(this), 0, flashAmount, flashData); } } /** * @dev Validate arbitrage parameters comprehensively */ function _validateArbitrageParams(ArbitrageParams memory params) private pure { require(params.tokenA != address(0) && params.tokenB != address(0), "Invalid token addresses"); require(params.tokenA != params.tokenB, "Tokens must be different"); require(params.amountIn > 0, "Amount must be positive"); require(params.minProfit > 0, "Minimum profit must be positive"); require(params.maxSlippageBps <= MAX_SLIPPAGE_BPS, "Slippage tolerance too high"); require(params.camelotPath.length >= 2, "Invalid Camelot path"); require(params.deadline > 0, "Invalid deadline"); require(params.uniswapFee == 500 || params.uniswapFee == 3000 || params.uniswapFee == 10000, "Invalid Uniswap fee"); } /** * @dev Validate pool tokens match arbitrage parameters */ function _isValidPoolTokens(address pool, address tokenA, address tokenB) private view returns (bool) { try IUniswapV3Pool(pool).token0() returns (address token0) { try IUniswapV3Pool(pool).token1() returns (address token1) { return (token0 == tokenA && token1 == tokenB) || (token0 == tokenB && token1 == tokenA); } catch { return false; } } catch { return false; } } /** * @dev Uniswap V3 flash callback with enhanced security validation */ function uniswapV3FlashCallback( uint256 fee0, uint256 fee1, bytes calldata data ) external nonReentrant { uint256 gasStart = gasleft(); // Critical: Validate callback is from authorized pool FIRST require(authorizedPools[msg.sender], "Unauthorized pool callback"); require(!emergencyStop, "Emergency stop activated"); (ArbitrageParams memory params, uint256 timestamp, address originalCaller) = abi.decode(data, (ArbitrageParams, uint256, address)); // Enhanced callback validation require(_isValidPoolTokens(msg.sender, params.tokenA, params.tokenB), "Invalid pool tokens"); require(block.timestamp <= timestamp + 300, "Callback too old"); require(block.timestamp <= params.deadline, "Transaction deadline exceeded"); // Calculate amount owed with overflow protection uint256 flashFee = params.tokenA == IUniswapV3Pool(msg.sender).token0() ? fee0 : fee1; uint256 amountOwed; require(params.amountIn + flashFee >= params.amountIn, "Overflow in amount calculation"); amountOwed = params.amountIn + flashFee; // Ensure we have sufficient balance for flash loan execution uint256 initialBalance = IERC20(params.tokenA).balanceOf(address(this)); require(initialBalance >= params.amountIn, "Insufficient flash loan balance"); // Execute arbitrage strategy with slippage protection uint256 profit = executeArbitrageStrategy(params, amountOwed); // Enhanced profit validation require(profit >= minProfitThreshold, "Below minimum profit threshold"); require(profit >= params.minProfit, "Below user-specified minimum"); // Ensure we can repay the flash loan uint256 finalBalance = IERC20(params.tokenA).balanceOf(address(this)); require(finalBalance >= amountOwed, "Insufficient balance to repay flash loan"); // Repay flash loan with precise amount IERC20(params.tokenA).safeTransfer(msg.sender, amountOwed); // Calculate actual gas cost uint256 gasUsed = gasStart - gasleft(); emit ArbitrageExecuted( params.tokenA, params.tokenB, params.amountIn, profit, gasUsed, originalCaller ); } /** * @dev Execute the actual arbitrage strategy */ function executeArbitrageStrategy(ArbitrageParams memory params, uint256 amountOwed) private returns (uint256 profit) { uint256 startBalance = IERC20(params.tokenA).balanceOf(address(this)); if (params.buyOnUniswap) { // Buy tokenB on Uniswap, sell on Camelot uint256 amountOut = buyOnUniswap(params); uint256 finalAmount = sellOnCamelot(params.tokenB, params.tokenA, amountOut, params.camelotPath); uint256 endBalance = IERC20(params.tokenA).balanceOf(address(this)); profit = endBalance > startBalance + amountOwed ? endBalance - startBalance - amountOwed : 0; } else { // Buy tokenB on Camelot, sell on Uniswap uint256 amountOut = buyOnCamelot(params); uint256 finalAmount = sellOnUniswap(params.tokenB, params.tokenA, amountOut, params.uniswapFee); uint256 endBalance = IERC20(params.tokenA).balanceOf(address(this)); profit = endBalance > startBalance + amountOwed ? endBalance - startBalance - amountOwed : 0; } } /** * @dev Buy tokens on Uniswap V3 with precise approvals and slippage protection */ function buyOnUniswap(ArbitrageParams memory params) private returns (uint256 amountOut) { // Reset any existing approval to 0 first (for certain tokens like USDT) IERC20(params.tokenA).safeApprove(address(UNISWAP_V3_ROUTER), 0); // Approve exact amount needed, no more IERC20(params.tokenA).safeApprove(address(UNISWAP_V3_ROUTER), params.amountIn); // Calculate minimum amount out with slippage protection uint256 minAmountOut = _calculateMinAmountOut(params.amountIn, params.maxSlippageBps); IUniswapV3Router.ExactInputSingleParams memory swapParams = IUniswapV3Router.ExactInputSingleParams({ tokenIn: params.tokenA, tokenOut: params.tokenB, fee: params.uniswapFee, recipient: address(this), deadline: params.deadline, amountIn: params.amountIn, amountOutMinimum: minAmountOut, sqrtPriceLimitX96: 0 }); amountOut = UNISWAP_V3_ROUTER.exactInputSingle(swapParams); // Reset approval after use for security IERC20(params.tokenA).safeApprove(address(UNISWAP_V3_ROUTER), 0); // Validate slippage didn't exceed expectations require(amountOut >= minAmountOut, "Excessive slippage detected"); } /** * @dev Sell tokens on Uniswap V3 with precise approvals and slippage protection */ function sellOnUniswap(address tokenIn, address tokenOut, uint256 amountIn, uint24 fee) private returns (uint256 amountOut) { // Reset any existing approval to 0 first IERC20(tokenIn).safeApprove(address(UNISWAP_V3_ROUTER), 0); // Approve exact amount needed IERC20(tokenIn).safeApprove(address(UNISWAP_V3_ROUTER), amountIn); // Calculate minimum amount out with slippage protection (assuming 0.5% slippage for simplicity) uint256 minAmountOut = (amountIn * 9950) / 10000; // 0.5% slippage IUniswapV3Router.ExactInputSingleParams memory swapParams = IUniswapV3Router.ExactInputSingleParams({ tokenIn: tokenIn, tokenOut: tokenOut, fee: fee, recipient: address(this), deadline: block.timestamp + 300, amountIn: amountIn, amountOutMinimum: minAmountOut, sqrtPriceLimitX96: 0 }); amountOut = UNISWAP_V3_ROUTER.exactInputSingle(swapParams); // Reset approval after use IERC20(tokenIn).safeApprove(address(UNISWAP_V3_ROUTER), 0); require(amountOut >= minAmountOut, "Excessive slippage on Uniswap sell"); } /** * @dev Buy tokens on Camelot with precise approvals and slippage protection */ function buyOnCamelot(ArbitrageParams memory params) private returns (uint256 amountOut) { // Reset approval to 0 first IERC20(params.tokenA).safeApprove(address(CAMELOT_ROUTER), 0); // Approve exact amount needed IERC20(params.tokenA).safeApprove(address(CAMELOT_ROUTER), params.amountIn); // Calculate minimum amount out with slippage protection uint256 minAmountOut = _calculateMinAmountOut(params.amountIn, params.maxSlippageBps); uint256[] memory amounts = CAMELOT_ROUTER.swapExactTokensForTokens( params.amountIn, minAmountOut, params.camelotPath, address(this), address(0), // referrer params.deadline ); amountOut = amounts[amounts.length - 1]; // Reset approval after use IERC20(params.tokenA).safeApprove(address(CAMELOT_ROUTER), 0); require(amountOut >= minAmountOut, "Excessive slippage on Camelot buy"); } /** * @dev Sell tokens on Camelot with precise approvals and slippage protection */ function sellOnCamelot(address tokenIn, address tokenOut, uint256 amountIn, address[] memory path) private returns (uint256 amountOut) { // Reset approval to 0 first IERC20(tokenIn).safeApprove(address(CAMELOT_ROUTER), 0); // Approve exact amount needed IERC20(tokenIn).safeApprove(address(CAMELOT_ROUTER), amountIn); // Calculate minimum amount out with slippage protection (0.5% slippage) uint256 minAmountOut = (amountIn * 9950) / 10000; uint256[] memory amounts = CAMELOT_ROUTER.swapExactTokensForTokens( amountIn, minAmountOut, path, address(this), address(0), block.timestamp + 300 ); amountOut = amounts[amounts.length - 1]; // Reset approval after use IERC20(tokenIn).safeApprove(address(CAMELOT_ROUTER), 0); require(amountOut >= minAmountOut, "Excessive slippage on Camelot sell"); } /** * @dev Estimate profit for given arbitrage parameters */ function estimateProfit(ArbitrageParams memory params) public view returns (uint256 profit) { // This is a simplified estimation - in production you'd use more sophisticated pricing try CAMELOT_ROUTER.getAmountsOut(params.amountIn, params.camelotPath) returns (uint256[] memory amounts) { uint256 camelotOutput = amounts[amounts.length - 1]; // Estimate Uniswap output (simplified) uint256 uniswapOutput = params.amountIn * 995 / 1000; // Rough estimate with 0.5% slippage if (params.buyOnUniswap && camelotOutput > params.amountIn) { profit = camelotOutput - params.amountIn; } else if (!params.buyOnUniswap && uniswapOutput > params.amountIn) { profit = uniswapOutput - params.amountIn; } // Subtract estimated gas costs (0.002 ETH equivalent) uint256 gasCostInToken = 0.002 ether; // Rough estimate profit = profit > gasCostInToken ? profit - gasCostInToken : 0; } catch { profit = 0; } } /** * @dev Calculate optimal flash swap amount for maximum profit */ function calculateOptimalAmount(ArbitrageParams memory params) public view returns (uint256) { // Start with base amount and find optimal size uint256 baseAmount = 1 ether; // 1 token base uint256 maxProfit = 0; uint256 optimalAmount = baseAmount; // Test different amounts to find optimal for (uint256 multiplier = 1; multiplier <= 10; multiplier++) { uint256 testAmount = baseAmount * multiplier; ArbitrageParams memory testParams = params; testParams.amountIn = testAmount; uint256 estimatedProfit = estimateProfit(testParams); if (estimatedProfit > maxProfit && estimatedProfit >= minProfitThreshold) { maxProfit = estimatedProfit; optimalAmount = testAmount; } } return optimalAmount; } /** * @dev Validate that the callback is from a legitimate Uniswap V3 pool */ function isValidPool(address pool, address tokenA, address tokenB) private view returns (bool) { try IUniswapV3Pool(pool).token0() returns (address token0) { try IUniswapV3Pool(pool).token1() returns (address token1) { return (token0 == tokenA && token1 == tokenB) || (token0 == tokenB && token1 == tokenA); } catch { return false; } } catch { return false; } } /** * @dev Calculate minimum amount out based on slippage tolerance */ function _calculateMinAmountOut(uint256 amountIn, uint256 slippageBps) private pure returns (uint256) { require(slippageBps <= MAX_SLIPPAGE_BPS, "Slippage too high"); return (amountIn * (10000 - slippageBps)) / 10000; } /** * @dev Withdraw accumulated profits with enhanced security */ function withdrawProfits(address token, uint256 amount) external onlyRole(ADMIN_ROLE) nonReentrant { require(token != address(0), "Invalid token address"); require(amount > 0, "Amount must be positive"); uint256 balance = IERC20(token).balanceOf(address(this)); require(balance >= amount, "Insufficient balance"); require(amount <= balance, "Amount exceeds balance"); address admin = getRoleMember(ADMIN_ROLE, 0); IERC20(token).safeTransfer(admin, amount); emit ProfitWithdrawn(token, amount, admin); } /** * @dev Emergency withdrawal function with strict access control */ function emergencyWithdraw(address token, uint256 amount) external onlyRole(EMERGENCY_ROLE) nonReentrant { require(!emergencyStop, "Emergency stop active"); require(token != address(0), "Invalid token"); require(amount > 0, "Invalid amount"); uint256 balance = IERC20(token).balanceOf(address(this)); require(balance >= amount, "Insufficient balance"); address emergencyAdmin = getRoleMember(EMERGENCY_ROLE, 0); IERC20(token).safeTransfer(emergencyAdmin, amount); emit ProfitWithdrawn(token, amount, emergencyAdmin); } /** * @dev Update minimum profit threshold with validation */ function setMinProfitThreshold(uint256 _minProfitThreshold) external onlyRole(ADMIN_ROLE) { require(_minProfitThreshold > 0, "Threshold must be positive"); require(_minProfitThreshold <= 1 ether, "Threshold too high"); minProfitThreshold = _minProfitThreshold; } /** * @dev Update maximum gas price with validation */ function setMaxGasPrice(uint256 _maxGasPrice) external onlyRole(ADMIN_ROLE) { require(_maxGasPrice > 0, "Gas price must be positive"); require(_maxGasPrice <= 50 gwei, "Gas price too high"); maxGasPrice = _maxGasPrice; } /** * @dev Pause contract in case of emergency */ function pause() external onlyRole(EMERGENCY_ROLE) { _pause(); } /** * @dev Unpause contract */ function unpause() external onlyRole(ADMIN_ROLE) { _unpause(); } /** * @dev Receive ETH */ receive() external payable {} }