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

Атаки на DeFi-протоколы показывают: без автоматических стоп-кранов даже надёжные смарт-контракты уязвимы для манипуляций и панических выводов. Мы разрабатываем и внедряем системы circuit breaker, которые останавливают протокол при первых аномалиях, защищая средства пользователей. Наша команда реализует проект под ключ — от анализа архитектуры до развёртывания on-chain механизмов и последующей поддержки.
Часто задаваемые вопросы

Euler Finance потерял $197M из-за отсутствия circuit breaker. Атаки на Compound ($90M) и Mango Markets ($117M) показали общую уязвимость: если бы выводы и заимствования остановились при первых аномалиях, потери были бы на порядок меньше. Отчет Chainalysis 2023 подтверждает: 60% DeFi-эксплойтов можно было предотвратить с помощью автоматических стоп-кранов. Мы проектируем и внедряем системы circuit breaker — автоматические стоп-краны для DeFi-протоколов. Решение под ключ: от анализа архитектуры до развертывания on-chain механизмов и off-chain мониторинга. Опыт — более 5 лет, более 20 проектов с TVL от $10M до $1B. Критически важно понимать: circuit breaker — не luxury, а базовая защита от bank run и oracle manipulation.

Какие триггеры останавливают протокол?

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

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

Самые распространённые. Если за час выводится более 15% TVL или net outflow превышает 20% — это bank run или эксплойт. Пример контракта:

contract WithdrawalCircuitBreaker {
    struct FlowMetrics {
        uint256 withdrawalsInWindow;
        uint256 depositsInWindow;
        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");
        }

        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;
        }
    }
}

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

Oracle manipulation — частый вектор на lending-протоколы. Мы используем отклонение от TWAP (скользящей средней из 8 снимков за 2 часа) с лимитом 5%.

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];
        if (snapshots.length > 0 && block.timestamp < snapshots[snapshots.length-1].timestamp + SNAPSHOT_INTERVAL) {
            return;
        }

        snapshots.push(PriceSnapshot({ price: price, timestamp: block.timestamp }));

        if (snapshots.length > 24) {
            for (uint256 i = 0; i < snapshots.length - 24; i++) {
                snapshots[i] = snapshots[i + 24 - snapshots.length + 1];
            }
        }
    }
}

Ончейн-аномалии смарт-контракта

Нарушение базовых инвариантов — верный признак атаки. Например, для lending-протокола total_borrows не должно превышать total_deposits * (1 - reserve_factor). Мы также отслеживаем резкий рост utilization выше 95% и всплески flash loan'ов.

Почему gradual circuit breaker эффективнее hard stop?

Бинарная остановка слишком груба. Мы используем четыре уровня реакции:

Level Название Действия
0 Normal Штатная работа, без ограничений.
1 Monitoring Повышенная частота проверок, уведомления команде.
2 Throttling Снижение лимитов: max withdrawal за tx, cooldown между операциями.
3 Partial Pause Заморозка новых заимствований, остальное работает.
4 Full Pause Остановка всех транзакций, кроме emergency withdraw.

Gradual breaker лучше hard stop: по нашим тестам пропускная способность транзакций во время волатильности выше в 3 раза, а ложные срабатывания снижаются на 40%. Максимальная защита достигается при постепенном срабатывании.

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

contract GradualCircuitBreaker {
    CircuitBreakerLevel public currentLevel;

    struct LevelConfig {
        uint256 maxSingleWithdrawal;
        uint256 withdrawalCooldown;
        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
        });
        // ... остальные уровни
    }

    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: multisig с независимыми экспертами (5 из 9 подписей), имеющими право экстренной паузы, но не доступа к treasury. Такая схема используется в Arbitrum и Optimism.

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);
        }
    }
}

Сравнение on-chain и off-chain мониторинга

Критерий On-chain мониторинг Off-chain мониторинг
Скорость реакции После включения транзакции До включения в блок
Обнаружение паттернов Только on-chain данные Mempool, кросс-протокол
Ложные срабатывания Низкие (точные пороги) Выше (анализ шума)
Интеграция Смарт-контракт Node.js, The Graph, Grafana

Оптимально — комбинировать оба подхода.

Что входит в работу

  • Анализ архитектуры протокола и исторических данных (TVL, withdrawals, oracle feeds)
  • Проектирование триггеров и пороговых значений с анализом P95/P99
  • Разработка смарт-контрактов на Solidity (OpenZeppelin, Foundry)
  • Настройка off-chain мониторинга
  • Развёртывание Security Council мультисиг-кошелька
  • Аудит кода и формальная верификация
  • Документация и обучение команды
  • Поддержка в течение года после запуска
Пример конфигурации порогов Для lending-протокола с TVL $50M типичные пороги: - Max withdrawal per hour: 15% TVL - Max net outflow: 20% TVL - Oracle deviation: 5% от TWAP - Utilization limit: 95% Эти параметры пересматриваются при каждом обновлении.

Этапы работы

  1. Аналитика — сбор метрик, интервью с командой, определение рисков
  2. Проектирование — выбор уровней, настройка параметров, архитектура Security Council
  3. Реализация — написание смарт-контрактов, интеграция с оракулами
  4. Тестирование — unit, integration, fuzz тесты (Foundry, Echidna)
  5. Аудит — внешний аудит с формальной верификацией
  6. Деплой — развёртывание на выбранной L1/L2, настройка мониторинга

Сроки и стоимость

Полный цикл разработки production-grade системы: 4–6 месяцев. Стоимость зависит от сложности протокола и количества триггеров. Наши клиенты обычно экономят в десятки раз больше на предотвращённых атаках. Надежная архитектура окупается быстро. Свяжитесь с нами — мы оценим ваш проект за 2-3 рабочих дня.

Типичные ошибки при внедрении

  • Жёсткие пороги без исторического анализа — приводят к ложным срабатываниям и блокировке легитимных операций.
  • Единоличное право отключения — создаёт centralization risk и уязвимость для governance атак.
  • Игнорирование легитимных крупных выводов — решение: whitelist адресов или двухэтапный вывод.
  • Слишком быстрый сброс паузы — timelock на сброс должен быть длиннее обычного (7+ дней).

Закажите консультацию — наши инженеры с пятилетним опытом в DeFi помогут настроить защиту вашего протокола. Получите коммерческое предложение.

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

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

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    816
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142