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

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

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

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

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1217
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    919
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1146
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    609
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    884

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

Формальна верифікація — це математичне доведення коректності програми. Не «ми перевірили всі тестові випадки», а «ми довели, що для будь-яких вхідних даних контракт поводиться відповідно до специфікації». Різниця критична: тести знаходять наявність помилок, верифікація доводить їхню відсутність. MakerDAO, Aave, Compound використовують формальну верифікацію для критичних компонентів. Розглянемо, як це працює на практиці.

Інструменти формальної верифікації

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 намагається знайти контрприклад — набір вхідних даних, при яких assertion порушується. Якщо контрприклад не знайдено за дане час — специфікацію вважають доказаною.

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");

        // SMTChecker автоматично перевіряє:
        // - немає переповнення в balances[msg.sender] += amount
        // - немає переповнення в totalDeposited += amount
        // - інваріант: totalDeposited = Σ balances[i]

        balances[msg.sender] += amount;
        totalDeposited += amount;
    }

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

        balances[msg.sender] -= amount;
        totalDeposited -= amount;
        // assert не потрібен — SMTChecker перевіряє що underflow неможливий
    }
}

Запуск через Hardhat:

npx hardhat compile --config hardhat.smtchecker.config.ts
// hardhat.smtchecker.config.ts
export default {
    solidity: {
        version: "0.8.20",
        settings: {
            modelChecker: {
                engine: "chc",        // Constrained Horn Clauses — найпотужніший
                targets: [
                    "assert",
                    "overflow",
                    "underflow",
                    "divByZero",
                    "constantCondition",
                    "balance"
                ],
                timeout: 20000,       // мілісекунди на кожну перевірку
                showUnproved: true
            }
        }
    }
};

Halmos

Halmos — символічне виконання для EVM, написан на Python. Працює з існуючими тестами Foundry — символічно виконує їх для всіх можливих вхідних даних:

// Тест Foundry стає формальною верифікацією у Halmos
// Запуск: halmos --contract TestVault --function testFormal

contract TestVault is Test {
    Vault vault;

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

    // Halmos запустить це для ВСІХ можливих значень amount та caller
    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);

        // Для всіх можливих amount та caller: після deposit+withdraw
        // баланс восстановлюється (з урахуванням комісій)
        assertGe(token.balanceOf(caller), amount * 99 / 100);
    }
}

Специфікація: найскладніша частина

Інструменти — це засоби. Головна робота — написання специфікації. Погана специфікація доведе, що контракт коректен відповідно до неправильних вимог.

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

Властивості безпеки («погане ніколи не відбувається»):

  • Баланс ніколи не йде у мінус
  • totalSupply ніколи не перевищує MAX_SUPPLY
  • Тільки власник може викликати pause()
  • Reentrancy guard працює коректно

Властивості життєвості («хороше врешті-решт відбувається»):

  • Якщо користувач внесе кошти, він може їх вивести
  • Пропозиції врешті-решт виконуються або відхиляються
  • Staker врешті-решт отримує нагороди

Інваріанти («завжди істинно»):

  • Σ balances = totalSupply (збереження токенів)
  • lockedAmount <= totalDeposited
  • Ціна оракула завжди > 0

Приклад повної специфікації для протоколу кредитування

// Специфікація для спрощеного Aave-подібного протоколу

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; // health factor >= 1.0

    env e;
    liquidate@withrevert(e, borrower);

    assert lastReverted, "Healthy borrower should not be liquidatable";
}

// Інваріант: сума боргів не перевищує суму заставок (з коефіцієнтом LTV)
invariant solvencyInvariant(address user)
    debt(user) * 100 <= collateral(user) * MAX_LTV_PERCENT
    filtered { f -> !f.isView }

// Reentrancy: стан не може змінитися дважды у одній транзакції
rule noReentrancy(method f) {
    uint256 collateralBefore = collateral(currentContract);

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

    uint256 collateralAfter = collateral(currentContract);

    // Баланс змінився точно як очікується
    assert collateralAfter >= collateralBefore || /* withdrawal */
           collateralAfter <= collateralBefore; // deposit
}

Обмеження формальної верифікації

Формальна верифікація не є панацеєю:

Прогалина повноти: верифікується тільки те, що вказано у специфікації. Якщо атакувальник знайде вектор, не охоплений специфікацією — верифікація його не виявить. Це трапилося з деякими «верифікованими» протоколами.

Масштабованість: великі контракти (> 1000 рядків) важко верифікувати повністю. Інструменти можуть не завершити за розумний час (timeout). Рішення — верифікувати критичні компоненти окремо.

Припущення оракула: якщо контракт використовує оракул, верифікація припускає, що оракул повертає коректні дані. Неправильний оракул — поза межами верифікації.

Зовнішні дзвінки: взаємодія з зовнішніми контрактами важко специфікувати повністю. Certora вимагає явного вказання поведінки зовнішніх дзвінків через summarization.

Процес верифікаційного аудиту

Повний процес формальної верифікації:

Етап 1 — Специфікація вимог: спільно з командою протоколу визначаємо критичні властивості, які мають тримати при будь-яких умовах.

Етап 2 — Написання CVL/Halmos специфікацій: 2-4 тижні для типового DeFi протоколу. Охоплюємо safety інваріанти, контроль доступу, економічні інваріанти.

Етап 3 — Ітеративна верифікація: запуск Prover, аналіз контрприкладів, уточнення специфікації або виправлення коду. Це ітеративний процес.

Етап 4 — Звіт: список верифікованих властивостей, знайдених порушень, обмежень верифікації.

Типова вартість верифікаційного аудиту — $50k-$200k залежно від складності протоколу. Суттєво дорожче, ніж звичайний аудит, але виправдано для контрактів з $100M+ TVL: вартість знаходження однієї критичної вразливості вартістю $10k у специфікації може запобігти втраті $10M.

Тип перевірки Що знаходить Вартість Час
Unit тести Конкретні сценарії Низька 1-2 тижні
Fuzz тестування Випадкові вхідні дані Низька 1 тиждень
Ручний аудит Логічні помилки Середня 2-4 тижні
Формальна верифікація Математичне доведення Висока 4-8 тижнів

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