Разработка системы circuit breaker для DeFi-протоколов

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

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

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

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

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

Разработка системы circuit breaker для DeFi-протоколов

Название пришло из финансового рынка: на NYSE и Nasdaq торги автоматически останавливаются при падении индекса на 7%, 13%, 20%. Смысл — предотвратить паническую спираль и дать рынку время «остыть». DeFi нужен аналог, потому что смарт-контракты исполняются без паузы, а эксплойты часто опустошают протокол за секунды.

Euler Finance ($197M), Compound ($90M в oracle manipulation), Mango Markets ($117M) — все эти инциденты объединяет одно: если бы выводы/заимствования остановились при первых аномальных признаках, масштаб потерь был бы на порядок меньше.

Circuit breaker для DeFi — это система автоматических триггеров, останавливающих или ограничивающих ключевые функции протокола при обнаружении аномалий. Задача — минимизировать потери при атаке или техническом сбое, с возможностью быстрого восстановления нормальной работы.

Что останавливает circuit breaker: классификация триггеров

Не каждый параметр одинаково значим как триггер. Хороший circuit breaker срабатывает достаточно редко, чтобы не мешать нормальной работе, но достаточно чувствительно, чтобы поймать атаку до значительного ущерба.

Группа 1: Триггеры по объёму вывода

Самые распространённые и понятные. Если за короткое время выводится нетипично большой объём — это либо bank run, либо эксплойт.

contract WithdrawalCircuitBreaker {
    struct FlowMetrics {
        uint256 withdrawalsInWindow;    // сумма выводов за window
        uint256 depositsInWindow;       // сумма депозитов за window
        uint256 windowStartTime;
        uint256 windowStartBlock;
    }
    
    uint256 public constant WINDOW_DURATION = 1 hours;
    uint256 public constant MAX_WITHDRAWAL_PERCENT_BPS = 1500; // 15% TVL за окно
    uint256 public constant NET_OUTFLOW_LIMIT_BPS = 2000;      // -20% net за окно
    
    FlowMetrics public currentWindow;
    uint256 public totalTVL;
    bool public withdrawalsPaused;
    
    event CircuitBreakerTriggered(string reason, uint256 triggeredAt, uint256 amount);
    event CircuitBreakerReset(uint256 resetAt, address resetBy);
    
    modifier notPaused() {
        require(!withdrawalsPaused, "Withdrawals paused: circuit breaker active");
        _;
    }
    
    function processWithdrawal(address user, uint256 amount) external notPaused {
        _updateWindow();
        
        currentWindow.withdrawalsInWindow += amount;
        
        // Проверка абсолютного объёма
        uint256 maxWithdrawalAmount = totalTVL * MAX_WITHDRAWAL_PERCENT_BPS / 10000;
        if (currentWindow.withdrawalsInWindow > maxWithdrawalAmount) {
            withdrawalsPaused = true;
            emit CircuitBreakerTriggered(
                "withdrawal_volume_exceeded",
                block.timestamp,
                currentWindow.withdrawalsInWindow
            );
            revert("Circuit breaker: withdrawal limit exceeded");
        }
        
        // Проверка net outflow
        int256 netFlow = int256(currentWindow.depositsInWindow) - 
                         int256(currentWindow.withdrawalsInWindow);
        uint256 netOutflow = netFlow < 0 ? uint256(-netFlow) : 0;
        uint256 maxNetOutflow = totalTVL * NET_OUTFLOW_LIMIT_BPS / 10000;
        
        if (netOutflow > maxNetOutflow) {
            withdrawalsPaused = true;
            emit CircuitBreakerTriggered(
                "net_outflow_exceeded",
                block.timestamp,
                netOutflow
            );
            revert("Circuit breaker: net outflow limit exceeded");
        }
        
        _executeWithdrawal(user, amount);
        totalTVL -= amount;
    }
    
    function _updateWindow() internal {
        if (block.timestamp >= currentWindow.windowStartTime + WINDOW_DURATION) {
            currentWindow.withdrawalsInWindow = 0;
            currentWindow.depositsInWindow = 0;
            currentWindow.windowStartTime = block.timestamp;
        }
    }
}

Группа 2: Триггеры по аномальным ценам оракула

Oracle manipulation — распространённый вектор атаки на lending протоколы. Атакующий манипулирует ценой залогового токена для получения незаконных займов.

contract OracleCircuitBreaker {
    struct PriceSnapshot {
        uint256 price;
        uint256 timestamp;
    }
    
    mapping(address => PriceSnapshot[]) public priceHistory;
    mapping(address => bool) public oraclePaused;
    
    uint256 public constant MAX_PRICE_DEVIATION_BPS = 500;  // 5% от TWAP
    uint256 public constant TWAP_PERIODS = 8;               // 8 snapshot'ов
    uint256 public constant SNAPSHOT_INTERVAL = 15 minutes;
    
    function checkOracleHealth(address token, uint256 currentPrice) 
        external returns (bool healthy) 
    {
        _recordSnapshot(token, currentPrice);
        
        uint256 twap = _calculateTWAP(token);
        if (twap == 0) return true; // Недостаточно данных
        
        uint256 deviation;
        if (currentPrice > twap) {
            deviation = (currentPrice - twap) * 10000 / twap;
        } else {
            deviation = (twap - currentPrice) * 10000 / twap;
        }
        
        if (deviation > MAX_PRICE_DEVIATION_BPS) {
            oraclePaused[token] = true;
            emit CircuitBreakerTriggered(
                "oracle_deviation",
                block.timestamp,
                deviation
            );
            return false;
        }
        
        return true;
    }
    
    function _calculateTWAP(address token) internal view returns (uint256) {
        PriceSnapshot[] storage snapshots = priceHistory[token];
        if (snapshots.length < 2) return 0;
        
        uint256 start = snapshots.length > TWAP_PERIODS 
            ? snapshots.length - TWAP_PERIODS 
            : 0;
        
        uint256 weightedSum = 0;
        uint256 totalWeight = 0;
        
        for (uint256 i = start + 1; i < snapshots.length; i++) {
            uint256 timeDelta = snapshots[i].timestamp - snapshots[i-1].timestamp;
            weightedSum += snapshots[i-1].price * timeDelta;
            totalWeight += timeDelta;
        }
        
        return totalWeight > 0 ? weightedSum / totalWeight : 0;
    }
    
    function _recordSnapshot(address token, uint256 price) internal {
        PriceSnapshot[] storage snapshots = priceHistory[token];
        
        // Записываем снапшот не чаще чем раз в SNAPSHOT_INTERVAL
        if (snapshots.length > 0 && 
            block.timestamp < snapshots[snapshots.length-1].timestamp + SNAPSHOT_INTERVAL) {
            return;
        }
        
        snapshots.push(PriceSnapshot({
            price: price,
            timestamp: block.timestamp
        }));
        
        // Храним только последние 24 снапшота
        if (snapshots.length > 24) {
            // Сдвиг массива (дорого, в production — circular buffer)
            for (uint256 i = 0; i < snapshots.length - 24; i++) {
                snapshots[i] = snapshots[i + 24 - snapshots.length + 1];
            }
            // В реальном контракте лучше использовать маппинг + счётчик
        }
    }
}

Группа 3: Ончейн-аномалии смарт-контракта

Некоторые аномалии сигнализируют о нарушении инвариантов протокола.

Invariant checks: базовые арифметические инварианты протокола должны выполняться всегда. Для lending: total_borrows <= total_deposits * (1 - reserve_factor). Нарушение — немедленный стоп.

Utilization rate spike: для lending протокола резкий рост utilization выше 95% за короткое время — аномалия.

Flash loan volume spike: если в одном блоке происходит flash loan на сумму > X% от TVL — повышенный риск, требует проверки.

contract InvariantMonitor {
    uint256 public constant MAX_UTILIZATION_BPS = 9500;   // 95%
    uint256 public constant FLASH_LOAN_TVL_LIMIT_BPS = 5000; // 50% TVL за блок
    uint256 public constant INVARIANT_TOLERANCE_BPS = 100; // 1% допуск
    
    uint256 public lastBlockFlashLoanVolume;
    uint256 public lastFlashLoanBlock;
    
    function checkInvariants() external view returns (bool) {
        uint256 totalDeposited = getTotalDeposited();
        uint256 totalBorrowed = getTotalBorrowed();
        uint256 reserveFactor = getReserveFactor();
        
        // Базовый инвариант: borrows не превышают доступный капитал
        uint256 maxBorrowable = totalDeposited * (10000 - reserveFactor) / 10000;
        
        if (totalBorrowed > maxBorrowable * (10000 + INVARIANT_TOLERANCE_BPS) / 10000) {
            return false; // Инвариант нарушен
        }
        
        // Проверка utilization
        if (totalDeposited > 0) {
            uint256 utilization = totalBorrowed * 10000 / totalDeposited;
            if (utilization > MAX_UTILIZATION_BPS) {
                return false;
            }
        }
        
        return true;
    }
    
    modifier checkInvariantsAfter() {
        _;
        require(checkInvariants(), "Protocol invariant violated: circuit breaker");
    }
    
    function trackFlashLoan(uint256 amount) internal {
        if (block.number > lastFlashLoanBlock) {
            lastBlockFlashLoanVolume = 0;
            lastFlashLoanBlock = block.number;
        }
        
        lastBlockFlashLoanVolume += amount;
        
        uint256 currentTVL = getTotalDeposited();
        uint256 flashLoanLimit = currentTVL * FLASH_LOAN_TVL_LIMIT_BPS / 10000;
        
        if (lastBlockFlashLoanVolume > flashLoanLimit) {
            // Не блокируем flash loan полностью (это сломает легитимные арбитражи),
            // но логируем для мониторинга и уведомляем
            emit AnomalyDetected("flash_loan_spike", lastBlockFlashLoanVolume, block.number);
        }
    }
}

Gradual vs Hard Circuit Breaker

Бинарная остановка («работает / не работает») слишком груба. Правильная система имеет несколько уровней реакции.

Level 0 — Normal: всё работает штатно.

Level 1 — Monitoring: аномальные показатели, но в допустимом диапазоне. Повышенная частота проверок, уведомления команде. Никаких ограничений для пользователей.

Level 2 — Throttling: небольшие аномалии. Снижение лимитов: максимальный withdrawal за транзакцию уменьшается, вводится cooldown между крупными операциями.

Level 3 — Partial Pause: значительные аномалии. Заморозка конкретной функции (например, только новых заимствований), остальное работает. Депозиты и вывод депозитов обычно остаются активными дольше всего.

Level 4 — Full Pause: критическая аномалия или подтверждённый exploit. Полная остановка всех транзакций кроме emergency withdraw.

enum CircuitBreakerLevel { Normal, Monitoring, Throttling, PartialPause, FullPause }

contract GradualCircuitBreaker {
    CircuitBreakerLevel public currentLevel;
    
    struct LevelConfig {
        uint256 maxSingleWithdrawal;    // максимум за транзакцию
        uint256 withdrawalCooldown;     // cooldown между крупными выводами
        bool newBorrowsAllowed;
        bool newDepositsAllowed;
        bool withdrawalsAllowed;
        bool liquidationsAllowed;
    }
    
    mapping(CircuitBreakerLevel => LevelConfig) public levelConfigs;
    
    constructor() {
        levelConfigs[CircuitBreakerLevel.Normal] = LevelConfig({
            maxSingleWithdrawal: type(uint256).max,
            withdrawalCooldown: 0,
            newBorrowsAllowed: true,
            newDepositsAllowed: true,
            withdrawalsAllowed: true,
            liquidationsAllowed: true
        });
        
        levelConfigs[CircuitBreakerLevel.Throttling] = LevelConfig({
            maxSingleWithdrawal: 1_000_000e6, // $1M max per tx
            withdrawalCooldown: 5 minutes,
            newBorrowsAllowed: true,
            newDepositsAllowed: true,
            withdrawalsAllowed: true,
            liquidationsAllowed: true
        });
        
        levelConfigs[CircuitBreakerLevel.PartialPause] = LevelConfig({
            maxSingleWithdrawal: 500_000e6,
            withdrawalCooldown: 30 minutes,
            newBorrowsAllowed: false,  // Новые займы заморожены
            newDepositsAllowed: true,
            withdrawalsAllowed: true,
            liquidationsAllowed: true  // Ликвидации должны работать для health
        });
        
        levelConfigs[CircuitBreakerLevel.FullPause] = LevelConfig({
            maxSingleWithdrawal: 0,
            withdrawalCooldown: type(uint256).max,
            newBorrowsAllowed: false,
            newDepositsAllowed: false,
            withdrawalsAllowed: false,
            liquidationsAllowed: false
        });
    }
    
    function escalateLevel(CircuitBreakerLevel newLevel, string calldata reason) 
        external onlyRiskManager 
    {
        require(uint8(newLevel) > uint8(currentLevel), "Can only escalate");
        emit LevelEscalated(currentLevel, newLevel, reason, block.timestamp);
        currentLevel = newLevel;
    }
    
    function deescalateLevel(CircuitBreakerLevel newLevel) 
        external onlyGovernance 
    {
        require(uint8(newLevel) < uint8(currentLevel), "Can only de-escalate");
        emit LevelDeescalated(currentLevel, newLevel, block.timestamp);
        currentLevel = newLevel;
    }
}

Управление: кто может нажать стоп

Автоматические триггеры покрывают предсказуемые аномалии, но реальные атаки часто новы и нестандартны. Нужны люди с правом экстренной остановки.

Конфликт интересов: если один человек или команда может остановить протокол — это centralization risk. Они могут злоупотребить этим правом для манипуляции рынком.

Решение: Security Council с timelock-bypass

Схема, используемая Arbitrum, Optimism, Compound: отдельный multisig с N подписантами (часто независимые security-эксперты и исследователи). Security Council может:

  • Остановить протокол немедленно (в отличие от обычного governance с timelock)
  • Выполнить экстренные патчи с сокращённым timelock (24–72 часа вместо 7+ дней)

Они не могут: распоряжаться treasury, изменять токеномику, принимать обычные governance решения.

contract SecurityCouncil {
    address[] public members;
    uint256 public constant REQUIRED_SIGNATURES = 5; // из 9 членов
    
    mapping(bytes32 => mapping(address => bool)) public signatures;
    mapping(bytes32 => uint256) public signatureCount;
    
    function emergencyPause(address protocol) external onlyMember {
        IProtocol(protocol).emergencyPause();
        emit EmergencyPauseExecuted(protocol, msg.sender, block.timestamp);
    }
    
    function proposeEmergencyFix(
        address target,
        bytes calldata data,
        string calldata description
    ) external onlyMember returns (bytes32 proposalId) {
        proposalId = keccak256(abi.encodePacked(target, data, block.number));
        signatures[proposalId][msg.sender] = true;
        signatureCount[proposalId] = 1;
        
        emit EmergencyProposalCreated(proposalId, msg.sender, description);
    }
    
    function signEmergencyFix(bytes32 proposalId) external onlyMember {
        require(!signatures[proposalId][msg.sender], "Already signed");
        signatures[proposalId][msg.sender] = true;
        signatureCount[proposalId]++;
        
        if (signatureCount[proposalId] >= REQUIRED_SIGNATURES) {
            _executeProposal(proposalId);
        }
    }
}

Off-chain мониторинг и интеграция с on-chain триггерами

On-chain circuit breaker реагирует только на то, что происходит непосредственно в транзакциях. Более богатая картина — у off-chain мониторинга, который видит mempool, кросс-протокольные паттерны, социальные сигналы.

class DeFiMonitoringService {
    constructor(provider, protocolAddress, alertService) {
        this.provider = provider;
        this.protocol = new ethers.Contract(protocolAddress, PROTOCOL_ABI, provider);
        this.alertService = alertService;
        this.metrics = {};
    }
    
    async monitorBlock(blockNumber) {
        const [tvl, borrows, deposits, prices] = await Promise.all([
            this.protocol.getTotalTVL({ blockTag: blockNumber }),
            this.protocol.getTotalBorrows({ blockTag: blockNumber }),
            this.protocol.getTotalDeposits({ blockTag: blockNumber }),
            this.getPricesSnapshot(blockNumber)
        ]);
        
        const prevMetrics = this.metrics[blockNumber - 1] || {};
        
        // Детект резкого изменения TVL
        if (prevMetrics.tvl) {
            const tvlChangePct = Math.abs(tvl - prevMetrics.tvl) / prevMetrics.tvl;
            if (tvlChangePct > 0.05) { // 5% за блок = ~12 секунд
                await this.alertService.sendCritical({
                    type: 'tvl_spike',
                    change: tvlChangePct,
                    blockNumber,
                    current: tvl.toString(),
                    previous: prevMetrics.tvl.toString()
                });
            }
        }
        
        // Детект oracle аномалий
        for (const [token, price] of Object.entries(prices)) {
            if (prevMetrics.prices?.[token]) {
                const priceChange = Math.abs(price - prevMetrics.prices[token]) / 
                                   prevMetrics.prices[token];
                if (priceChange > 0.03) { // 3% за блок
                    await this.alertService.sendHigh({
                        type: 'price_anomaly',
                        token,
                        priceChange,
                        blockNumber
                    });
                    
                    // Попытка on-chain триггера если есть права
                    if (this.hasEmergencyRole) {
                        await this.protocol.triggerOracleCircuitBreaker(token);
                    }
                }
            }
        }
        
        this.metrics[blockNumber] = { tvl, borrows, deposits, prices };
    }
    
    startMonitoring() {
        this.provider.on('block', async (blockNumber) => {
            try {
                await this.monitorBlock(blockNumber);
            } catch (e) {
                console.error(`Monitor error at block ${blockNumber}:`, e);
            }
        });
    }
}

Важные edge cases

Легитимные большие выводы: whale может вывести $50M легально. Circuit breaker не должен делать это невозможным. Решение: whitelist крупных известных адресов (DAO treasury, крупные LP) с отдельными лимитами, или двухэтапный вывод крупных сумм (сначала request, потом execution через таймаут).

Cascade liquidations: при падении рынка массовые ликвидации выглядят как bank run. Circuit breaker не должен блокировать ликвидации — это разрушит протокол. Ликвидации исключаются из withdrawal limits или имеют отдельные параметры.

Governance attacks: если governance токен скомпрометирован, атакующий может попытаться сбросить circuit breaker через governance предложение. Timelock на сброс breaker должен быть длиннее обычного timelock (например, 7 дней вместо 2).

False positives в volatile market: в периоды высокой волатильности (crypto bull run, macro события) нормальные объёмы могут превышать пороговые значения. Параметры circuit breaker должны быть governance-управляемыми для адаптации к рыночным условиям.

Параметризация и настройка порогов

Выбор правильных пороговых значений — критическая задача, требующая анализа исторических данных протокола.

import pandas as pd
import numpy as np

def analyze_historical_flows(withdrawals_df: pd.DataFrame, 
                              tvl_df: pd.DataFrame) -> dict:
    """
    Анализ исторических данных для подбора оптимальных порогов circuit breaker
    """
    # Рассчитываем rolling window метрики
    merged = withdrawals_df.merge(tvl_df, on='timestamp')
    merged['withdrawal_pct'] = merged['withdrawal_amount'] / merged['tvl']
    
    # 1-часовые окна
    merged_hourly = merged.set_index('timestamp').resample('1H').sum()
    merged_hourly['tvl'] = tvl_df.set_index('timestamp').resample('1H').last()['tvl']
    merged_hourly['hourly_withdrawal_pct'] = (
        merged_hourly['withdrawal_amount'] / merged_hourly['tvl']
    )
    
    # Статистика нормального режима (исключаем известные кризисные периоды)
    normal_periods = merged_hourly[merged_hourly['hourly_withdrawal_pct'] < 0.5]
    
    stats = {
        'mean_hourly_withdrawal_pct': normal_periods['hourly_withdrawal_pct'].mean(),
        'std_hourly_withdrawal_pct': normal_periods['hourly_withdrawal_pct'].std(),
        'p95_hourly_withdrawal_pct': normal_periods['hourly_withdrawal_pct'].quantile(0.95),
        'p99_hourly_withdrawal_pct': normal_periods['hourly_withdrawal_pct'].quantile(0.99),
        'max_observed_normal': normal_periods['hourly_withdrawal_pct'].max(),
    }
    
    # Рекомендованные пороги: P99 × 1.5 для первого уровня, 
    # P99 × 2.5 для остановки
    stats['recommended_throttle_threshold'] = stats['p99_hourly_withdrawal_pct'] * 1.5
    stats['recommended_pause_threshold'] = stats['p99_hourly_withdrawal_pct'] * 2.5
    
    return stats

Пороги нужно пересматривать при значительном росте протокола: что было аномалией при TVL $10M, становится нормой при TVL $1B.

Стек и сроки разработки

Смарт-контракты: Solidity, OpenZeppelin (Pausable, AccessControl), Foundry для тестирования включая invariant tests. 8–12 недель на полную систему с градуальными уровнями и governance.

Off-chain мониторинг: Node.js / Go сервис, The Graph для исторических данных, PagerDuty / OpsGenie для алертов, Grafana для дашборда метрик.

Security Council смарт-контракт: Gnosis Safe как base + кастомный модуль для emergency actions. 2–3 недели.

Аудит: обязателен, особенно для логики сброса паузы — это потенциальный вектор атаки на сам circuit breaker.

Полный цикл разработки: 4–6 месяцев для production-grade системы с on-chain + off-chain компонентами, Security Council и документированными процедурами реагирования.