Разработка системы паузы смарт-контрактов при аномалиях

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы паузы смарт-контрактов при аномалиях
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка системы паузы смарт-контрактов при аномалиях

Pause механизм — базовый элемент безопасности для любого DeFi протокола, работающего с пользовательскими средствами. Когда начинается эксплойт, у команды есть минуты, иногда секунды, чтобы остановить утечку. Ручная реакция невозможна — нужна автоматика. Но автоматическая пауза, сработавшая ошибочно, тоже наносит ущерб: blocked transactions, недоверие пользователей, потенциальные liquidations из-за невозможности управлять позициями.

Задача — построить систему, которая паузирует контракт при реальных аномалиях с минимумом ложных срабатываний, и при этом сама не становится вектором атаки.

Pausable контракт: базовая реализация

OpenZeppelin Pausable — стандартный starting point:

import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract ProtectedVault is Pausable, AccessControl {
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");

    // Разные роли: автоматический guardian может паузировать,
    // только governance может разпаузировать
    function pause() external onlyRole(GUARDIAN_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
        // DEFAULT_ADMIN = timelock / multisig governance
        _unpause();
    }

    function deposit(uint256 amount) external whenNotPaused {
        // ...
    }

    function withdraw(uint256 amount) external whenNotPaused {
        // ...
    }
}

Ключевое: разные роли для pause и unpause. Автоматический guardian может быть скомпрометирован или ошибиться — но разпаузировать могут только люди через multisig/governance. Это асимметрия умышленная.

Аномалии: что детектируем

TVL anomaly — резкое падение

Если за N блоков из контракта вышло > X% TVL — что-то не так. Может быть легитимным (крупный withdrawal) или эксплойтом.

contract AnomalyDetector {
    uint256 public constant MAX_TVL_DROP_BPS = 1000; // 10% за период
    uint256 public constant MONITORING_WINDOW = 100; // блоков

    uint256 public tvlSnapshot;
    uint256 public snapshotBlock;

    function checkTVLAnomaly(uint256 currentTVL) internal {
        if (block.number >= snapshotBlock + MONITORING_WINDOW) {
            // обновляем snapshot
            tvlSnapshot = currentTVL;
            snapshotBlock = block.number;
            return;
        }

        if (tvlSnapshot == 0) return;

        uint256 dropBps = ((tvlSnapshot - currentTVL) * 10000) / tvlSnapshot;
        if (currentTVL < tvlSnapshot && dropBps > MAX_TVL_DROP_BPS) {
            _triggerPause("TVL anomaly detected");
        }
    }
}

Проблема: если протокол позволяет легитимные крупные withdrawals, этот детектор будет срабатывать при выходе крупного LP. Нужна калибровка под конкретный протокол.

Необычно крупная единичная транзакция

Если одна транзакция выводит > Y% от TVL:

uint256 public constant SINGLE_TX_THRESHOLD_BPS = 500; // 5% TVL

modifier checkWithdrawAnomaly(uint256 amount) {
    uint256 tvl = totalAssets();
    if (tvl > 0 && (amount * 10000 / tvl) > SINGLE_TX_THRESHOLD_BPS) {
        // Не паузируем автоматически — только логируем для off-chain
        emit LargeWithdrawal(msg.sender, amount, tvl);
    }
    _;
}

Автоматическая пауза на крупные withdrawals рискованна — это легитимная операция для крупных участников. Лучше — emit события для off-chain мониторинга.

Reentrancy detection on-chain

// Детектор для потенциального reentrancy через callback
uint256 private _callDepth;

modifier noDeepCalls() {
    _callDepth++;
    if (_callDepth > 1) {
        _triggerPause("Reentrancy detected");
        revert("Reentrancy");
    }
    _;
    _callDepth--;
}

Это дополнение к стандартному nonReentrant. Разница: вместо просто revert — пауза всего контракта при детекции попытки.

Off-chain мониторинг с автопаузой

On-chain детекторы ограничены: они видят только то, что происходит в текущей транзакции. Более мощный паттерн — off-chain мониторинг + privileged pause transaction.

OpenZeppelin Defender

OZ Defender Sentinel + Autotask — стандартный стек для этого:

// Autotask: вызывается Defender при срабатывании Sentinel условия
const { DefenderRelayProvider, DefenderRelaySigner } = require('@openzeppelin/defender-relay-client/lib/ethers');

exports.handler = async function(credentials) {
    const provider = new DefenderRelayProvider(credentials);
    const signer = new DefenderRelaySigner(credentials, provider, { speed: 'fast' });

    const contract = new ethers.Contract(VAULT_ADDRESS, VAULT_ABI, signer);

    // Проверяем условие перед паузой (избегаем ложные срабатывания)
    const tvl = await contract.totalAssets();
    const threshold = await contract.pauseThreshold();

    if (tvl < threshold) {
        const tx = await contract.pause();
        await tx.wait();
        console.log(`Paused. TVL: ${tvl}, Threshold: ${threshold}`);
    }
};

Sentinel настраивается на события из контракта (Transfer, Withdrawal) или на условия (баланс < X). При срабатывании — автоматически вызывает Autotask.

Forta Network

Forta — децентрализованная сеть detection ботов. Написать detection bot можно на TypeScript/Python:

import { Finding, HandleTransaction, TransactionEvent, FindingSeverity, FindingType } from 'forta-agent';

const handleTransaction: HandleTransaction = async (txEvent: TransactionEvent) => {
    const findings: Finding[] = [];

    // Проверяем крупные withdrawals из vault
    const withdrawalEvents = txEvent.filterLog(
        'Withdrawal(address,uint256,uint256)',
        VAULT_ADDRESS
    );

    for (const event of withdrawalEvents) {
        const amount = event.args.assets;
        if (amount > LARGE_WITHDRAWAL_THRESHOLD) {
            findings.push(
                Finding.fromObject({
                    name: 'Large Vault Withdrawal',
                    description: `${amount} assets withdrawn in single tx`,
                    alertId: 'VAULT-LARGE-WITHDRAWAL',
                    severity: FindingSeverity.High,
                    type: FindingType.Suspicious,
                    metadata: {
                        amount: amount.toString(),
                        recipient: event.args.receiver,
                    },
                })
            );
        }
    }

    return findings;
};

Forta алерты можно интегрировать в Defender через webhook → Autotask цепочку.

Circuit breaker паттерн

Более гибкий паттерн: не полная пауза, а circuit breaker — временное ограничение операций при аномалии:

contract CircuitBreaker {
    enum Status { Normal, Restricted, Paused }
    Status public status;

    uint256 public dailyWithdrawLimit;
    uint256 public dailyWithdrawn;
    uint256 public lastResetDay;

    function withdraw(uint256 amount) external {
        require(status != Status.Paused, "Paused");

        if (status == Status.Restricted) {
            // В restricted режиме — пониженный лимит
            require(amount <= restrictedWithdrawLimit, "Exceeds restricted limit");
        }

        // Daily limit check
        uint256 today = block.timestamp / 1 days;
        if (today > lastResetDay) {
            dailyWithdrawn = 0;
            lastResetDay = today;
        }

        dailyWithdrawn += amount;
        require(dailyWithdrawn <= dailyWithdrawLimit, "Daily limit exceeded");

        // ... withdraw logic
    }

    function _setRestricted() internal {
        status = Status.Restricted;
        emit StatusChanged(Status.Restricted);
    }
}

Преимущество circuit breaker перед полной паузой: при превышении дневного лимита протокол не паузируется — просто отклоняет транзакции сверх лимита. Пользователи могут продолжать работать в рамках нормального объёма. При активном эксплойте это ограничивает ущерб без полного останова.

MakerDAO, Compound, Aave используют похожие механизмы (supply/borrow caps, pause guardian).

Governance над pause механизмом

Pause функция сама по себе может стать вектором атаки: скомпрометированный guardian паузирует протокол, делая средства недоступными (DOS). Защита:

Time-limited pause: автоматическая распауза через N блоков, если governance не подтвердит продление:

uint256 public pauseExpiry;
uint256 public constant MAX_AUTO_PAUSE_DURATION = 1 days;

function pause() external onlyGuardian {
    _pause();
    pauseExpiry = block.timestamp + MAX_AUTO_PAUSE_DURATION;
}

function checkAutoUnpause() public {
    if (paused() && block.timestamp > pauseExpiry) {
        _unpause();
    }
}

Multisig pause с threshold: пауза требует 2-of-3 guardians, а не одного. Один скомпрометированный ключ недостаточен.

Сроки разработки: базовый Pausable с OZ Defender monitoring — 2-3 недели. Полная система с circuit breaker, Forta integration и governance механизмами — 5-7 недель.