Разработка системы реагирования на инциденты
Hack в DeFi — это не взлом в традиционном смысле. Нет брутфорса, нет фишинга. Атака занимает одну транзакцию (иногда несколько), деньги уходят необратимо за секунды. К моменту когда команда узнаёт об инциденте — ущерб уже нанесён. Единственный способ минимизировать потери — детектировать атаку в реальном времени (в идеале — до исполнения транзакции) и иметь заранее подготовленные механизмы остановки.
Ronin Bridge (март 2022, $625M) не заметили взлом 6 дней. Euler Finance (март 2023, $197M) среагировали быстро, договорились о возврате через 2 недели — нетипичный хэппи-энд. Разница в подходе к incident response.
Архитектура системы реагирования
Система состоит из трёх уровней:
Детекция (мониторинг) — on-chain и off-chain мониторинг аномалий в реальном времени.
Реакция (circuit breaker) — набор on-chain механизмов для немедленной остановки критичных функций.
Коммуникация и восстановление — процессы уведомления, оценки ущерба, координации ответа.
Circuit Breaker контракты
Pause механизм
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract ProtocolCore is Pausable, AccessControl {
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
// Emergency pause: отдельные функции, не весь протокол
mapping(bytes4 => bool) public functionPaused;
event FunctionPaused(bytes4 indexed selector, address indexed by);
event FunctionUnpaused(bytes4 indexed selector);
event EmergencyWithdrawTriggered(address indexed asset, uint256 amount);
modifier notFunctionPaused() {
require(!functionPaused[msg.sig], "Function paused");
_;
}
// Глобальная остановка — только через timelock (кроме Guardian)
function pause() external {
require(
hasRole(PAUSER_ROLE, msg.sender) || hasRole(GUARDIAN_ROLE, msg.sender),
"Not authorized"
);
_pause();
}
// Возобновление — только через timelock governance
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
// Точечная пауза конкретной функции
function pauseFunction(bytes4 selector) external onlyRole(GUARDIAN_ROLE) {
functionPaused[selector] = true;
emit FunctionPaused(selector, msg.sender);
}
// Критичная функция: вывод средств в safe wallet при компрометации
function emergencyWithdraw(
address asset,
address safeDestination
) external onlyRole(GUARDIAN_ROLE) whenPaused {
require(safeDestination != address(0), "Invalid destination");
uint256 balance = IERC20(asset).balanceOf(address(this));
if (balance > 0) {
IERC20(asset).safeTransfer(safeDestination, balance);
emit EmergencyWithdrawTriggered(asset, balance);
}
}
}
Rate limiter
Не весь DeFi взламывается одной транзакцией. Часто атака — серия транзакций. Rate limiter замедляет отток средств:
contract RateLimiter {
struct RateLimit {
uint256 maxAmount; // максимум за период
uint256 periodDuration; // длина периода в секундах
uint256 currentAmount; // потрачено в текущем периоде
uint256 periodStart; // начало текущего периода
}
mapping(address => RateLimit) public limits; // per-asset limits
event RateLimitExceeded(address indexed asset, uint256 requested, uint256 available);
function _checkAndUpdateRateLimit(address asset, uint256 amount) internal {
RateLimit storage limit = limits[asset];
if (limit.maxAmount == 0) return; // лимит не установлен
// Сбрасываем период если он истёк
if (block.timestamp >= limit.periodStart + limit.periodDuration) {
limit.currentAmount = 0;
limit.periodStart = block.timestamp;
}
uint256 available = limit.maxAmount - limit.currentAmount;
if (amount > available) {
emit RateLimitExceeded(asset, amount, available);
revert("Rate limit exceeded");
}
limit.currentAmount += amount;
}
}
Rate limiter — не панацея: атакующий может дождаться сброса периода. Но он даёт команде время для реакции и снижает максимально возможный единовременный ущерб.
Мониторинговая система
On-chain события как триггеры
Первый уровень детекции — listening к событиям контракта. Аномальные события, которые стоит мониторить:
interface MonitoringRule {
eventSignature: string;
condition: (event: Log) => boolean;
severity: 'low' | 'medium' | 'high' | 'critical';
action: 'alert' | 'auto-pause' | 'notify-guardian';
}
const rules: MonitoringRule[] = [
{
eventSignature: 'Withdrawal(address,uint256)',
condition: (e) => BigInt(e.data) > LARGE_WITHDRAWAL_THRESHOLD,
severity: 'high',
action: 'alert',
},
{
eventSignature: 'FlashLoan(address,uint256)',
condition: (e) => BigInt(e.data) > FLASH_LOAN_THRESHOLD,
severity: 'medium',
action: 'alert',
},
{
eventSignature: 'OwnershipTransferred(address,address)',
condition: () => true, // любой transfer ownership — critical
severity: 'critical',
action: 'notify-guardian',
},
];
Mempool мониторинг (pre-execution detection)
Наиболее ценная возможность — увидеть атаку до её исполнения. Транзакция в mempool ещё не включена в блок — у команды есть несколько секунд.
import { ethers } from 'ethers';
const provider = new ethers.WebSocketProvider(WS_RPC_URL);
// Подписка на pending транзакции
provider.on('pending', async (txHash) => {
const tx = await provider.getTransaction(txHash);
if (!tx) return;
// Проверяем: является ли это вызовом к нашему контракту
if (tx.to?.toLowerCase() !== CONTRACT_ADDRESS.toLowerCase()) return;
// Анализируем calldata
const decoded = decodeCalldata(tx.data);
const risk = assessRisk(decoded, tx);
if (risk.level === 'critical') {
await triggerEmergencyPause(risk);
await notifyGuardian(tx, risk);
}
});
async function triggerEmergencyPause(risk: RiskAssessment): Promise<void> {
// Отправляем pause транзакцию с высоким gasPrice
// чтобы она включилась в блок раньше атаки
const guardianWallet = new ethers.Wallet(GUARDIAN_KEY, provider);
const gasPrice = await provider.getFeeData();
await guardianContract.pause({
maxFeePerGas: gasPrice.maxFeePerGas! * 3n, // 3x от текущего
maxPriorityFeePerGas: ethers.parseUnits('50', 'gwei'), // высокий tip
});
}
Проблема: автоматический pause по pending транзакции создаёт риск ложных срабатываний и DoS. Нужна тщательная настройка правил.
Invariant monitoring
Самый надёжный детектор — непрерывная проверка финансовых инвариантов:
interface ProtocolInvariant {
name: string;
check: (state: ProtocolState) => boolean;
description: string;
}
const invariants: ProtocolInvariant[] = [
{
name: 'total_supply_consistency',
check: (s) => s.totalShares * s.pricePerShare <= s.totalAssets * 1.001,
description: 'Total shares value не превышает total assets',
},
{
name: 'no_sudden_tvl_drop',
check: (s) => s.currentTVL >= s.previousTVL * 0.8,
description: 'TVL не упал более чем на 20% за один блок',
},
{
name: 'oracle_price_sanity',
check: (s) => Math.abs(s.oraclePrice - s.chainlinkPrice) / s.chainlinkPrice < 0.05,
description: 'Цена oracle не отклоняется более чем на 5% от Chainlink',
},
];
// Запускается каждый блок
async function checkInvariants(blockNumber: number): Promise<void> {
const state = await fetchProtocolState(blockNumber);
for (const invariant of invariants) {
if (!invariant.check(state)) {
await triggerAlert({
level: 'critical',
invariant: invariant.name,
description: invariant.description,
blockNumber,
state,
});
}
}
}
Инфраструктура мониторинга
Технический стек
| Компонент | Инструмент | Назначение |
|---|---|---|
| Event listening | Alchemy Webhooks / QuickNode Streams | Real-time on-chain события |
| Mempool watching | Blocknative / собственная нода | Pre-execution detection |
| Indexing | The Graph / Ponder | Исторические данные для аномалий |
| Alerting | PagerDuty + Telegram Bot | Уведомления команды |
| Metrics | Prometheus + Grafana | Dashboards и trending |
| Automation | Chainlink Automation / Gelato | Автоматические действия |
Forta Network интеграция
Forta — децентрализованная сеть мониторинга смарт-контрактов. Позволяет деплоить detection bots, которые анализируют каждую транзакцию:
// Forta Detection Bot
import { Finding, FindingSeverity, FindingType, TransactionEvent } from 'forta-agent';
export function handleTransaction(txEvent: TransactionEvent) {
const findings: Finding[] = [];
// Проверяем вызовы к нашему контракту
const calls = txEvent.filterFunction(
'withdraw(uint256,address)',
CONTRACT_ADDRESS
);
for (const call of calls) {
const amount = BigInt(call.args.amount);
if (amount > LARGE_WITHDRAWAL_THRESHOLD) {
findings.push(
Finding.fromObject({
name: 'Large Withdrawal Detected',
description: `Withdrawal of ${amount} tokens`,
alertId: 'LARGE-WITHDRAWAL',
severity: FindingSeverity.High,
type: FindingType.Suspicious,
})
);
}
}
return findings;
}
Forta имеет сеть нод, которые запускают боты — распределённая детекция без единой точки отказа.
Процессы и runbook
Технические компоненты бесполезны без чётких процессов. Runbook — документ «что делать при инциденте»:
T+0 (первый alert):
- Guardian on-call получает Telegram/PagerDuty алерт
- Оценка: false positive или реальная атака? Время на оценку: 2-3 минуты максимум
- Если сомнение — pause немедленно, разбираемся потом
T+5 минут:
- Pause выполнен (или принято решение не паузить)
- Уведомление core team
- Начало on-chain расследования (Tenderly transaction trace)
T+30 минут:
- Публичное уведомление через Twitter/Discord: «We are investigating an issue»
- Оценка ущерба
- Координация с другими протоколами (если атака cross-protocol)
T+2 часа:
- Детальный post-mortem начат
- Связь с белыми хакерами/исследователями (если нужна помощь)
- Обращение в полицию (если юрисдикция позволяет)
T+24-48 часов:
- Публичный post-mortem
- Plan восстановления
- Координация с биржами (заморозка адресов атакующего)
Ключи Guardian и безопасность
Guardian ключ, который может pause протокол — сам по себе уязвимость. Если он скомпрометирован — атакующий паузит протокол и вымогает деньги за разблокировку.
Рекомендации:
- Guardian — Gnosis Safe 2/3 или 3/5, не EOA
- Hardware wallet для signing (Ledger/Trezor)
- Географически распределённые keyholders
- Регулярные drill-тренировки (раз в квартал)
- Мониторинг самого Guardian мультисига
Процесс работы
Дизайн (1 неделя). Карта угроз: какие атаки возможны, какие инварианты нарушаются, какие функции критичны. Дизайн circuit breaker архитектуры.
Разработка on-chain компонентов (2 недели). Pause механизм + rate limiter + emergency withdraw + тесты.
Разработка мониторинга (2-3 недели). Event listeners + invariant checks + Forta bot (опционально) + alerting pipeline.
Разработка runbook (3-5 дней). Документация процессов, drill training.
Аудит circuit breaker контрактов (1 неделя). Контракты с pause механизмом — сами по себе attack surface.
Интеграция и тестирование (1 неделя). End-to-end тест: симуляция атаки → детекция → pause → recovery.
Полный цикл: 2-2,5 месяца. Стоимость зависит от размера протокола и уровня автоматизации.







