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 помогут настроить защиту вашего протокола. Получите коммерческое предложение.







