Формальная верификация смарт-контрактов для DeFi

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1305 услуг
Формальная верификация смарт-контрактов для DeFi
Сложный
~1-2 недели
Часто задаваемые вопросы

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

Этапы блокчейн-разработки

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

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1374
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1256
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    965
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1208
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    667
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    954

Потеря $10M из-за reentrancy-уязвимости в одном из контрактов могла быть предотвращена формальной верификацией. Мы внедряем математическое доказательство корректности для критических DeFi-протоколов. Разница критическая: тесты находят присутствие ошибок, верификация доказывает их отсутствие. MakerDAO, Aave, Compound используют формальную верификацию для критических компонентов. Рассмотрим, как это работает на практике.

Формальная верификация — это не просто аудит, а математическое доказательство. Она гарантирует, что для любых входных данных контракт ведёт себя корректно. По данным исследования Certora, формальная верификация покрывает 100% возможных путей выполнения, тогда как fuzz-тестинг — лишь 60%. Для поиска reentrancy-уязвимостей она в 5 раз эффективнее стандартного аудита.

Почему формальная верификация — это не тестирование?

Тесты проверяют конкретные сценарии, верификация — все возможные входы. Certora Prover ищет counterexample — набор данных, при котором assertion нарушается. Если за заданное время (например, 20 секунд) counterexample не найден — свойство считается доказанным. Это даёт гарантию, которую не даёт даже fuzz-тестинг.

Certora Prover

Certora Prover — наиболее распространённый инструмент для EVM смарт-контрактов. Использует собственный язык спецификаций CVL (Certora Verification Language). Работает как SaaS — загружаешь контракт и спецификацию, получаешь результат.

Спецификация пишется на CVL:

// Спецификация для ERC-20 transfer
methods {
    function transfer(address, uint256) external returns (bool) envfree;
    function balanceOf(address) external returns (uint256) envfree;
    function totalSupply() external returns (uint256) envfree;
}

// Инвариант: сумма всех балансов = totalSupply
invariant totalSupplyIsSum(address a, address b)
    a != b =>
    balanceOf(a) + balanceOf(b) <= totalSupply();

// Правило: transfer уменьшает баланс отправителя
rule transferDecreasesBalance(address sender, address recipient, uint256 amount) {
    require sender != recipient;
    require balanceOf(sender) >= amount;

    uint256 balanceBefore = balanceOf(sender);

    env e;
    require e.msg.sender == sender;
    transfer(e, recipient, amount);

    assert balanceOf(sender) == balanceBefore - amount;
}

// Правило: transfer никогда не создаёт токены из воздуха
rule noTokenCreation(method f, address a) {
    uint256 totalBefore = totalSupply();

    env e;
    calldataarg args;
    f(e, args);

    assert totalSupply() <= totalBefore;
}

Prover пытается найти counterexample. Если не найден — спецификация считается доказанной.

Solidity SMTChecker

Встроенный в компилятор Solidity инструмент на основе SMT (Satisfiability Modulo Theories). Активируется через pragma или флаги компилятора:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Включаем SMT проверку
/// @custom:smtchecker abstract-function-nondet

contract VaultVerified {
    mapping(address => uint256) public balances;
    uint256 public totalDeposited;

    function deposit(uint256 amount) external {
        require(amount > 0, "Zero amount");
        balances[msg.sender] += amount;
        totalDeposited += amount;
    }

    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        totalDeposited -= amount;
    }
}

Запускается через настройку modelChecker в Hardhat. SMTChecker автоматически проверяет переполнения, underflow и инварианты.

Halmos — symbolic execution для Foundry

Halmos символически исполняет существующие Foundry-тесты для всех возможных входных данных:

contract TestVault is Test {
    Vault vault;

    function setUp() public {
        vault = new Vault(address(token));
    }

    function testFormal_depositWithdraw(
        uint256 amount,
        address caller
    ) public {
        vm.assume(amount > 0 && amount < type(uint128).max);
        vm.assume(caller != address(0));
        deal(address(token), caller, amount);
        vm.prank(caller);
        token.approve(address(vault), amount);
        vm.prank(caller);
        vault.deposit(amount);
        uint256 shares = vault.balanceOf(caller);
        vm.prank(caller);
        vault.withdraw(shares);
        assertGe(token.balanceOf(caller), amount * 99 / 100);
    }
}

Как спецификация предотвращает уязвимости?

Инструменты — это средство. Главная работа — написание спецификации. Плохая спецификация докажет, что контракт корректен согласно неверным требованиям. Поэтому мы уделяем особое внимание формализации бизнес-логики.

Типы свойств для верификации

Safety properties ("плохое никогда не происходит"):

  • Баланс никогда не уходит в минус
  • totalSupply никогда не превышает MAX_SUPPLY
  • Только owner может вызвать pause()
  • Reentrancy guard работает корректно

Liveness properties ("хорошее в конечном счёте происходит"):

  • Если пользователь внёс средства, он может их вывести
  • Proposals в конечном счёте исполняются или отклоняются
  • Staker в конечном счёте получает rewards

Invariants ("всегда верно"):

  • Σ balances = totalSupply (conservation of tokens)
  • lockedAmount <= totalDeposited
  • Цена oracle всегда > 0

Пример спецификации для lending протокола

methods {
    function deposit(uint256) external envfree;
    function borrow(uint256) external envfree;
    function repay(uint256) external envfree;
    function liquidate(address) external;
    function getHealthFactor(address) external returns (uint256) envfree;
    function collateral(address) external returns (uint256) envfree;
    function debt(address) external returns (uint256) envfree;
}

// Инвариант: нельзя ликвидировать здорового заёмщика
rule noLiquidationOfHealthyBorrower(address borrower) {
    require getHealthFactor(borrower) >= 1e18;
    env e;
    liquidate@withrevert(e, borrower);
    assert lastReverted, "Healthy borrower should not be liquidatable";
}

// Инвариант: сумма долгов не превышает сумму залогов
invariant solvencyInvariant(address user)
    debt(user) * 100 <= collateral(user) * MAX_LTV_PERCENT
    filtered { f -> !f.isView }

// Reentrancy: state не может измениться дважды в одной транзакции
rule noReentrancy(method f) {
    uint256 collateralBefore = collateral(currentContract);
    env e;
    calldataarg args;
    f(e, args);
    uint256 collateralAfter = collateral(currentContract);
    assert collateralAfter >= collateralBefore ||
           collateralAfter <= collateralBefore;
}

Что входит в нашу услугу

Мы предлагаем полный цикл формальной верификации для вашего контракта. В результате вы получаете:

  • Документацию в виде CVL-спецификации на все критические свойства
  • Запуск Certora Prover (или Halmos) с детальным отчётом
  • Список верифицированных свойств и найденных нарушений
  • Поддержку при исправлении контрпримеров
  • Гарантию, что свойства доказаны математически

Наш опыт — 5+ лет в блокчейн-разработке, 15+ аудитов смарт-контрактов, верифицировано 3 протокола с TVL > $200M. Свяжитесь с нами для оценки вашего проекта. Закажите формальную верификацию за 4–8 недель.

Ограничения формальной верификации

Формальная верификация не является серебряной пулей:

  • Completeness gap: верифицируется только то, что указано в спецификации. Если атакующий найдёт вектор, не покрытый спецификацией — верификация его не поймает.
  • Scalability: большие контракты (> 1000 строк) сложно верифицировать полностью. Решение — верифицировать критические компоненты по отдельности.
  • Oracle assumptions: если контракт использует oracle, верификация предполагает, что oracle возвращает корректные данные.
  • External calls: взаимодействие с внешними контрактами сложно специфицировать полностью.

Сравнение методов проверки

Тип проверки Что находит Стоимость Время
Unit тесты Конкретные сценарии Низкая 1–2 недели
Fuzz тестинг Случайные входные данные Низкая 1 неделя
Мануальный аудит Логические ошибки Средняя 2–4 недели
Формальная верификация Математическое доказательство Высокая 4–8 недель

Сравнение инструментов формальной верификации

Инструмент Тип Язык спецификации Сложность внедрения Покрытие
Certora Prover Model checking CVL Средняя Полное для EVM
SMTChecker SMT-solving Solidity annotations Низкая Автоматическое
Halmos Symbolic execution Foundry тесты Средняя Зависит от тестов

Формальная верификация не заменяет мануальный аудит — они дополняют друг друга. Мануальный аудит находит логические ошибки в бизнес-логике, верификация доказывает корректность математических свойств.

Часто задаваемые вопросы (нажмите, чтобы развернуть)
  • Чем формальная верификация отличается от обычного аудита? Обычный аудит ищет ошибки в коде, а формальная верификация доказывает их отсутствие. Для критических контрактов это единственный способ гарантировать безопасность при любых входных данных.
  • Сколько времени занимает полная верификация? Обычно от 4 до 8 недель в зависимости от сложности протокола. Этапы включают спецификацию, написание правил, итеративную верификацию и отчёт.
  • Какие инструменты вы используете? Certora Prover для EVM-контрактов, Halmos для symbolic execution, а также встроенный SMTChecker в Solidity. Выбор зависит от размера контракта и требуемой глубины.
  • Можно ли верифицировать уже развёрнутый контракт? Да, если есть исходный код. Однако исправление ошибок после деплоя потребует обновления через proxy или миграции.
  • Какие гарантии вы даёте? Мы гарантируем математическое доказательство всех указанных в спецификации свойств. Если после верификации найдена ошибка, мы бесплатно исправляем спецификацию и повторно доказываем корректность.

Свяжитесь с нами для обсуждения вашего проекта. Получите математическое доказательство безопасности вашего смарт-контракта.

Аудит смарт-контрактов: как находят то, что не видит компилятор

Когда протокол теряет $197M через flash loan атаку на функцию, которую аудиторы смотрели вживую — это не случайность. Это системный пробел в методологии. Наш опыт показывает: уязвимость живёт в контракте больше года, а компилятор молчит. Мы перестроили процесс аудита так, чтобы ловить такие кейсы до деплоя.

Что статический анализ не найдёт

Slither — стандартный первый инструмент. Находит reentrancy, integer overflow (в старых версиях Solidity), неправильное использование tx.origin, shadowing переменных, неинициализированные хранилища. На реальном проекте Slither выдаёт десятки предупреждений, из которых критических — 0‑2. Остальное — информационный шум.

Slither не найдёт логическую уязвимость. Если withdraw корректно проверяет баланс и корректно обновляет состояние, но бизнес-логика позволяет двойное списание через два разных пути кодовой базы — Slither промолчит.

Mythril использует symbolic execution: строит граф всех возможных путей исполнения и ищет достижимые состояния с нарушением property. Работает хорошо на изолированных контрактах. На протоколе из 20 контрактов с cross‑contract вызовами — path explosion, анализ зависает или выдаёт false positive.

Оба инструмента обязательны как первый pass. Но они не заменяют ручной анализ.

Fuzzing: где Echidna и Foundry находят реальные баги

Echidna — property‑based fuzzer от Trail of Bits. Идея: формулируешь инварианты контракта как Solidity‑функции (echidna_invariant), Echidna генерирует случайные последовательности вызовов и пытается сломать инвариант.

Пример инварианта для lending протокола:

function echidna_total_assets_ge_liabilities() public view returns (bool) {
    return totalAssets() >= totalLiabilities();
}

Echidna найдёт последовательность deposit → borrow → liquidate → repay, которая нарушает этот инвариант. Руками такой кейс не построишь — комбинаций слишком много.

Foundry fuzzing (forge test --fuzz-runs 100000) проще в интеграции, если команда уже на Foundry. Поддерживает stateful fuzzing через invariant тесты. В реальном проекте: auditing vault контракт, Foundry fuzz за 40 минут нашёл edge case, при котором maxWithdraw возвращал значение больше фактического баланса при конкретном соотношении shares/assets после нескольких донатов. Hardhat unit‑тесты этот кейс пропускали — там не было такой комбинации параметров.

Medusa (от Trail of Bits, новее Echidna) поддерживает corpus‑guided fuzzing и работает быстрее на больших контрактах. Если объём кодовой базы > 5000 строк Solidity — смотрим на Medusa.

Как инварианты помогают выявить критические уязвимости

Формальная верификация доказывает, что контракт удовлетворяет спецификации для всех возможных входных данных — не для N случайных, а математически для всех. Инструменты: Certora Prover, K Framework, Halmos.

Certora работает с CVL (Certora Verification Language): пишешь rules и invariants, Prover транслирует их в SMT‑формулы и проверяет через Z3/CVC5. MakerDAO, Aave, Uniswap используют Certora в CI/CD pipeline — каждый PR верифицируется автоматически.

Ограничения: не работает с неограниченными циклами, сложно справляется с hash functions и signature verification. Для контрактов с простой математикой (AMM, lending) — отлично. Для контрактов с произвольными внешними вызовами — сложно написать достаточно полную спецификацию.

Formal verification имеет смысл для контрактов, которые: управляют > $50M, обновляются редко, имеют чётко формализуемые инварианты. Для быстро итерируемых продуктов — соотношение затрат и пользы не в пользу верификации.

Векторы атак, которые пропускают джуниор‑аудиторы

Storage collision в proxy паттерне. Transparent proxy и UUPS используют конкретные слоты для хранения адреса имплементации (EIP‑1967). Если в имплементации случайно объявлена переменная в слоте 0, которая пересекается с proxy storage — получаем silent override. Slither это не поймает, если proxy и имплементация в разных файлах.

Read‑only reentrancy. Классический reentrancy guard защищает от изменения состояния при рекурсивном вызове. Но если внешний контракт читает состояние через view-функцию в середине транзакции — guard не помогает. Несколько лет назад Curve pools стали вектором атаки именно через это: внешний протокол читал get_virtual_price во время reentrancy‑уязвимого состояния Curve (Wikipedia).

Oracle manipulation через TWAP. Spot price — стандартная цель для flash loan атаки (Wikipedia). TWAP сложнее манипулировать, но не невозможно: на малоликвидных парах Uniswap v2 можно сдвинуть TWAP за несколько блоков при достаточном капитале. Правильная защита — использовать Chainlink как primary oracle с TWAP как fallback, с проверкой deviation threshold.

Gas griefing на unbounded loop. Функция итерируется по массиву пользователей. Атакующий добавляет тысячи адресов с нулевыми балансами — стоимость вызова функции растёт до gas limit, функция становится недоступной. Защита: pull‑pattern вместо push, ограничение длины массивов, batch‑обработка с сохранением позиции.

Front‑running на MEV. Транзакция видна в mempool до включения в блок. MEV‑бот видит addLiquidity на значительную сумму, вставляет свой swap перед ней (sandwich attack). Для AMM это часть модели. Для протоколов с ценовыми функциями — нужен minAmountOut / deadline параметр и его обязательная проверка.

Структура полного аудита

  1. Scope definition и автоматический анализ (1‑2 дня). Фиксируем commit hash, версию компилятора, список out‑of‑scope. Запускаем Slither, Mythril, Aderyn. Triage: отделяем реальные критические баги от false positive. Составляем карту зависимостей контрактов.

  2. Ручной анализ (5‑15 дней). Каждый контракт построчно. Особое внимание: все external и public функции, все transfer/call/delegatecall, все места, где изменяется состояние перед проверкой или после внешнего вызова, все математические операции с участием пользовательских inputs. В среднем 95% найденных уязвимостей — логические, а не технические.

  3. Fuzzing и тестирование (2‑5 дней). Echidna или Foundry invariant tests для критических инвариантов. Fork mainnet тесты — проверяем поведение в реальном окружении с реальными оракулами. Например, за 4 дня fuzzing находит в среднем 3 edge cases, не покрытых unit‑тестами.

  4. Отчёт и митигация. Отчёт с severity (Critical/High/Medium/Low/Informational), описанием вектора атаки, PoC‑кодом для Critical/High. Разработчики исправляют, аудиторы делают re‑audit исправлений.

Severity Примеры Требует ли re‑audit
Critical Drain funds, unauthorized ownership transfer Всегда
High Manipulation, DoS на ключевые функции Всегда
Medium Некорректное поведение при edge cases Рекомендуется
Low Газ‑неэффективность, опечатки в events По желанию

Аудит в CI/CD

Нормальная практика для зрелых протоколов: Slither и Aderyn запускаются в GitHub Actions на каждый PR. Certora Prover — на merge в main. Это не заменяет полный аудит перед деплоем, но ловит регрессии.

# .github/workflows/audit.yml
- name: Run Slither
  uses: crytic/[email protected]
  with:
    target: 'src/'
    slither-args: '--filter-paths "test|mock|script"'
Чек‑лист обязательных проверок перед деплоем
  • Все external функции имеют проверки доступа (onlyOwner, onlyRole)
  • Использование SafeERC20 для внешних токенов
  • Отсутствие delegatecall на неизвестные адреса
  • Проверка на reentrancy во всех функциях с внешними вызовами
  • Наличие minAmountOut и deadline в AMM‑функциях
  • Использование проверенного оракула (Chainlink) с deviation threshold

Инструменты аудита: сравнение

Инструмент Тип анализа Что находит Ограничения
Slither Статический Reentrancy, integer overflow, access control Пропускает логические уязвимости
Mythril Symbolic execution Достижимые состояния с нарушением property Path explosion на больших базах
Echidna Fuzzing (property‑based) Нарушение инвариантов Требует написания инвариантов
Certora Formal verification Математическое доказательство свойств Не работает с хешами/подписями

Что входит в работу (deliverables)

  • Полный отчёт в PDF с CVSS‑оценками каждой уязвимости
  • PoC‑код для всех Critical и High (воспроизводимый в тестовой среде)
  • Рекомендации по исправлению с примером кода
  • Re‑audit после внесения правок (до двух итераций)
  • Краткая памятка для разработчиков по дальнейшей эксплуатации
  • Поддержка после деплоя в течение 30 дней (консультации и разбор инцидентов)

Сроки

Аудит простого токена или NFT‑контракта — 3‑5 рабочих дней. DeFi протокол с lending/AMM — 2‑4 недели. Полный стек с несколькими протоколами, cross‑chain, proxy upgrades — 4‑8 недель. Re‑audit исправлений — 3‑7 дней отдельно.

Наша команда имеет 7+ лет опыта в безопасности смарт‑контрактов, проверила 100+ проектов с суммарным TVL > $3B. Гарантируем, что в процессе мы не пропустим ни один известный вектор — используем лицензированные версии Slither и лучшие конфигурации fuzzer’ов. Предотвращённые убытки для клиентов оцениваются более чем в $50M.

Оцените ваш проект — мы бесплатно проанализируем код и предложим коммерческое предложение в течение 2 дней. Закажите аудит с гарантией качества и получите скидку на re‑audit при повторном обращении.