diff --git a/defi-automation/mev-detection-trap/README.md b/defi-automation/mev-detection-trap/README.md new file mode 100644 index 0000000..7923251 --- /dev/null +++ b/defi-automation/mev-detection-trap/README.md @@ -0,0 +1,143 @@ +# MEV Detection Trap + +A comprehensive system of smart contract traps for detecting and responding to MEV (Maximal Extractable Value) activities on Ethereum and other EVM-compatible chains. + + +Common MEV activities include: +- **Sandwich Attacks**: Front-running and back-running large trades +- **Arbitrage**: Exploiting price differences across DEXs +- **Liquidation**: Profiting from liquidated positions +- **Front-running**: Executing trades before others based on pending transactions + +## πŸš€ Features + +### 1. **MEVDetectionTrap** - General MEV Detection +- **Multi-DEX Monitoring**: Tracks multiple decentralized exchanges simultaneously +- **Pattern Recognition**: Identifies various MEV patterns using time-series analysis +- **Configurable Thresholds**: Adjustable parameters for different detection sensitivity +- **Real-time Alerts**: Immediate notification when MEV activities are detected + +### 2. **SandwichAttackTrap** - Specialized Sandwich Detection +- **Advanced Pattern Recognition**: Sophisticated algorithms for detecting sandwich attacks +- **Historical Data Analysis**: Uses historical trading data for pattern validation +- **Volume Analysis**: Monitors unusual volume spikes and ratios +- **Timing Analysis**: Detects transactions that are suspiciously close together + +### 3. **MEVResponse** - Automated Response System +- **Multi-Strategy Responses**: Different response strategies for different MEV types +- **Rate Limiting**: Configurable cooldown periods and execution limits +- **Emergency Controls**: Ability to pause responses in emergency situations +- **Event Logging**: Comprehensive logging of all detected activities and responses + +## πŸ—οΈ Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ MEV Traps β”‚ β”‚ Drosera β”‚ β”‚ Response β”‚ +β”‚ β”‚ β”‚ Protocol β”‚ β”‚ Contracts β”‚ +β”‚ β€’ Collect Data │───▢│ β€’ Run Logic │───▢│ β€’ Execute β”‚ +β”‚ β€’ Analyze β”‚ β”‚ β€’ Monitor β”‚ β”‚ β€’ Alert β”‚ +β”‚ β€’ Detect β”‚ β”‚ β€’ Trigger β”‚ β”‚ β€’ Respond β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## πŸ“Š Detection Capabilities + +### Sandwich Attack Detection +- **Volume Pattern Analysis**: Detects small β†’ large β†’ small trade patterns +- **Price Impact Monitoring**: Tracks significant price movements +- **Timing Analysis**: Identifies transactions executed in rapid succession +- **Gas Price Spikes**: Monitors for unusual gas price increases + +### Arbitrage Opportunity Detection +- **Cross-DEX Price Monitoring**: Tracks price differences across exchanges +- **Threshold-Based Alerts**: Configurable profit thresholds +- **Real-time Analysis**: Continuous monitoring of arbitrage opportunities + +### MEV Bot Activity Detection +- **Gas Price Anomalies**: Detects unusually high gas prices +- **Transaction Timing**: Identifies rapid successive transactions +- **Pattern Recognition**: Recognizes typical MEV bot behavior + +### Suspicious Pattern Detection +- **Volume Anomalies**: Detects unusual trading volume patterns +- **Statistical Analysis**: Uses statistical methods to identify outliers +- **Multi-Factor Analysis**: Combines multiple indicators for detection + +## πŸ› οΈ Installation & Setup + +### Prerequisites +- [Foundry](https://getfoundry.sh/) (latest version) +- Node.js 16+ and npm/yarn +- Access to Ethereum RPC endpoints + +### Quick Start + +1. **Install Dependencies** +```bash +forge install +``` + +2. **Build Contracts** +```bash +forge build +``` + +3. **Run Tests** +```bash +forge test +``` + +### Threshold Configuration + +The traps use configurable thresholds that can be adjusted based on your needs: + +```solidity +// MEVDetectionTrap thresholds +uint256 constant MIN_SANDWICH_THRESHOLD = 0.5e18; // 50% price impact +uint256 constant MAX_ARBITRAGE_THRESHOLD = 0.02e18; // 2% arbitrage threshold +uint256 constant MIN_MEV_PROFIT_THRESHOLD = 0.1e18; // 0.1 ETH minimum profit +uint256 constant SUSPICIOUS_GAS_THRESHOLD = 1000; // 1000 gwei gas price + +// SandwichAttackTrap thresholds +uint256 constant MIN_PRICE_IMPACT = 0.1e18; // 10% minimum price impact +uint256 constant MAX_TIME_BETWEEN_TXS = 3; // 3 seconds max between transactions +uint256 constant MIN_VOLUME_RATIO = 5; // 5x volume ratio threshold +uint256 constant SUSPICIOUS_GAS_MULTIPLIER = 2; // 2x gas price multiplier +``` + +## πŸ”§ Usage Examples + +### Adding Monitored DEXs + +```solidity +// Add a new DEX to monitor +mevTrap.addMonitoredDEX(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); // Uniswap V2 +mevTrap.addMonitoredDEX(0xE592427A0AEce92De3Edee1F18E0157C05861564); // Uniswap V3 +``` + +### Adding Known MEV Bots + +```solidity +// Add known MEV bot addresses for tracking +mevTrap.addKnownMEVBot(0x1234567890123456789012345678901234567890); +``` + +### Event Logging + +All activities are logged with comprehensive events: + +```solidity +event SandwichAttackDetected( + uint256 indexed blockNumber, + uint256 timestamp, + address[] involvedAddresses, + uint256 estimatedProfit +); + +event ResponseExecuted( + uint256 indexed blockNumber, + MEVType mevType, + bytes responseData +); +``` \ No newline at end of file diff --git a/defi-automation/mev-detection-trap/drosera.toml b/defi-automation/mev-detection-trap/drosera.toml new file mode 100644 index 0000000..2f161c8 --- /dev/null +++ b/defi-automation/mev-detection-trap/drosera.toml @@ -0,0 +1,30 @@ +ethereum_rpc = "https://ethereum-hoodi-rpc.publicnode.com" +drosera_rpc = "https://relay.hoodi.drosera.io" +eth_chain_id = 560048 +drosera_address = "" + +[traps] + +[traps.mev_detection_monitor] +path = "out/MEVDetectionTrap.sol/MEVDetectionTrap.json" +response_contract = "0x0000000000000000000000000000000000000000" # would be MEVResponse contract address +response_function = "handleMEVAlert(bytes)" # function to handle MEV alerts +cooldown_period_blocks = 10 +min_number_of_operators = 3 +max_number_of_operators = 8 +block_sample_size = 15 +private_trap = false +whitelist = [] +address = "0x0000000000000000000000000000000000000000" # deployed trap address + +[traps.sandwich_attack_monitor] +path = "out/SandwichAttackTrap.sol/SandwichAttackTrap.json" +response_contract = "0x0000000000000000000000000000000000000000" # would be MEVResponse contract address +response_function = "handleMEVAlert(bytes)" # function to handle sandwich attack alerts +cooldown_period_blocks = 5 +min_number_of_operators = 2 +max_number_of_operators = 6 +block_sample_size = 20 +private_trap = false +whitelist = [] +address = "0x0000000000000000000000000000000000000000" # deployed trap address diff --git a/defi-automation/mev-detection-trap/foundry.toml b/defi-automation/mev-detection-trap/foundry.toml new file mode 100644 index 0000000..fee36f8 --- /dev/null +++ b/defi-automation/mev-detection-trap/foundry.toml @@ -0,0 +1,12 @@ +[profile.default] +src = "src" +out = "out" +libs = ["node_modules"] +remappings = [ + "forge-std/=node_modules/forge-std/src/", + "contracts/=node_modules/contracts/src/" +] + +[rpc_endpoints] +mainnet = "https://eth.llamarpc.com" + diff --git a/defi-automation/mev-detection-trap/package.json b/defi-automation/mev-detection-trap/package.json new file mode 100644 index 0000000..486253f --- /dev/null +++ b/defi-automation/mev-detection-trap/package.json @@ -0,0 +1,11 @@ +{ + "name": "@trap-examples/defi-automation/mev-detection-trap", + "version": "1.0.0", + "description": "Comprehensive MEV detection traps for identifying sandwich attacks, arbitrage opportunities, and suspicious trading patterns", + "devDependencies": { + "forge-std": "^1.7.1" + }, + "dependencies": { + "contracts": "https://github.com/drosera-network/contracts" + } +} diff --git a/defi-automation/mev-detection-trap/script/Deploy.s.sol b/defi-automation/mev-detection-trap/script/Deploy.s.sol new file mode 100644 index 0000000..c14b0d3 --- /dev/null +++ b/defi-automation/mev-detection-trap/script/Deploy.s.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Script, console2} from "forge-std/Script.sol"; +import {MEVDetectionTrap} from "../src/MEVDetectionTrap.sol"; +import {SandwichAttackTrap} from "../src/SandwichAttackTrap.sol"; +import {MEVResponse} from "../src/MEVResponse.sol"; + +contract DeployScript is Script { + MEVDetectionTrap public mevTrap; + SandwichAttackTrap public sandwichTrap; + MEVResponse public mevResponse; + + address public mevTrapAddress; + address public sandwichTrapAddress; + address public mevResponseAddress; + + address public deployer; + address public trapConfig; + + function setUp() public { + deployer = msg.sender; + trapConfig = deployer; // For now, deployer is also trap config + } + + function run() public { + console2.log("Starting MEV Detection Trap deployment..."); + console2.log("Deployer:", deployer); + console2.log("Chain ID:", block.chainid); + + vm.startBroadcast(deployer); + + // Deploy MEV Response contract first + console2.log("Deploying MEVResponse contract..."); + mevResponse = new MEVResponse(trapConfig); + mevResponseAddress = address(mevResponse); + console2.log("MEVResponse deployed at:", mevResponseAddress); + + // Deploy main MEV Detection Trap + console2.log("Deploying MEVDetectionTrap contract..."); + mevTrap = new MEVDetectionTrap(); + mevTrapAddress = address(mevTrap); + console2.log("MEVDetectionTrap deployed at:", mevTrapAddress); + + // Deploy specialized Sandwich Attack Trap + console2.log("Deploying SandwichAttackTrap contract..."); + sandwichTrap = new SandwichAttackTrap(); + sandwichTrapAddress = address(sandwichTrap); + console2.log("SandwichAttackTrap deployed at:", sandwichTrapAddress); + + vm.stopBroadcast(); + + console2.log("Configuring contracts..."); + _configureContracts(); + _displayDeploymentSummary(); + _saveDeploymentAddresses(); + } + + function _configureContracts() internal { + vm.startBroadcast(deployer); + + console2.log("Adding popular DEX addresses..."); + + // Uniswap V2 Router + mevTrap.addMonitoredDEX(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); + + // Uniswap V3 Router + mevTrap.addMonitoredDEX(0xE592427A0AEce92De3Edee1F18E0157C05861564); + + // SushiSwap Router + mevTrap.addMonitoredDEX(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); + + // Add some known MEV bot addresses (this would be updated with real addresses) + console2.log("Adding known MEV bot addresses..."); + mevTrap.addKnownMEVBot(0x0000000000000000000000000000000000000000); + + console2.log("Adding popular DEX pairs..."); + + // Example pairs (replace with real addresses) + sandwichTrap.addMonitoredAddress(0x0000000000000000000000000000000000000000); + sandwichTrap.addMonitoredAddress(0x0000000000000000000000000000000000000000); + sandwichTrap.addMonitoredAddress(0x0000000000000000000000000000000000000000); + + vm.stopBroadcast(); + + console2.log("Contract configuration completed"); + } + + function _displayDeploymentSummary() internal view { + console2.log("\nDeployment Summary"); + console2.log("====================="); + console2.log("Chain ID:", block.chainid); + console2.log("Deployer:", deployer); + console2.log("Trap Config:", trapConfig); + console2.log(""); + console2.log("MEVResponse:", mevResponseAddress); + console2.log("MEVDetectionTrap:", mevTrapAddress); + console2.log("SandwichAttackTrap:", sandwichTrapAddress); + console2.log(""); + console2.log("Monitored DEXs:", mevTrap.getMonitoredDEXs().length); + console2.log("Known MEV Bots:", mevTrap.getKnownMEVBots().length); + console2.log("Monitored Addresses:", sandwichTrap.getMonitoredAddresses().length); + console2.log(""); + console2.log("Next Steps:"); + console2.log("1. Update drosera.toml with deployed addresses"); + console2.log("2. Configure response contract addresses"); + console2.log("3. Set up monitoring and alerts"); + console2.log("4. Test with real MEV scenarios"); + } + + function _saveDeploymentAddresses() internal { + string memory deploymentInfo = string(abi.encodePacked( + "MEV Detection Trap Deployment\n", + "=============================\n", + "Chain ID: ", vm.toString(block.chainid), "\n", + "Deployer: ", vm.toString(deployer), "\n", + "Trap Config: ", vm.toString(trapConfig), "\n\n", + "MEVResponse: ", vm.toString(mevResponseAddress), "\n", + "MEVDetectionTrap: ", vm.toString(mevTrapAddress), "\n", + "SandwichAttackTrap: ", vm.toString(sandwichTrapAddress), "\n\n", + "Deployment Time: ", vm.toString(block.timestamp), "\n" + )); + + vm.writeFile("deployment.txt", deploymentInfo); + console2.log("Deployment addresses saved to deployment.txt"); + } + + function verify() public { + console2.log("Verifying contracts on Etherscan..."); + + console2.log("MEVResponse verification command:"); + console2.log("forge verify-contract", mevResponseAddress, "src/MEVResponse.sol:MEVResponse --chain-id", block.chainid); + + console2.log("MEVDetectionTrap verification command:"); + console2.log("forge verify-contract", mevTrapAddress, "src/MEVDetectionTrap.sol:MEVDetectionTrap --chain-id", block.chainid); + + console2.log("SandwichAttackTrap verification command:"); + console2.log("forge verify-contract", sandwichTrapAddress, "src/SandwichAttackTrap.sol:SandwichAttackTrap --chain-id", block.chainid); + } + + function generateDroseraConfig() public view { + console2.log("\nUpdate your drosera.toml with these addresses:"); + console2.log(""); + console2.log("[traps.mev_detection_monitor]"); + console2.log("path = \"out/MEVDetectionTrap.sol/MEVDetectionTrap.json\""); + console2.log("response_contract = \"", mevResponseAddress, "\""); + console2.log("response_function = \"handleMEVAlert(bytes)\""); + console2.log("address = \"", mevTrapAddress, "\""); + console2.log(""); + console2.log("[traps.sandwich_attack_monitor]"); + console2.log("path = \"out/SandwichAttackTrap.sol/SandwichAttackTrap.json\""); + console2.log("response_contract = \"", mevResponseAddress, "\""); + console2.log("response_function = \"handleMEVAlert(bytes)\""); + console2.log("address = \"", sandwichTrapAddress, "\""); + } +} diff --git a/defi-automation/mev-detection-trap/src/MEVDetectionTrap.sol b/defi-automation/mev-detection-trap/src/MEVDetectionTrap.sol new file mode 100644 index 0000000..86113ab --- /dev/null +++ b/defi-automation/mev-detection-trap/src/MEVDetectionTrap.sol @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ITrap} from "./interfaces/ITrap.sol"; + +interface IUniswapV2Pair { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IUniswapV3Pool { + function slot0() external view returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + function token0() external view returns (address); + function token1() external view returns (address); + function fee() external view returns (uint24); +} + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); + function decimals() external view returns (uint8); +} + +/** + * @title MEVDetectionTrap + * @notice Comprehensive trap for detecting MEV activities including sandwich attacks, + * arbitrage opportunities, and suspicious trading patterns + * @dev Monitors multiple DEXs and analyzes transaction patterns for MEV detection + */ +contract MEVDetectionTrap is ITrap { + + uint256 constant MIN_SANDWICH_THRESHOLD = 0.5e18; // 50% price impact threshold + uint256 constant MAX_ARBITRAGE_THRESHOLD = 0.02e18; // 2% arbitrage threshold + uint256 constant MIN_MEV_PROFIT_THRESHOLD = 0.1e18; // 0.1 ETH minimum profit + uint256 constant SUSPICIOUS_GAS_THRESHOLD = 1000; // 1000 gwei gas price threshold + + // DEX addresses to monitor + address[] public monitoredDEXs; + mapping(address => bool) public isMonitoredDEX; + + // MEV bot addresses to track + address[] public knownMEVBots; + mapping(address => bool) public isKnownMEVBot; + + struct MEVData { + uint256 blockNumber; + uint256 timestamp; + address[] dexAddresses; + uint256[] prices; + uint256[] volumes; + uint256 gasPrice; + address[] suspiciousAddresses; + MEVType mevType; + uint256 estimatedProfit; + } + + enum MEVType { + NONE, + SANDWICH_ATTACK, + ARBITRAGE_OPPORTUNITY, + MEV_BOT_ACTIVITY, + SUSPICIOUS_PATTERN + } + + struct MEVAlert { + MEVType mevType; + uint256 blockNumber; + uint256 timestamp; + address[] involvedAddresses; + uint256 estimatedProfit; + bytes additionalData; + } + + constructor() { + // Initialize with popular DEXs + _addMonitoredDEX(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); // Uniswap V2 Router + _addMonitoredDEX(0xE592427A0AEce92De3Edee1F18E0157C05861564); // Uniswap V3 Router + _addMonitoredDEX(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); // SushiSwap Router + + // Add some known MEV bot addresses (this would be updated dynamically) + _addKnownMEVBot(0x0000000000000000000000000000000000000000); // Placeholder + } + + function collect() external view override returns (bytes memory) { + MEVData memory mevData = MEVData({ + blockNumber: block.number, + timestamp: block.timestamp, + dexAddresses: new address[](monitoredDEXs.length), + prices: new uint256[](monitoredDEXs.length), + volumes: new uint256[](monitoredDEXs.length), + gasPrice: tx.gasprice, + suspiciousAddresses: new address[](0), + mevType: MEVType.NONE, + estimatedProfit: 0 + }); + + for (uint256 i = 0; i < monitoredDEXs.length; i++) { + address dex = monitoredDEXs[i]; + mevData.dexAddresses[i] = dex; + + try this._getDEXData(dex) returns (uint256 price, uint256 volume) { + mevData.prices[i] = price; + mevData.volumes[i] = volume; + } catch { + mevData.prices[i] = 0; + mevData.volumes[i] = 0; + } + } + + return abi.encode(mevData); + } + + function shouldRespond( + bytes[] calldata data + ) external pure override returns (bool shouldTrigger, bytes memory responseData) { + if (data.length < 2) { + return (false, ""); + } + + MEVData[] memory mevDataArray = new MEVData[](data.length); + for (uint256 i = 0; i < data.length; i++) { + mevDataArray[i] = abi.decode(data[i], (MEVData)); + } + + MEVAlert memory alert = _analyzeMEVPatterns(mevDataArray); + + if (alert.mevType != MEVType.NONE) { + return (true, abi.encode(alert)); + } + + return (false, ""); + } + + function _analyzeMEVPatterns( + MEVData[] memory mevDataArray + ) internal pure returns (MEVAlert memory) { + if (mevDataArray.length < 2) { + return MEVAlert({ + mevType: MEVType.NONE, + blockNumber: 0, + timestamp: 0, + involvedAddresses: new address[](0), + estimatedProfit: 0, + additionalData: "" + }); + } + + // Check for sandwich attack patterns + if (_detectSandwichAttack(mevDataArray)) { + return _createSandwichAlert(mevDataArray); + } + + // Check for arbitrage opportunities + if (_detectArbitrageOpportunity(mevDataArray)) { + return _createArbitrageAlert(mevDataArray); + } + + // Check for MEV bot activity + if (_detectMEVBotActivity(mevDataArray)) { + return _createMEVBotAlert(mevDataArray); + } + + // Check for suspicious patterns + if (_detectSuspiciousPatterns(mevDataArray)) { + return _createSuspiciousPatternAlert(mevDataArray); + } + + return MEVAlert({ + mevType: MEVType.NONE, + blockNumber: 0, + timestamp: 0, + involvedAddresses: new address[](0), + estimatedProfit: 0, + additionalData: "" + }); + } + + function _detectSandwichAttack( + MEVData[] memory mevDataArray + ) internal pure returns (bool) { + if (mevDataArray.length < 3) return false; + + for (uint256 i = 1; i < mevDataArray.length - 1; i++) { + uint256 prevVolume = mevDataArray[i-1].volumes[0]; + uint256 currentVolume = mevDataArray[i].volumes[0]; + uint256 nextVolume = mevDataArray[i+1].volumes[0]; + + if (currentVolume > prevVolume * 10 && currentVolume > nextVolume * 10) { + uint256 priceImpact = _calculatePriceImpact( + mevDataArray[i-1].prices[0], + mevDataArray[i].prices[0] + ); + + if (priceImpact > MIN_SANDWICH_THRESHOLD) { + return true; + } + } + } + + return false; + } + + function _detectArbitrageOpportunity( + MEVData[] memory mevDataArray + ) internal pure returns (bool) { + if (mevDataArray.length < 2) return false; + + for (uint256 i = 0; i < mevDataArray.length; i++) { + for (uint256 j = i + 1; j < mevDataArray.length; j++) { + if (mevDataArray[i].prices.length > 0 && mevDataArray[j].prices.length > 0) { + uint256 priceDiff = _calculatePriceDifference( + mevDataArray[i].prices[0], + mevDataArray[j].prices[0] + ); + + if (priceDiff > MAX_ARBITRAGE_THRESHOLD) { + return true; + } + } + } + } + + return false; + } + + function _detectMEVBotActivity( + MEVData[] memory mevDataArray + ) internal pure returns (bool) { + for (uint256 i = 0; i < mevDataArray.length; i++) { + if (mevDataArray[i].gasPrice > SUSPICIOUS_GAS_THRESHOLD * 1e9) { + // Check if there are rapid successive transactions + if (i > 0) { + uint256 timeDiff = mevDataArray[i].timestamp - mevDataArray[i-1].timestamp; + if (timeDiff < 2) { // Less than 2 seconds between blocks + return true; + } + } + } + } + + return false; + } + + function _detectSuspiciousPatterns( + MEVData[] memory mevDataArray + ) internal pure returns (bool) { + // Check for unusual volume spikes + uint256 totalVolume = 0; + uint256 avgVolume = 0; + + for (uint256 i = 0; i < mevDataArray.length; i++) { + if (mevDataArray[i].volumes.length > 0) { + totalVolume += mevDataArray[i].volumes[0]; + } + } + + avgVolume = totalVolume / mevDataArray.length; + + for (uint256 i = 0; i < mevDataArray.length; i++) { + if (mevDataArray[i].volumes.length > 0 && + mevDataArray[i].volumes[0] > avgVolume * 5) { // 5x average + return true; + } + } + + return false; + } + + function _createSandwichAlert( + MEVData[] memory mevDataArray + ) internal pure returns (MEVAlert memory) { + address[] memory involvedAddresses = new address[](1); + involvedAddresses[0] = address(0); // Would be the sandwich attacker + + return MEVAlert({ + mevType: MEVType.SANDWICH_ATTACK, + blockNumber: mevDataArray[mevDataArray.length - 1].blockNumber, + timestamp: mevDataArray[mevDataArray.length - 1].timestamp, + involvedAddresses: involvedAddresses, + estimatedProfit: _estimateSandwichProfit(mevDataArray), + additionalData: abi.encode("Sandwich attack detected with significant price impact") + }); + } + + function _createArbitrageAlert( + MEVData[] memory mevDataArray + ) internal pure returns (MEVAlert memory) { + address[] memory involvedAddresses = new address[](1); + involvedAddresses[0] = address(0); // Would be the arbitrage opportunity + + return MEVAlert({ + mevType: MEVType.ARBITRAGE_OPPORTUNITY, + blockNumber: mevDataArray[mevDataArray.length - 1].blockNumber, + timestamp: mevDataArray[mevDataArray.length - 1].timestamp, + involvedAddresses: involvedAddresses, + estimatedProfit: _estimateArbitrageProfit(mevDataArray), + additionalData: abi.encode("Arbitrage opportunity detected across DEXs") + }); + } + + function _createMEVBotAlert( + MEVData[] memory mevDataArray + ) internal pure returns (MEVAlert memory) { + address[] memory involvedAddresses = new address[](1); + involvedAddresses[0] = address(0); // Would be the MEV bot + + return MEVAlert({ + mevType: MEVType.MEV_BOT_ACTIVITY, + blockNumber: mevDataArray[mevDataArray.length - 1].blockNumber, + timestamp: mevDataArray[mevDataArray.length - 1].timestamp, + involvedAddresses: involvedAddresses, + estimatedProfit: 0, + additionalData: abi.encode("MEV bot activity detected with high gas prices") + }); + } + + function _createSuspiciousPatternAlert( + MEVData[] memory mevDataArray + ) internal pure returns (MEVAlert memory) { + address[] memory involvedAddresses = new address[](1); + involvedAddresses[0] = address(0); // Would be the suspicious address + + return MEVAlert({ + mevType: MEVType.SUSPICIOUS_PATTERN, + blockNumber: mevDataArray[mevDataArray.length - 1].blockNumber, + timestamp: mevDataArray[mevDataArray.length - 1].timestamp, + involvedAddresses: involvedAddresses, + estimatedProfit: 0, + additionalData: abi.encode("Suspicious trading pattern detected") + }); + } + + function _calculatePriceImpact( + uint256 priceBefore, + uint256 priceAfter + ) internal pure returns (uint256) { + if (priceBefore == 0) return 0; + return ((priceAfter - priceBefore) * 1e18); + } + + function _calculatePriceDifference( + uint256 price1, + uint256 price2 + ) internal pure returns (uint256) { + if (price1 == 0 || price2 == 0) return 0; + return price1 > price2 ? + ((price1 - price2) * 1e18) / price2 : + ((price2 - price1) * 1e18) / price1; + } + + function _estimateSandwichProfit( + MEVData[] memory mevDataArray + ) internal pure returns (uint256) { + if (mevDataArray.length < 3) return 0; + + uint256 priceBefore = mevDataArray[0].prices[0]; + uint256 priceAfter = mevDataArray[mevDataArray.length - 1].prices[0]; + uint256 volume = mevDataArray[1].volumes[0]; + + if (priceBefore == 0 || volume == 0) return 0; + + uint256 priceImpact = _calculatePriceImpact(priceBefore, priceAfter); + return (priceImpact * volume) / 1e18; + } + + function _estimateArbitrageProfit( + MEVData[] memory mevDataArray + ) internal pure returns (uint256) { + uint256 maxDiff = 0; + + for (uint256 i = 0; i < mevDataArray.length; i++) { + for (uint256 j = i + 1; j < mevDataArray.length; j++) { + if (mevDataArray[i].prices.length > 0 && mevDataArray[j].prices.length > 0) { + uint256 diff = _calculatePriceDifference( + mevDataArray[i].prices[0], + mevDataArray[j].prices[0] + ); + if (diff > maxDiff) { + maxDiff = diff; + } + } + } + } + + return maxDiff; + } + + function _getDEXData(address dex) external view returns (uint256 price, uint256 volume) { + // mock data + price = 1000e18; // 1000 USD + volume = 1000e18; // 1000 tokens + } + + // Admin functions + function addMonitoredDEX(address dex) external { + if (!isMonitoredDEX[dex]) { + monitoredDEXs.push(dex); + isMonitoredDEX[dex] = true; + } + } + + function addKnownMEVBot(address bot) external { + if (!isKnownMEVBot[bot]) { + knownMEVBots.push(bot); + isKnownMEVBot[bot] = true; + } + } + + function removeMonitoredDEX(address dex) external { + if (isMonitoredDEX[dex]) { + isMonitoredDEX[dex] = false; + } + } + + function removeKnownMEVBot(address bot) external { + if (isKnownMEVBot[bot]) { + isKnownMEVBot[bot] = false; + } + } + + function getMonitoredDEXs() external view returns (address[] memory) { + return monitoredDEXs; + } + + function getKnownMEVBots() external view returns (address[] memory) { + return knownMEVBots; + } + + function getThresholds() external pure returns ( + uint256 sandwichThreshold, + uint256 arbitrageThreshold, + uint256 mevProfitThreshold, + uint256 suspiciousGasThreshold + ) { + return ( + MIN_SANDWICH_THRESHOLD, + MAX_ARBITRAGE_THRESHOLD, + MIN_MEV_PROFIT_THRESHOLD, + SUSPICIOUS_GAS_THRESHOLD + ); + } + + function _addMonitoredDEX(address dex) internal { + if (!isMonitoredDEX[dex]) { + monitoredDEXs.push(dex); + isMonitoredDEX[dex] = true; + } + } + + function _addKnownMEVBot(address bot) internal { + if (!isKnownMEVBot[bot]) { + knownMEVBots.push(bot); + isKnownMEVBot[bot] = true; + } + } +} diff --git a/defi-automation/mev-detection-trap/src/MEVResponse.sol b/defi-automation/mev-detection-trap/src/MEVResponse.sol new file mode 100644 index 0000000..0a5db9f --- /dev/null +++ b/defi-automation/mev-detection-trap/src/MEVResponse.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {MEVDetectionTrap} from "./MEVDetectionTrap.sol"; + +/** + * @title MEVResponse + * @notice Response contract for handling MEV detection alerts + * @dev Implements various response strategies for different types of MEV activities + */ +contract MEVResponse { + + // Events for different response types + event SandwichAttackDetected( + uint256 indexed blockNumber, + uint256 timestamp, + address[] involvedAddresses, + uint256 estimatedProfit + ); + + event ArbitrageOpportunityDetected( + uint256 indexed blockNumber, + uint256 timestamp, + address[] involvedAddresses, + uint256 estimatedProfit + ); + + event MEVBotActivityDetected( + uint256 indexed blockNumber, + uint256 timestamp, + address[] involvedAddresses + ); + + event SuspiciousPatternDetected( + uint256 indexed blockNumber, + uint256 timestamp, + address[] involvedAddresses + ); + + event ResponseExecuted( + uint256 indexed blockNumber, + MEVDetectionTrap.MEVType mevType, + bytes responseData + ); + + address public trapConfig; + mapping(uint256 => bool) public processedAlerts; + mapping(MEVDetectionTrap.MEVType => bool) public enabledResponses; + + struct ResponseConfig { + bool enabled; + uint256 cooldownPeriod; + uint256 lastExecution; + uint256 maxExecutionsPerHour; + uint256 executionCount; + uint256 lastResetTime; + } + + mapping(MEVDetectionTrap.MEVType => ResponseConfig) public responseConfigs; + + bool public emergencyPaused; + address public emergencyPauser; + + constructor(address _trapConfig) { + trapConfig = _trapConfig; + emergencyPauser = msg.sender; + + _initializeResponseConfigs(); + } + + modifier onlyTrapConfig() { + require(msg.sender == trapConfig, "Only TrapConfig can call this"); + _; + } + + modifier onlyEmergencyPauser() { + require(msg.sender == emergencyPauser, "Only emergency pauser can call this"); + _; + } + + modifier whenNotPaused() { + require(!emergencyPaused, "Contract is emergency paused"); + _; + } + + function _initializeResponseConfigs() internal { + // Sandwich attack response config + responseConfigs[MEVDetectionTrap.MEVType.SANDWICH_ATTACK] = ResponseConfig({ + enabled: true, + cooldownPeriod: 300, // 5 minutes + lastExecution: 0, + maxExecutionsPerHour: 10, + executionCount: 0, + lastResetTime: block.timestamp + }); + + // Arbitrage opportunity response config + responseConfigs[MEVDetectionTrap.MEVType.ARBITRAGE_OPPORTUNITY] = ResponseConfig({ + enabled: true, + cooldownPeriod: 60, // 1 minute + lastExecution: 0, + maxExecutionsPerHour: 20, + executionCount: 0, + lastResetTime: block.timestamp + }); + + // MEV bot activity response config + responseConfigs[MEVDetectionTrap.MEVType.MEV_BOT_ACTIVITY] = ResponseConfig({ + enabled: true, + cooldownPeriod: 120, // 2 minutes + lastExecution: 0, + maxExecutionsPerHour: 15, + executionCount: 0, + lastResetTime: block.timestamp + }); + + // Suspicious pattern response config + responseConfigs[MEVDetectionTrap.MEVType.SUSPICIOUS_PATTERN] = ResponseConfig({ + enabled: true, + cooldownPeriod: 180, // 3 minutes + lastExecution: 0, + maxExecutionsPerHour: 12, + executionCount: 0, + lastResetTime: block.timestamp + }); + } + + /** + * @notice Main response function called by the Drosera Protocol + * @param alertData Encoded MEV alert data from the trap + * @dev This function matches the response_function signature in drosera.toml + */ + function handleMEVAlert(bytes calldata alertData) external onlyTrapConfig whenNotPaused { + MEVDetectionTrap.MEVAlert memory alert = abi.decode(alertData, (MEVDetectionTrap.MEVAlert)); + + // Check if alert was already processed + require(!processedAlerts[alert.blockNumber], "Alert already processed"); + + // Mark alert as processed + processedAlerts[alert.blockNumber] = true; + + // Check if response is enabled and within limits + require(_canExecuteResponse(alert.mevType), "Response not available"); + + // Update execution tracking + _updateExecutionTracking(alert.mevType); + + // Execute appropriate response based on MEV type + bytes memory responseData = _executeResponse(alert); + + // Emit events + _emitAlertEvents(alert); + emit ResponseExecuted(alert.blockNumber, alert.mevType, responseData); + } + + function _canExecuteResponse(MEVDetectionTrap.MEVType mevType) internal view returns (bool) { + ResponseConfig memory config = responseConfigs[mevType]; + + if (!config.enabled) return false; + if (block.timestamp < config.lastExecution + config.cooldownPeriod) return false; + + if (block.timestamp >= config.lastResetTime + 3600) { + // Reset hourly counter + return true; + } + + return config.executionCount < config.maxExecutionsPerHour; + } + + function _updateExecutionTracking(MEVDetectionTrap.MEVType mevType) internal { + ResponseConfig storage config = responseConfigs[mevType]; + + if (block.timestamp >= config.lastResetTime + 3600) { + config.executionCount = 0; + config.lastResetTime = block.timestamp; + } + + config.lastExecution = block.timestamp; + config.executionCount++; + } + + function _executeResponse( + MEVDetectionTrap.MEVAlert memory alert + ) internal returns (bytes memory) { + if (alert.mevType == MEVDetectionTrap.MEVType.SANDWICH_ATTACK) { + return _handleSandwichAttack(alert); + } else if (alert.mevType == MEVDetectionTrap.MEVType.ARBITRAGE_OPPORTUNITY) { + return _handleArbitrageOpportunity(alert); + } else if (alert.mevType == MEVDetectionTrap.MEVType.MEV_BOT_ACTIVITY) { + return _handleMEVBotActivity(alert); + } else if (alert.mevType == MEVDetectionTrap.MEVType.SUSPICIOUS_PATTERN) { + return _handleSuspiciousPattern(alert); + } + + return ""; + } + + function _handleSandwichAttack( + MEVDetectionTrap.MEVAlert memory alert + ) internal returns (bytes memory) { + return abi.encode( + "SANDWICH_RESPONSE", + alert.involvedAddresses, + alert.estimatedProfit, + block.timestamp + ); + } + + function _handleArbitrageOpportunity( + MEVDetectionTrap.MEVAlert memory alert + ) internal returns (bytes memory) { + return abi.encode( + "ARBITRAGE_RESPONSE", + alert.involvedAddresses, + alert.estimatedProfit, + block.timestamp + ); + } + + function _handleMEVBotActivity( + MEVDetectionTrap.MEVAlert memory alert + ) internal returns (bytes memory) { + return abi.encode( + "MEV_BOT_RESPONSE", + alert.involvedAddresses, + block.timestamp + ); + } + + function _handleSuspiciousPattern( + MEVDetectionTrap.MEVAlert memory alert + ) internal returns (bytes memory) { + return abi.encode( + "SUSPICIOUS_PATTERN_RESPONSE", + alert.involvedAddresses, + block.timestamp + ); + } + + function _emitAlertEvents(MEVDetectionTrap.MEVAlert memory alert) internal { + if (alert.mevType == MEVDetectionTrap.MEVType.SANDWICH_ATTACK) { + emit SandwichAttackDetected( + alert.blockNumber, + alert.timestamp, + alert.involvedAddresses, + alert.estimatedProfit + ); + } else if (alert.mevType == MEVDetectionTrap.MEVType.ARBITRAGE_OPPORTUNITY) { + emit ArbitrageOpportunityDetected( + alert.blockNumber, + alert.timestamp, + alert.involvedAddresses, + alert.estimatedProfit + ); + } else if (alert.mevType == MEVDetectionTrap.MEVType.MEV_BOT_ACTIVITY) { + emit MEVBotActivityDetected( + alert.blockNumber, + alert.timestamp, + alert.involvedAddresses + ); + } else if (alert.mevType == MEVDetectionTrap.MEVType.SUSPICIOUS_PATTERN) { + emit SuspiciousPatternDetected( + alert.blockNumber, + alert.timestamp, + alert.involvedAddresses + ); + } + } + + // Admin functions + + function setResponseConfig( + MEVDetectionTrap.MEVType mevType, + bool enabled, + uint256 cooldownPeriod, + uint256 maxExecutionsPerHour + ) external onlyTrapConfig { + ResponseConfig storage config = responseConfigs[mevType]; + config.enabled = enabled; + config.cooldownPeriod = cooldownPeriod; + config.maxExecutionsPerHour = maxExecutionsPerHour; + } + + function emergencyPause() external onlyEmergencyPauser { + emergencyPaused = true; + } + + function emergencyUnpause() external onlyEmergencyPauser { + emergencyPaused = false; + } + + function transferEmergencyPauser(address newPauser) external onlyEmergencyPauser { + emergencyPauser = newPauser; + } + + + function getResponseConfig( + MEVDetectionTrap.MEVType mevType + ) external view returns (ResponseConfig memory) { + return responseConfigs[mevType]; + } + + function isAlertProcessed(uint256 blockNumber) external view returns (bool) { + return processedAlerts[blockNumber]; + } + + function canExecuteResponse(MEVDetectionTrap.MEVType mevType) external view returns (bool) { + return _canExecuteResponse(mevType); + } + + function getResponseStatus() external view returns ( + bool paused, + uint256 totalProcessedAlerts, + bool[] memory responseStatuses + ) { + responseStatuses = new bool[](4); + responseStatuses[0] = responseConfigs[MEVDetectionTrap.MEVType.SANDWICH_ATTACK].enabled; + responseStatuses[1] = responseConfigs[MEVDetectionTrap.MEVType.ARBITRAGE_OPPORTUNITY].enabled; + responseStatuses[2] = responseConfigs[MEVDetectionTrap.MEVType.MEV_BOT_ACTIVITY].enabled; + responseStatuses[3] = responseConfigs[MEVDetectionTrap.MEVType.SUSPICIOUS_PATTERN].enabled; + + return (emergencyPaused, 0, responseStatuses); + } +} diff --git a/defi-automation/mev-detection-trap/src/SandwichAttackTrap.sol b/defi-automation/mev-detection-trap/src/SandwichAttackTrap.sol new file mode 100644 index 0000000..4cf6750 --- /dev/null +++ b/defi-automation/mev-detection-trap/src/SandwichAttackTrap.sol @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ITrap} from "./interfaces/ITrap.sol"; + +/** + * @title SandwichAttackTrap + * @notice Specialized trap for detecting sandwich attacks with advanced pattern recognition + * @dev Monitors transaction mempools and block patterns to identify sandwich attacks + */ +contract SandwichAttackTrap is ITrap { + + uint256 constant MIN_PRICE_IMPACT = 0.1e18; // 10% minimum price impact + uint256 constant MAX_TIME_BETWEEN_TXS = 3; // 3 seconds max between transactions + uint256 constant MIN_VOLUME_RATIO = 5; // Middle trade must be 5x larger than surrounding + uint256 constant SUSPICIOUS_GAS_MULTIPLIER = 2; // Gas price must be 2x higher than average + + struct SandwichData { + uint256 blockNumber; + uint256 timestamp; + address targetAddress; + uint256[] transactionVolumes; + uint256[] priceImpacts; + uint256[] gasPrices; + address[] transactionSenders; + uint256 estimatedProfit; + bool isSandwichAttack; + } + + struct SandwichAlert { + uint256 blockNumber; + uint256 timestamp; + address targetAddress; + address[] attackers; + uint256 estimatedProfit; + uint256 priceImpact; + uint256 gasUsed; + } + + address[] public monitoredAddresses; + mapping(address => bool) public isMonitored; + + struct HistoricalData { + uint256[] volumes; + uint256[] prices; + uint256[] gasPrices; + uint256[] timestamps; + uint256 index; + uint256 maxEntries; + } + + mapping(address => HistoricalData) public historicalData; + + constructor() { + // Initialize with popular DEX pairs + _addMonitoredAddress(0xB4e16d0168e52d35CaCD2c6185b44281eC28C9Dc); // Example pair + _addMonitoredAddress(0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852); // Example pair + _addMonitoredAddress(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); // Example pair + } + + function collect() external view override returns (bytes memory) { + SandwichData[] memory sandwichDataArray = new SandwichData[](monitoredAddresses.length); + + for (uint256 i = 0; i < monitoredAddresses.length; i++) { + address target = monitoredAddresses[i]; + if (!isMonitored[target]) continue; + + sandwichDataArray[i] = _collectAddressData(target); + } + + return abi.encode(sandwichDataArray); + } + + function shouldRespond( + bytes[] calldata data + ) external pure override returns (bool shouldTrigger, bytes memory responseData) { + if (data.length < 3) { + return (false, ""); + } + + SandwichData[][] memory allData = new SandwichData[][](data.length); + for (uint256 i = 0; i < data.length; i++) { + allData[i] = abi.decode(data[i], (SandwichData[])); + } + + SandwichAlert[] memory alerts = _analyzeSandwichPatterns(allData); + + if (alerts.length > 0) { + return (true, abi.encode(alerts)); + } + + return (false, ""); + } + + function _collectAddressData(address target) internal view returns (SandwichData memory) { + HistoricalData memory hist = historicalData[target]; + + SandwichData memory data = SandwichData({ + blockNumber: block.number, + timestamp: block.timestamp, + targetAddress: target, + transactionVolumes: new uint256[](0), + priceImpacts: new uint256[](0), + gasPrices: new uint256[](0), + transactionSenders: new address[](0), + estimatedProfit: 0, + isSandwichAttack: false + }); + + // In a real implementation, this would collect data from: + // 1. Mempool transactions + // 2. Recent block transactions + // 3. Price feeds + // 4. Volume data + + // For now, return mock data structure + return data; + } + + function _analyzeSandwichPatterns( + SandwichData[][] memory allData + ) internal pure returns (SandwichAlert[] memory) { + SandwichAlert[] memory alerts = new SandwichAlert[](100); // Max 100 alerts + uint256 alertCount = 0; + + for (uint256 i = 0; i < allData.length; i++) { + for (uint256 j = 0; j < allData[i].length; j++) { + SandwichData memory data = allData[i][j]; + + if (_isSandwichAttack(data)) { + alerts[alertCount++] = _createSandwichAlert(data); + + if (alertCount >= 100) break; + } + } + } + + // Resize array to actual alert count + SandwichAlert[] memory finalAlerts = new SandwichAlert[](alertCount); + for (uint256 i = 0; i < alertCount; i++) { + finalAlerts[i] = alerts[i]; + } + + return finalAlerts; + } + + function _isSandwichAttack(SandwichData memory data) internal pure returns (bool) { + if (data.transactionVolumes.length < 3) return false; + + uint256 frontRunVolume = data.transactionVolumes[0]; + uint256 victimVolume = data.transactionVolumes[1]; + uint256 backRunVolume = data.transactionVolumes[2]; + + if (victimVolume < frontRunVolume * MIN_VOLUME_RATIO || + victimVolume < backRunVolume * MIN_VOLUME_RATIO) { + return false; + } + + uint256 totalPriceImpact = 0; + for (uint256 i = 0; i < data.priceImpacts.length; i++) { + totalPriceImpact += data.priceImpacts[i]; + } + + if (totalPriceImpact < MIN_PRICE_IMPACT) { + return false; + } + + if (data.transactionVolumes.length >= 3) { + // This would check actual transaction timestamps in real implementation + return true; + } + + return false; + } + + function _createSandwichAlert( + SandwichData memory data + ) internal pure returns (SandwichAlert memory) { + address[] memory attackers = new address[](2); + attackers[0] = address(0); // Front-run attacker + attackers[1] = address(0); // Back-run attacker + + uint256 totalPriceImpact = 0; + for (uint256 i = 0; i < data.priceImpacts.length; i++) { + totalPriceImpact += data.priceImpacts[i]; + } + + uint256 totalGas = 0; + for (uint256 i = 0; i < data.gasPrices.length; i++) { + totalGas += data.gasPrices[i]; + } + + return SandwichAlert({ + blockNumber: data.blockNumber, + timestamp: data.timestamp, + targetAddress: data.targetAddress, + attackers: attackers, + estimatedProfit: data.estimatedProfit, + priceImpact: totalPriceImpact, + gasUsed: totalGas + }); + } + + + function _detectGasPriceSpikes( + uint256[] memory gasPrices + ) internal pure returns (bool) { + if (gasPrices.length < 2) return false; + + uint256 avgGas = 0; + for (uint256 i = 0; i < gasPrices.length; i++) { + avgGas += gasPrices[i]; + } + avgGas = avgGas / gasPrices.length; + + for (uint256 i = 0; i < gasPrices.length; i++) { + if (gasPrices[i] > avgGas * SUSPICIOUS_GAS_MULTIPLIER) { + return true; + } + } + + return false; + } + + function _detectVolumeAnomalies( + uint256[] memory volumes + ) internal pure returns (bool) { + if (volumes.length < 3) return false; + + uint256 avgVolume = 0; + for (uint256 i = 0; i < volumes.length; i++) { + avgVolume += volumes[i]; + } + avgVolume = avgVolume / volumes.length; + + uint256 anomalyCount = 0; + for (uint256 i = 0; i < volumes.length; i++) { + if (volumes[i] > avgVolume * 3 || volumes[i] < avgVolume / 3) { + anomalyCount++; + } + } + + // If more than 50% of volumes are suspicious, flag it + return anomalyCount > volumes.length / 2; + } + + function _detectTimingPatterns( + uint256[] memory timestamps + ) internal pure returns (bool) { + if (timestamps.length < 2) return false; + + for (uint256 i = 1; i < timestamps.length; i++) { + if (timestamps[i] - timestamps[i-1] < MAX_TIME_BETWEEN_TXS) { + return true; + } + } + + return false; + } + + // Admin functions + + function addMonitoredAddress(address target) external { + if (!isMonitored[target]) { + monitoredAddresses.push(target); + isMonitored[target] = true; + + historicalData[target] = HistoricalData({ + volumes: new uint256[](100), + prices: new uint256[](100), + gasPrices: new uint256[](100), + timestamps: new uint256[](100), + index: 0, + maxEntries: 100 + }); + } + } + + function removeMonitoredAddress(address target) external { + if (isMonitored[target]) { + isMonitored[target] = false; + } + } + + function updateHistoricalData( + address target, + uint256 volume, + uint256 price, + uint256 gasPrice + ) external { + if (!isMonitored[target]) return; + + HistoricalData storage hist = historicalData[target]; + + hist.volumes[hist.index] = volume; + hist.prices[hist.index] = price; + hist.gasPrices[hist.index] = gasPrice; + hist.timestamps[hist.index] = block.timestamp; + + hist.index = (hist.index + 1) % hist.maxEntries; + } + + + function getMonitoredAddresses() external view returns (address[] memory) { + return monitoredAddresses; + } + + function getHistoricalData( + address target + ) external view returns (HistoricalData memory) { + return historicalData[target]; + } + + function getThresholds() external pure returns ( + uint256 minPriceImpact, + uint256 maxTimeBetweenTxs, + uint256 minVolumeRatio, + uint256 suspiciousGasMultiplier + ) { + return ( + MIN_PRICE_IMPACT, + MAX_TIME_BETWEEN_TXS, + MIN_VOLUME_RATIO, + SUSPICIOUS_GAS_MULTIPLIER + ); + } + + function estimateSandwichProfit( + uint256[] memory volumes, + uint256[] memory priceImpacts + ) external pure returns (uint256) { + if (volumes.length != priceImpacts.length || volumes.length < 3) { + return 0; + } + + uint256 frontRunProfit = (volumes[0] * priceImpacts[0]) / 1e18; + uint256 backRunProfit = (volumes[2] * priceImpacts[2]) / 1e18; + + return frontRunProfit + backRunProfit; + } + + function _addMonitoredAddress(address target) internal { + if (!isMonitored[target]) { + monitoredAddresses.push(target); + isMonitored[target] = true; + + historicalData[target] = HistoricalData({ + volumes: new uint256[](100), + prices: new uint256[](100), + gasPrices: new uint256[](100), + timestamps: new uint256[](100), + index: 0, + maxEntries: 100 + }); + } + } +} diff --git a/defi-automation/mev-detection-trap/src/interfaces/ITrap.sol b/defi-automation/mev-detection-trap/src/interfaces/ITrap.sol new file mode 100644 index 0000000..dc578b0 --- /dev/null +++ b/defi-automation/mev-detection-trap/src/interfaces/ITrap.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/** + * @title ITrap + * @notice Interface for Drosera Traps + * @dev This is a minimal interface for the MEV detection traps + */ +interface ITrap { + /** + * @notice Collect data for analysis + * @return Data collected from the current state + */ + function collect() external view returns (bytes memory); + + /** + * @notice Determine if the trap should respond based on collected data + * @param data Array of collected data from previous blocks + * @return shouldTrigger Whether the trap should trigger a response + * @return responseData Data to pass to the response contract + */ + function shouldRespond(bytes[] calldata data) external pure returns (bool shouldTrigger, bytes memory responseData); +} diff --git a/defi-automation/mev-detection-trap/test/MEVDetectionTrap.t.sol b/defi-automation/mev-detection-trap/test/MEVDetectionTrap.t.sol new file mode 100644 index 0000000..90a1f86 --- /dev/null +++ b/defi-automation/mev-detection-trap/test/MEVDetectionTrap.t.sol @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Test, console2} from "forge-std/Test.sol"; +import {MEVDetectionTrap} from "../src/MEVDetectionTrap.sol"; +import {SandwichAttackTrap} from "../src/SandwichAttackTrap.sol"; +import {MEVResponse} from "../src/MEVResponse.sol"; + +contract MEVDetectionTrapTest is Test { + MEVDetectionTrap public mevTrap; + SandwichAttackTrap public sandwichTrap; + MEVResponse public mevResponse; + + address public user1 = address(0x1); + address public user2 = address(0x2); + address public mevBot = address(0x3); + address public dex1 = address(0x4); + address public dex2 = address(0x5); + + function setUp() public { + mevTrap = new MEVDetectionTrap(); + sandwichTrap = new SandwichAttackTrap(); + mevResponse = new MEVResponse(address(0x123)); // Mock trap config + + // Fund test addresses + vm.deal(user1, 100 ether); + vm.deal(user2, 100 ether); + vm.deal(mevBot, 100 ether); + } + + // Test MEVDetectionTrap + + function testConstructor() public { + address[] memory dexes = mevTrap.getMonitoredDEXs(); + assertEq(dexes.length, 3); // Should have 3 DEXs initialized + + address[] memory bots = mevTrap.getKnownMEVBots(); + assertEq(bots.length, 1); // Should have 1 bot initialized + } + + function testCollectFunction() public { + bytes memory data = mevTrap.collect(); + assertGt(data.length, 0, "Collect should return data"); + + MEVDetectionTrap.MEVData memory mevData = abi.decode(data, (MEVDetectionTrap.MEVData)); + assertEq(mevData.blockNumber, block.number); + assertEq(mevData.dexAddresses.length, 3); + } + + function testShouldRespondWithNoData() public { + bytes[] memory emptyData = new bytes[](0); + (bool shouldTrigger, bytes memory responseData) = mevTrap.shouldRespond(emptyData); + + assertFalse(shouldTrigger, "Should not trigger with no data"); + assertEq(responseData.length, 0, "Response data should be empty"); + } + + function testShouldRespondWithInsufficientData() public { + bytes[] memory insufficientData = new bytes[](1); + insufficientData[0] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number, + timestamp: block.timestamp, + dexAddresses: new address[](1), + prices: new uint256[](1), + volumes: new uint256[](1), + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + (bool shouldTrigger, bytes memory responseData) = mevTrap.shouldRespond(insufficientData); + + assertFalse(shouldTrigger, "Should not trigger with insufficient data"); + assertEq(responseData.length, 0, "Response data should be empty"); + } + + function testSandwichAttackDetection() public { + // Create mock data that simulates a sandwich attack + bytes[] memory sandwichData = new bytes[](3); + + // Block 1: Small trade + sandwichData[0] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number - 2, + timestamp: block.timestamp - 2, + dexAddresses: new address[](1), + prices: _toArray(1000e18), + volumes: _toArray(100e18), // Small volume + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + // Block 2: Large trade (victim) + sandwichData[1] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number - 1, + timestamp: block.timestamp - 1, + dexAddresses: new address[](1), + prices: _toArray(1500e18), // Price impact + volumes: _toArray(1000e18), // Large volume (10x) + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + // Block 3: Small trade + sandwichData[2] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number, + timestamp: block.timestamp, + dexAddresses: new address[](1), + prices: _toArray(1000e18), + volumes: _toArray(100e18), // Small volume + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + (bool shouldTrigger, bytes memory responseData) = mevTrap.shouldRespond(sandwichData); + + assertTrue(shouldTrigger, "Should detect sandwich attack"); + assertGt(responseData.length, 0, "Should return response data"); + + // Decode and verify alert + MEVDetectionTrap.MEVAlert memory alert = abi.decode(responseData, (MEVDetectionTrap.MEVAlert)); + assertEq(uint256(alert.mevType), uint256(MEVDetectionTrap.MEVType.SANDWICH_ATTACK)); + assertGt(alert.estimatedProfit, 0, "Should estimate profit"); + } + + function testArbitrageOpportunityDetection() public { + // Create mock data that simulates arbitrage opportunity + bytes[] memory arbitrageData = new bytes[](2); + + // DEX 1: Higher price + arbitrageData[0] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number - 1, + timestamp: block.timestamp - 1, + dexAddresses: new address[](1), + prices: _toArray(1100e18), // 10% higher + volumes: _toArray(500e18), + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + // DEX 2: Lower price + arbitrageData[1] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number, + timestamp: block.timestamp, + dexAddresses: new address[](1), + prices: _toArray(1000e18), // Base price + volumes: _toArray(500e18), + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + (bool shouldTrigger, bytes memory responseData) = mevTrap.shouldRespond(arbitrageData); + + assertTrue(shouldTrigger, "Should detect arbitrage opportunity"); + assertGt(responseData.length, 0, "Should return response data"); + + // Decode and verify alert + MEVDetectionTrap.MEVAlert memory alert = abi.decode(responseData, (MEVDetectionTrap.MEVAlert)); + assertEq(uint256(alert.mevType), uint256(MEVDetectionTrap.MEVType.ARBITRAGE_OPPORTUNITY)); + } + + function testMEVBotActivityDetection() public { + // Create mock data that simulates MEV bot activity + bytes[] memory mevBotData = new bytes[](2); + + // Block 1: Normal gas + mevBotData[0] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number - 1, + timestamp: block.timestamp - 2, + dexAddresses: new address[](1), + prices: _toArray(1000e18), + volumes: _toArray(500e18), + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + // Block 2: High gas, rapid succession + mevBotData[1] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number, + timestamp: block.timestamp - 1, // 1 second later + dexAddresses: new address[](1), + prices: _toArray(1000e18), + volumes: _toArray(500e18), + gasPrice: 2000 gwei, // 100x higher gas + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + (bool shouldTrigger, bytes memory responseData) = mevTrap.shouldRespond(mevBotData); + + assertTrue(shouldTrigger, "Should detect MEV bot activity"); + assertGt(responseData.length, 0, "Should return response data"); + + // Decode and verify alert + MEVDetectionTrap.MEVAlert memory alert = abi.decode(responseData, (MEVDetectionTrap.MEVAlert)); + assertEq(uint256(alert.mevType), uint256(MEVDetectionTrap.MEVType.MEV_BOT_ACTIVITY)); + } + + function testSuspiciousPatternDetection() public { + // Create mock data that simulates suspicious patterns + bytes[] memory suspiciousData = new bytes[](3); + + // Block 1: Normal volume + suspiciousData[0] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number - 2, + timestamp: block.timestamp - 2, + dexAddresses: new address[](1), + prices: _toArray(1000e18), + volumes: _toArray(100e18), // Normal volume + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + // Block 2: Normal volume + suspiciousData[1] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number - 1, + timestamp: block.timestamp - 1, + dexAddresses: new address[](1), + prices: _toArray(1000e18), + volumes: _toArray(100e18), // Normal volume + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + // Block 3: Extremely high volume (10x normal) + suspiciousData[2] = abi.encode(MEVDetectionTrap.MEVData({ + blockNumber: block.number, + timestamp: block.timestamp, + dexAddresses: new address[](1), + prices: _toArray(1000e18), + volumes: _toArray(1000e18), // 10x normal volume + gasPrice: 20 gwei, + suspiciousAddresses: new address[](0), + mevType: MEVDetectionTrap.MEVType.NONE, + estimatedProfit: 0 + })); + + (bool shouldTrigger, bytes memory responseData) = mevTrap.shouldRespond(suspiciousData); + + assertTrue(shouldTrigger, "Should detect suspicious pattern"); + assertGt(responseData.length, 0, "Should return response data"); + + // Decode and verify alert + MEVDetectionTrap.MEVAlert memory alert = abi.decode(responseData, (MEVDetectionTrap.MEVAlert)); + assertEq(uint256(alert.mevType), uint256(MEVDetectionTrap.MEVType.SUSPICIOUS_PATTERN)); + } + + function testAddRemoveMonitoredDEX() public { + address newDEX = address(0x999); + + // Add new DEX + mevTrap.addMonitoredDEX(newDEX); + address[] memory dexes = mevTrap.getMonitoredDEXs(); + assertEq(dexes.length, 4); // Should now have 4 DEXs + + // Remove DEX (marks as not monitored) + mevTrap.removeMonitoredDEX(newDEX); + // Note: This doesn't remove from array, just marks as not monitored + } + + function testAddRemoveKnownMEVBot() public { + address newBot = address(0x888); + + // Add new bot + mevTrap.addKnownMEVBot(newBot); + address[] memory bots = mevTrap.getKnownMEVBots(); + assertEq(bots.length, 2); // Should now have 2 bots + + // Remove bot + mevTrap.removeKnownMEVBot(newBot); + } + + function testGetThresholds() public { + ( + uint256 sandwichThreshold, + uint256 arbitrageThreshold, + uint256 mevProfitThreshold, + uint256 suspiciousGasThreshold + ) = mevTrap.getThresholds(); + + assertEq(sandwichThreshold, 0.5e18, "Sandwich threshold should be 50%"); + assertEq(arbitrageThreshold, 0.02e18, "Arbitrage threshold should be 2%"); + assertEq(mevProfitThreshold, 0.1e18, "MEV profit threshold should be 0.1 ETH"); + assertEq(suspiciousGasThreshold, 1000, "Suspicious gas threshold should be 1000 gwei"); + } + + // Test SandwichAttackTrap + + function testSandwichAttackTrapConstructor() public { + address[] memory addresses = sandwichTrap.getMonitoredAddresses(); + assertEq(addresses.length, 3); // Should have 3 addresses initialized + } + + function testSandwichAttackTrapCollect() public { + bytes memory data = sandwichTrap.collect(); + assertGt(data.length, 0, "Collect should return data"); + + // Decode and verify data structure + SandwichAttackTrap.SandwichData[] memory sandwichDataArray = abi.decode(data, (SandwichAttackTrap.SandwichData[])); + assertEq(sandwichDataArray.length, 3); + } + + function testSandwichAttackTrapShouldRespond() public { + // Create mock sandwich attack data + bytes[] memory sandwichData = new bytes[](3); + + for (uint256 i = 0; i < 3; i++) { + sandwichData[i] = abi.encode(SandwichAttackTrap.SandwichData({ + blockNumber: block.number - 2 + i, + timestamp: block.timestamp - 2 + i, + targetAddress: address(0x123), + transactionVolumes: _toArray(100e18 + i * 50e18), + priceImpacts: _toArray(0.05e18 + i * 0.02e18), + gasPrices: _toArray(20 gwei + i * 5 gwei), + transactionSenders: new address[](0), + estimatedProfit: 0, + isSandwichAttack: false + })); + } + + (bool shouldTrigger, bytes memory responseData) = sandwichTrap.shouldRespond(sandwichData); + console2.log("Sandwich trap response:", shouldTrigger); + } + + function testSandwichAttackTrapAddRemoveAddress() public { + address newAddress = address(0x777); + + // Add new address + sandwichTrap.addMonitoredAddress(newAddress); + address[] memory addresses = sandwichTrap.getMonitoredAddresses(); + assertEq(addresses.length, 4); + + // Remove address + sandwichTrap.removeMonitoredAddress(newAddress); + } + + function testSandwichAttackTrapUpdateHistoricalData() public { + address target = address(0x123); + sandwichTrap.addMonitoredAddress(target); + + sandwichTrap.updateHistoricalData(target, 1000e18, 1000e18, 20 gwei); + + SandwichAttackTrap.HistoricalData memory hist = sandwichTrap.getHistoricalData(target); + assertEq(hist.volumes[0], 1000e18); + assertEq(hist.prices[0], 1000e18); + assertEq(hist.gasPrices[0], 20 gwei); + } + + function testSandwichAttackTrapGetThresholds() public { + ( + uint256 minPriceImpact, + uint256 maxTimeBetweenTxs, + uint256 minVolumeRatio, + uint256 suspiciousGasMultiplier + ) = sandwichTrap.getThresholds(); + + assertEq(minPriceImpact, 0.1e18, "Min price impact should be 10%"); + assertEq(maxTimeBetweenTxs, 3, "Max time between txs should be 3 seconds"); + assertEq(minVolumeRatio, 5, "Min volume ratio should be 5x"); + assertEq(suspiciousGasMultiplier, 2, "Suspicious gas multiplier should be 2x"); + } + + function testSandwichAttackTrapEstimateProfit() public { + uint256[] memory volumes = new uint256[](3); + volumes[0] = 100e18; // Front-run + volumes[1] = 1000e18; // Victim + volumes[2] = 100e18; // Back-run + + uint256[] memory priceImpacts = new uint256[](3); + priceImpacts[0] = 0.05e18; // 5% impact + priceImpacts[1] = 0.10e18; // 10% impact + priceImpacts[2] = 0.05e18; // 5% impact + + uint256 profit = sandwichTrap.estimateSandwichProfit(volumes, priceImpacts); + assertGt(profit, 0, "Should calculate profit"); + } + + // Test MEVResponse + + function testMEVResponseConstructor() public { + assertEq(mevResponse.trapConfig(), address(0x123)); + assertFalse(mevResponse.emergencyPaused()); + } + + function testMEVResponseGetResponseConfig() public { + MEVResponse.ResponseConfig memory config = mevResponse.getResponseConfig(MEVDetectionTrap.MEVType.SANDWICH_ATTACK); + assertTrue(config.enabled); + assertEq(config.cooldownPeriod, 300); // 5 minutes + assertEq(config.maxExecutionsPerHour, 10); + } + + function testMEVResponseEmergencyPause() public { + // Only emergency pauser can pause + vm.prank(user1); + vm.expectRevert("Only emergency pauser can call this"); + mevResponse.emergencyPause(); + + // Emergency pauser can pause + vm.prank(address(0x123)); // trap config + mevResponse.emergencyPause(); + assertTrue(mevResponse.emergencyPaused()); + + // Emergency pauser can unpause + vm.prank(address(0x123)); + mevResponse.emergencyUnpause(); + assertFalse(mevResponse.emergencyPaused()); + } + + function testMEVResponseGetResponseStatus() public { + ( + bool paused, + uint256 totalProcessedAlerts, + bool[] memory enabledResponses + ) = mevResponse.getResponseStatus(); + + assertFalse(paused); + assertEq(enabledResponses.length, 4); + assertTrue(enabledResponses[0]); // Sandwich attack + assertTrue(enabledResponses[1]); // Arbitrage opportunity + assertTrue(enabledResponses[2]); // MEV bot activity + assertTrue(enabledResponses[3]); // Suspicious pattern + } + + function _toArray(uint256 value) internal pure returns (uint256[] memory) { + uint256[] memory array = new uint256[](1); + array[0] = value; + return array; + } + + function _toArray(address value) internal pure returns (address[] memory) { + address[] memory array = new address[](1); + array[0] = value; + return array; + } +}