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% Эти параметры пересматриваются при каждом обновлении.Этапы работы
- Аналитика — сбор метрик, интервью с командой, определение рисков
- Проектирование — выбор уровней, настройка параметров, архитектура Security Council
- Реализация — написание смарт-контрактов, интеграция с оракулами
- Тестирование — unit, integration, fuzz тесты (Foundry, Echidna)
- Аудит — внешний аудит с формальной верификацией
- Деплой — развёртывание на выбранной L1/L2, настройка мониторинга
Сроки и стоимость
Полный цикл разработки production-grade системы: 4–6 месяцев. Стоимость зависит от сложности протокола и количества триггеров. Наши клиенты обычно экономят в десятки раз больше на предотвращённых атаках. Надежная архитектура окупается быстро. Свяжитесь с нами — мы оценим ваш проект за 2-3 рабочих дня.
Типичные ошибки при внедрении
- Жёсткие пороги без исторического анализа — приводят к ложным срабатываниям и блокировке легитимных операций.
- Единоличное право отключения — создаёт centralization risk и уязвимость для governance атак.
- Игнорирование легитимных крупных выводов — решение: whitelist адресов или двухэтапный вывод.
- Слишком быстрый сброс паузы — timelock на сброс должен быть длиннее обычного (7+ дней).
Закажите консультацию — наши инженеры с пятилетним опытом в DeFi помогут настроить защиту вашего протокола. Получите коммерческое предложение.







