Інтеграція з 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

Розробка системи захисту від маніпуляції оракулами

Маніпуляція оракулом — один з найприбутковіших векторів атак у DeFi. Суть: зловмисник штучно змінює ціну в джерелі даних (oracle), яке використовує протокол, отримує прибуток від неправильної ціни, потім ціна повертається в норму. На відміну від класичних експлойтів коду, вам не обов'язково потрібно зламувати смарт-контракт—просто тимчасово вплинути на дані, яким довіряє контракт.

Mango Markets (жовтень 2022): $114M вкрадено через маніпуляцію токеном MNGO в Mango oracle. CREAM Finance: $130M через flash loan + маніпуляцію ціною yUSD. Це не граничні випадки—це системний ризик при неправильній архітектурі price feeds.

Типи оракулів та їх вектори атак

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 → виклик вразливого протоколу → повернення 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 не підходить як єдиний захист для волатильних активів або пулів з низькою ліквідністю.

Chainlink Price Feeds

Chainlink—децентралізована мережа оракулів. Ціна агрегується від десятків незалежних node operators, оновлюється при відхиленні більше ніж на поріг (зазвичай 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"
        );
        
        // Перевірка повноти раунду
        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—прийняти застарілу ціну під час простою Chainlink
  • Не перевіряти answeredInRound >= roundId—прийняти ціну з неповного раунду
  • Hardcode STALENESS_THRESHOLD без урахування конкретного feed (heartbeat для ETH/USD = 1 година, для екзотичних = 24 години)

Система мультиоракула: медіанна агрегація

Найкращий захист—кілька незалежних джерел з агрегацією через медіану. Маніпуляція одного джерела не впливає на медіану з трьох.

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 {
                // Оракул недоступний—продовжуємо з іншими
            }
        }
        
        // Потрібно мінімум 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;
    
    // Максимальна зміна ціни за одне оновлення
    uint256 public constant MAX_PRICE_CHANGE_BPS = 1000;  // 10% за оновлення
    
    // Максимальна зміна за 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;
            
            // Різка зміна за одне оновлення
            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 заблокований, 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 детекція на рівні транзакції

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 та pull oracle модель

Chainlink використовує push модель: дані оновлюються автоматично при відхиленні або heartbeat. Pyth використовує pull модель: користувач сам оновлює ціну перед транзакцією, надаючи підписану 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 секунд для більшості активів)
    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%—дані ненадійні і операцію краще відхилити.

Мониторинг аномалій оракула

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]
          });
          
          // Відправляємо alert негайно
          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 активи 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 тиж