Разработка системы детекции MEV-атак

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка системы детекции MEV-атак
Сложный
~1-2 недели
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    920
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1147
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    885

Разработка системы детекции MEV-атак

MEV (Maximal Extractable Value) — это стоимость, которую валидаторы и searcher-ы извлекают, переупорядочивая транзакции внутри блока. Технически это не всегда «атака» — часть MEV безвредна (арбитраж между DEX восстанавливает ценовое равновесие). Но sandwich attacks, liquidation front-running и time-bandit атаки наносят прямой ущерб пользователям и протоколам. Система детекции MEV решает две задачи: постфактум анализ (что произошло с пользователем) и проактивная защита (предотвращение конкретных типов атак).

Типология MEV атак

Sandwich attack

Классика и наиболее распространённый тип. Схема:

  1. Жертва отправляет swap на DEX с large slippage tolerance
  2. Атакующий видит транзакцию в mempool
  3. Вставляет buy ПЕРЕД жертвой (front-run), поднимая цену
  4. Жертва исполняется по более высокой цене
  5. Атакующий продаёт ПОСЛЕ жертвы (back-run), фиксируя прибыль

Идентификатор в блоке: три транзакции подряд в одном блоке, где адреса и пары образуют характерный паттерн.

Liquidation front-running

Позиция на lending протоколе (Aave, Compound) становится ликвидируемой. Несколько ботов видят это и гонятся за liquidation reward. Первый выиграл — остальные потратили gas впустую. Это не атака на пользователя, но создаёт waste и MEV гонку.

Более агрессивная версия: атакующий манипулирует ценовым оракулом (через flash loan в пуле, используемом как oracle), чтобы принудительно вывести позицию под liquidation.

Arbitrage MEV

Технически не атака: если цена ETH на Uniswap v3 отличается от Curve на 0.3% — арбитражный бот выравнивает цену, получая прибыль. Это полезно для рынка. Тем не менее для протоколов важно понимать арбитражные потоки.

JIT (Just-in-Time) liquidity

Специфично для Uniswap v3. Атакующий видит крупный swap в mempool, добавляет concentrated liquidity точно в нужный range перед swap, получает fees, убирает ликвидность после. Честные LP теряют fees. Это спорная форма MEV — технически разрешённая протоколом.

Time-bandit attack

Реорг атака: если MEV в нескольких блоках назад превышает стоимость реорганизации цепи — атакующий валидатор может попытаться переписать историю. На Ethereum post-Merge это крайне сложно и дорого, но остаётся теоретической угрозой для PoS цепей с малым stake.

Детекция sandwich attack on-chain

Алгоритм идентификации

Sandwich — три транзакции в одном блоке с определёнными связями:

from dataclasses import dataclass
from typing import Optional
import pandas as pd

@dataclass
class SwapEvent:
    tx_hash: str
    block_number: int
    tx_index: int  # позиция в блоке
    sender: str
    token_in: str
    token_out: str
    amount_in: int
    amount_out: int
    pool: str

def detect_sandwiches(swaps_in_block: list[SwapEvent]) -> list[dict]:
    sandwiches = []

    # Группируем по пулу
    by_pool = {}
    for swap in swaps_in_block:
        by_pool.setdefault(swap.pool, []).append(swap)

    for pool, pool_swaps in by_pool.items():
        # Сортируем по позиции в блоке
        pool_swaps.sort(key=lambda s: s.tx_index)

        # Ищем паттерн: A buys → victim buys → A sells (одинаковый token_in/out)
        for i, front in enumerate(pool_swaps[:-2]):
            victim = pool_swaps[i + 1]
            back = pool_swaps[i + 2]

            is_sandwich = (
                front.sender == back.sender and           # один атакующий
                front.sender != victim.sender and          # жертва другая
                front.token_in == victim.token_in and      # одно направление
                back.token_in == front.token_out and       # разворот
                back.token_out == front.token_in
            )

            if is_sandwich:
                # Считаем profit атакующего
                attacker_profit = (
                    back.amount_out - front.amount_in
                )
                # Считаем ущерб жертвы: разница в execution price
                victim_price = victim.amount_out / victim.amount_in
                # fair_price нужно считать без front-run транзакции (simulation)

                sandwiches.append({
                    'front_tx': front.tx_hash,
                    'victim_tx': victim.tx_hash,
                    'back_tx': back.tx_hash,
                    'attacker': front.sender,
                    'pool': pool,
                    'attacker_profit_tokens': attacker_profit,
                    'block': front.block_number,
                })

    return sandwiches

Измерение ущерба жертвы

Для точного расчёта ущерба нужно симулировать как выполнилась бы транзакция жертвы без front-run транзакции. Это требует доступа к archive node и replay с state override:

async def simulate_swap_without_frontrun(
    victim_tx: dict,
    frontrun_tx: dict,
    block_number: int,
    rpc_url: str
) -> dict:
    """
    Симулирует victim_tx на состоянии блока ДО frontrun_tx
    """
    w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(rpc_url))

    # Получаем состояние на предыдущем блоке
    prev_block_state = block_number - 1

    # eth_call с block_number = prev_block (без frontrun)
    result = await w3.eth.call(
        {
            'from': victim_tx['from'],
            'to': victim_tx['to'],
            'data': victim_tx['input'],
            'gas': victim_tx['gas'],
        },
        prev_block_state
    )

    return {'simulated_output': result}

Разница между реальным amount_out и симулированным — это MEV извлечённое у жертвы.

Детекция через mempool мониторинг

Flashbots MEV-Share и MEV-Boost данные

Flashbots публикует MEV-Boost данные через API: https://boost-relay.flashbots.net/relay/v1/data/bidtraces/proposer_payload_delivered. Это исторические данные о блоках, прошедших через MEV-Boost, с информацией о MEV-bid.

Более детальные данные — через Flashbots MEV-Share API: информация о bundle содержимом, которую builder'ы публикуют post-execution.

Собственный mempool наблюдатель

Для реального времени: подключение к mempool через eth_subscribe("pendingTransactions"):

import { createPublicClient, webSocket, parseAbi, decodeEventLog } from 'viem';

const client = createPublicClient({
    transport: webSocket('wss://mainnet.infura.io/ws/v3/KEY'),
});

// Подписка на pending транзакции
const unwatch = client.watchPendingTransactions({
    onTransactions: async (hashes) => {
        for (const hash of hashes) {
            const tx = await client.getTransaction({ hash });
            if (tx && isLargeSwap(tx)) {
                // Это транзакция с большим slippage - возможная жертва
                await analyzeMevRisk(tx);
            }
        }
    },
});

Проблема: большинство MEV ботов сейчас используют private mempools (Flashbots bundles, MEV Blocker). Публичный mempool видит только часть картины.

Защитные механизмы для протоколов

Commit-reveal для чувствительных операций

Пользователь сначала публикует хэш транзакции (commit), потом reveal. Атакующий не знает параметры до reveal:

contract CommitRevealSwap {
    mapping(bytes32 => uint256) public commits;
    uint256 public constant REVEAL_DELAY = 2; // блоков

    function commit(bytes32 commitment) external {
        commits[commitment] = block.number;
    }

    function reveal(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minAmountOut,
        bytes32 salt
    ) external {
        bytes32 commitment = keccak256(
            abi.encodePacked(msg.sender, tokenIn, tokenOut, amountIn, minAmountOut, salt)
        );
        require(commits[commitment] != 0, "No commit");
        require(block.number >= commits[commitment] + REVEAL_DELAY, "Too early");
        delete commits[commitment];
        // исполнить swap
    }
}

Tight slippage + deadline

Пользователи с minAmountOut = 0 или очень большим slippage — лёгкие жертвы. Протокол должен:

  • Рассчитывать рекомендуемый slippage на основе pool depth
  • Предупреждать о high slippage в UI
  • Короткий deadline (не больше 10-15 минут) для предотвращения stale транзакций

MEV Blocker интеграция

Для frontend: routing транзакций через MEV Blocker (CoW Protocol) или Flashbots Protect вместо публичного mempool. Транзакции видят только доверенные builders, не публичные searcher-ы.

// Отправка через Flashbots Protect RPC
const provider = new ethers.JsonRpcProvider(
    'https://rpc.flashbots.net',
    1 // mainnet
);

Это не требует изменений в смарт-контрактах — только смена RPC endpoint.

TWAP oracle вместо spot price

Для протоколов, использующих цены из AMM: Uniswap v3 TWAP (Time-Weighted Average Price) устойчив к flash loan манипуляциям — нельзя сдвинуть TWAP за один блок.

function getTWAP(address pool, uint32 secondsAgo) public view returns (uint256 price) {
    uint32[] memory secondsAgos = new uint32[](2);
    secondsAgos[0] = secondsAgo;
    secondsAgos[1] = 0;

    (int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);
    int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
    int24 arithmeticMeanTick = int24(tickCumulativesDelta / int56(uint56(secondsAgo)));

    price = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
}

Аналитическая часть: мониторинг и reporting

MEV dashboard: визуализация исторических MEV событий по протоколу. Отвечает на вопросы: сколько пользователей пострадали от sandwich за последний месяц? Какие пулы наиболее атакуемы? Какой средний ущерб на событие?

Eigenφhi, Flashbots MEV-Explore, EigenPhi — публичные инструменты для исторического анализа. Для собственного мониторинга — The Graph subgraph + Dune Analytics.

Тип атаки Метод детекции Сложность Точность
Sandwich Паттерн в блоке Средняя Высокая
JIT liquidity LP + swap timing Средняя Высокая
Oracle manipulation Price deviation + flash loan Высокая Средняя
Liquidation front-run Race transactions Низкая Высокая
Time-bandit Reorg detection Очень высокая Низкая

Стек и сроки

Backend: Python (detection engine) + TypeScript (real-time mempool). Archive node доступ (Erigon self-hosted или Alchemy archive). PostgreSQL для хранения событий. Dune Analytics интеграция для визуализации.

Разработка full-stack MEV detection системы: 10-16 недель. Только post-hoc on-chain анализ sandwich attacks: 4-6 недель. Интеграция Flashbots Protect в frontend: 1-2 дня.