Интеграция с Flashbots Protect

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

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

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

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

  • 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

Разработка системы защиты от oracle manipulation

Oracle manipulation — один из самых прибыльных векторов атак в DeFi. Суть: злоумышленник искусственно изменяет цену в источнике данных (oracle), которым пользуется протокол, получает выгоду от некорректной цены, затем цена возвращается в норму. В отличие от классических эксплойтов кода, здесь не обязательно взламывать смарт-контракт — достаточно временно повлиять на данные, которым контракт доверяет.

Mango Markets (октябрь 2022): $114 млн украдено через манипуляцию собственным MNGO токеном в Mango oracle. CREAM Finance: $130 млн через flash loan + manipulation цены yUSD. Это не edge cases — это системный риск при неверной архитектуре price feeds.

Типы oracle и их векторы атак

Spot price из AMM (наихудший вариант)

Использование getReserves() от Uniswap V2 пары как источника цены — прямой путь к flash loan атаке.

// КРИТИЧЕСКИ УЯЗВИМО
function getPrice(address token) public view returns (uint256) {
    (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(pair).getReserves();
    // Spot price = reserve1 / reserve0
    // Flash loan может изменить reserves в 100x за одну транзакцию
    return uint256(reserve1) * 1e18 / uint256(reserve0);
}

Атака занимает одну транзакцию: flash loan → swap искажает reserves → вызов уязвимого протокола → repay flash loan. Никаких следов, никакого риска для атакующего.

TWAP (Time-Weighted Average Price)

TWAP — стандартная защита от flash loan атак. Средняя цена за период времени не может быть значительно изменена одной транзакцией.

// Uniswap V3 TWAP через OracleLibrary
import "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol";

contract TWAPOracle {
    address public immutable pool;        // Uniswap V3 pool
    uint32 public constant TWAP_PERIOD = 30 minutes;
    
    function getTWAP() public view returns (uint256 price) {
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = TWAP_PERIOD;
        secondsAgos[1] = 0;
        
        (int56[] memory tickCumulatives, ) = IUniswapV3Pool(pool).observe(secondsAgos);
        
        int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
        int24 arithmeticMeanTick = int24(tickCumulativesDelta / int56(uint56(TWAP_PERIOD)));
        
        // Конвертируем tick в цену
        uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
        price = FullMath.mulDiv(
            uint256(sqrtRatioX96) * uint256(sqrtRatioX96),
            1e18,
            2 ** 192
        );
    }
}

Ограничения TWAP: 30-минутный TWAP можно сдвинуть постепенной манипуляцией в течение 30 минут. Для токенов с малой ликвидностью — стоимость атаки снижается. TWAP не подходит как единственная защита для volatile assets или low-liquidity pools.

Chainlink Price Feeds

Chainlink — децентрализованная сеть оракулов. Цена агрегируется от десятков независимых node operators, обновляется при отклонении более чем на deviation threshold (обычно 0.5% или 1%) или по heartbeat (раз в 24 часа).

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract ChainlinkOracleConsumer {
    AggregatorV3Interface public immutable priceFeed;
    
    // Максимальное время с последнего обновления
    uint256 public constant STALENESS_THRESHOLD = 3600; // 1 час для большинства feeds
    
    constructor(address _priceFeed) {
        priceFeed = AggregatorV3Interface(_priceFeed);
    }
    
    function getPrice() public view returns (uint256) {
        (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        
        // Проверка staleness — цена не устарела?
        require(
            block.timestamp - updatedAt <= STALENESS_THRESHOLD,
            "Oracle: stale price"
        );
        
        // Проверка round completeness
        require(answeredInRound >= roundId, "Oracle: incomplete round");
        
        // Проверка на отрицательную цену (не должно быть, но проверяем)
        require(answer > 0, "Oracle: invalid price");
        
        // Нормализуем до 18 decimals
        uint8 decimals = priceFeed.decimals();
        uint256 normalizedPrice = uint256(answer);
        if (decimals < 18) {
            normalizedPrice = normalizedPrice * 10 ** (18 - decimals);
        } else if (decimals > 18) {
            normalizedPrice = normalizedPrice / 10 ** (decimals - 18);
        }
        
        return normalizedPrice;
    }
}

Типичные ошибки при использовании Chainlink:

  • Не проверять updatedAt — принять устаревшую цену во время downtime Chainlink
  • Не проверять answeredInRound >= roundId — принять цену из неполного раунда
  • Hardcode STALENESS_THRESHOLD без учёта конкретного feed (heartbeat у ETH/USD = 1 час, у экзотических = 24 часа)

Multi-oracle система: медианная агрегация

Лучшая защита — несколько независимых источников с агрегацией через медиану. Манипуляция одного источника не влияет на медиану из трёх.

contract MultiOracleAggregator {
    struct OracleConfig {
        address oracle;
        uint256 stalenessThreshold;
        uint256 weight;     // вес при взвешенном среднем (если нужно)
        bool active;
    }
    
    OracleConfig[] public oracles;
    
    // Максимальное допустимое отклонение между оракулами (basis points)
    uint256 public constant MAX_DEVIATION = 500;  // 5%
    
    function getPrice() external view returns (uint256 price, bool isValid) {
        uint256[] memory prices = new uint256[](oracles.length);
        uint256 validCount = 0;
        
        for (uint256 i = 0; i < oracles.length; i++) {
            if (!oracles[i].active) continue;
            
            try IOracle(oracles[i].oracle).getPrice() returns (uint256 p, bool valid) {
                if (valid && p > 0) {
                    prices[validCount++] = p;
                }
            } catch {
                // Oracle недоступен — продолжаем с остальными
            }
        }
        
        // Нужно минимум 2 валидных источника
        require(validCount >= 2, "Insufficient oracle responses");
        
        // Сортируем и берём медиану
        uint256 median = _getMedian(prices, validCount);
        
        // Проверяем что все цены в пределах MAX_DEVIATION от медианы
        bool allWithinDeviation = true;
        for (uint256 i = 0; i < validCount; i++) {
            uint256 deviation = _absDiff(prices[i], median) * 10000 / median;
            if (deviation > MAX_DEVIATION) {
                allWithinDeviation = false;
                break;
            }
        }
        
        return (median, allWithinDeviation);
    }
    
    function _getMedian(uint256[] memory arr, uint256 length) internal pure returns (uint256) {
        // Insertion sort для небольших массивов (обычно 3-5 оракулов)
        for (uint256 i = 1; i < length; i++) {
            uint256 key = arr[i];
            int256 j = int256(i) - 1;
            while (j >= 0 && arr[uint256(j)] > key) {
                arr[uint256(j + 1)] = arr[uint256(j)];
                j--;
            }
            arr[uint256(j + 1)] = key;
        }
        
        if (length % 2 == 1) {
            return arr[length / 2];
        } else {
            return (arr[length / 2 - 1] + arr[length / 2]) / 2;
        }
    }
}

Circuit breaker при аномальных ценах

При резком отклонении цены — автоматическая пауза протокола:

contract PriceCircuitBreaker {
    uint256 public lastValidPrice;
    uint256 public lastUpdateTime;
    
    // Максимальное изменение цены за один update
    uint256 public constant MAX_PRICE_CHANGE_BPS = 1000;  // 10% за update
    
    // Максимальное изменение за 1 час
    uint256 public constant MAX_HOURLY_CHANGE_BPS = 2000;  // 20% за час
    
    bool public circuitBreakerTripped;
    
    struct PriceHistory {
        uint256 price;
        uint256 timestamp;
    }
    
    PriceHistory[10] public priceHistory;  // Ring buffer последних 10 цен
    uint256 public historyIndex;
    
    function updatePrice(uint256 newPrice) external onlyOracle {
        require(!circuitBreakerTripped, "Circuit breaker active");
        
        if (lastValidPrice > 0) {
            uint256 change = _absDiff(newPrice, lastValidPrice) * 10000 / lastValidPrice;
            
            // Резкое изменение за один update
            if (change > MAX_PRICE_CHANGE_BPS) {
                circuitBreakerTripped = true;
                emit CircuitBreakerTripped(lastValidPrice, newPrice, change);
                return;
            }
            
            // Изменение за час
            uint256 hourlyChange = _calculateHourlyChange(newPrice);
            if (hourlyChange > MAX_HOURLY_CHANGE_BPS) {
                circuitBreakerTripped = true;
                emit CircuitBreakerTripped(lastValidPrice, newPrice, hourlyChange);
                return;
            }
        }
        
        // Сохраняем в историю
        priceHistory[historyIndex % 10] = PriceHistory(newPrice, block.timestamp);
        historyIndex++;
        
        lastValidPrice = newPrice;
        lastUpdateTime = block.timestamp;
        
        emit PriceUpdated(newPrice, block.timestamp);
    }
    
    function resetCircuitBreaker() external onlyGovernance {
        // Сброс только через governance после анализа причины
        circuitBreakerTripped = false;
        emit CircuitBreakerReset(msg.sender);
    }
}

Уязвимость к flash loan: детекция и защита

Read-only reentrancy

Специфическая атака для Balancer/Curve стиля пулов: злоумышленник использует flash loan callback чтобы вызвать другой протокол пока балансы уже изменены но state protection (reentrancy guard) ещё активен в оригинальном пуле.

// Balancer V2 re-entrancy lock check
interface IVault {
    function getReserves() external view returns (uint256, uint256);
    // Vault locked → getReserves() вернёт некорректные данные во время callback
}

contract BalancerOracleConsumer {
    IVault public immutable balancerVault;
    
    modifier ensureNotLocked() {
        // Проверяем что Balancer vault не заблокирован (не в flash loan)
        // Пытаемся вызвать operationKind — если vault locked, revertnет
        (, bytes memory data) = address(balancerVault).staticcall(
            abi.encodeWithSignature("getAuthorizer()")
        );
        require(data.length > 0, "Balancer vault locked");
        _;
    }
    
    function getBalancerPrice() external view ensureNotLocked returns (uint256) {
        // Безопасный вызов только когда vault не в flash loan
        (uint256 reserve0, uint256 reserve1) = balancerVault.getReserves();
        return reserve1 * 1e18 / reserve0;
    }
}

Flash loan detection на уровне транзакции

contract FlashLoanAwareProtocol {
    // Mapping от block.number → true если в этом блоке был flash loan
    // через известные flash loan провайдеры
    mapping(uint256 => bool) private _flashLoanInBlock;
    
    // Callback от flash loan провайдеров (AAVE, Uniswap, Balancer)
    // Мы получаем уведомление если flash loan используется в нашем протоколе
    function markFlashLoanBlock() external onlyFlashLoanProvider {
        _flashLoanInBlock[block.number] = true;
    }
    
    modifier noFlashLoan() {
        require(!_flashLoanInBlock[block.number], "Flash loan detected in block");
        _;
    }
    
    // Ценочувствительные операции только когда нет flash loan в блоке
    function borrow(address asset, uint256 amount) external noFlashLoan {
        uint256 price = oracle.getPrice(asset);
        // ...
    }
}

Pyth Network и push oracle модель

Chainlink использует push модель: данные обновляются автоматически при deviation или heartbeat. Pyth использует pull модель: пользователь сам обновляет цену перед транзакцией, предоставляя signed price attestation от сети.

import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";

contract PythOracleConsumer {
    IPyth public immutable pyth;
    bytes32 public immutable priceId;
    
    // Максимальное время жизни цены (60 секунд для большинства assets)
    uint256 public constant PRICE_MAX_AGE = 60;
    
    // updatePriceFeeds вызывается в той же транзакции что и операция
    function borrowWithPythPrice(
        uint256 borrowAmount,
        bytes[] calldata priceUpdateData  // данные от Pyth
    ) external payable {
        // Обновляем цену (пользователь платит fee ~$0.001)
        uint256 updateFee = pyth.getUpdateFee(priceUpdateData);
        pyth.updatePriceFeeds{value: updateFee}(priceUpdateData);
        
        // Получаем обновлённую цену
        PythStructs.Price memory price = pyth.getPriceNoOlderThan(priceId, PRICE_MAX_AGE);
        
        require(price.price > 0, "Invalid price");
        require(price.conf < uint64(price.price) / 10, "Price confidence too low");
        
        uint256 normalizedPrice = _normalizePrice(price.price, price.expo);
        
        _executeBorrow(msg.sender, borrowAmount, normalizedPrice);
    }
    
    function _normalizePrice(int64 price, int32 expo) internal pure returns (uint256) {
        if (expo >= 0) {
            return uint256(uint64(price)) * 10 ** uint32(expo);
        } else {
            return uint256(uint64(price)) / 10 ** uint32(-expo);
        }
    }
}

Confidence interval в Pyth — важный параметр: conf показывает неопределённость цены. Если conf / price > 10% — данные ненадёжны и операцию лучше отклонить.

Мониторинг oracle аномалий

interface PriceAnomaly {
  asset: string;
  currentPrice: bigint;
  historicalMedian: bigint;
  deviationPct: number;
  timestamp: number;
  oracleSource: string;
}

class OracleMonitor {
  private priceHistory: Map<string, bigint[]> = new Map();
  
  async detectAnomalies(
    assets: string[],
    oracleAddresses: string[]
  ): Promise<PriceAnomaly[]> {
    const anomalies: PriceAnomaly[] = [];
    
    for (let i = 0; i < assets.length; i++) {
      const currentPrice = await this.getCurrentPrice(oracleAddresses[i]);
      const history = this.priceHistory.get(assets[i]) ?? [];
      
      if (history.length >= 10) {
        // Медиана последних 10 цен
        const sorted = [...history].sort((a, b) => (a > b ? 1 : -1));
        const median = sorted[Math.floor(sorted.length / 2)];
        
        const deviation = Math.abs(
          Number((currentPrice - median) * 10000n / median)
        ) / 100;
        
        if (deviation > 15) {  // 15% отклонение от медианы
          anomalies.push({
            asset: assets[i],
            currentPrice,
            historicalMedian: median,
            deviationPct: deviation,
            timestamp: Date.now(),
            oracleSource: oracleAddresses[i]
          });
          
          // Отправляем алерт немедленно
          await this.sendAlert(anomalies[anomalies.length - 1]);
        }
      }
      
      // Обновляем историю
      history.push(currentPrice);
      if (history.length > 100) history.shift();
      this.priceHistory.set(assets[i], history);
    }
    
    return anomalies;
  }
}

Рекомендации по выбору oracle стратегии

Сценарий Рекомендуемое решение
Major assets (ETH, BTC, stablecoins) Chainlink + TWAP как fallback
Long-tail DeFi токены Uniswap V3 TWAP 30min + Circuit breaker
Cross-chain приложения Pyth (быстрое обновление) + Chainlink verification
High-frequency протоколы Pyth pull model (актуальная цена в каждой tx)
Low-liquidity assets Multi-oracle с высоким deviation threshold

Этапы разработки

Фаза Содержание Срок
Аудит текущих oracle Анализ уязвимостей существующей интеграции 1–2 нед
Дизайн multi-oracle Выбор источников, веса, агрегация логика 1–2 нед
Смарт-контракты Aggregator, circuit breaker, anomaly detection 3–4 нед
Интеграция Chainlink, Pyth, TWAP подключение 2–3 нед
Мониторинг система Off-chain alerting, dashboard 2–3 нед
Тестирование Fork tests с симуляцией атак, fuzz 2–3 нед
Аудит 2–3 нед