Аудит безпеки смарт-контрактів

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

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

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

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

  • 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

Аудит безпеки смарт-контрактів

Смарт-контракт — це незмінний код, що керує реальними грошима. Одна помилка в логіці, неправильна перевірка доступу або непередбачена послідовність викликів можуть призвести до втрати коштів користувачів без можливості відкату. У 2023-2024 роках сукупні втрати від експлойтів смарт-контрактів перевищили $2 млрд. Аудит — це не формальність та не страховка: це єдиний систематичний спосіб виявити вразливості до того, як це роблять атакувальники.

Що відбувається під час аудиту

Аудит — це не просто запуск автоматичних інструментів. Це комбінація ручного аналізу, автоматизації та моделювання загроз, специфічних для протоколу. Сліпе довіри до звітів інструментів створює хибне почуття безпеки.

Ручний аналіз коду

Аудитор читає код як атакувальник. Ключові питання для кожної функції: чи може той, хто викликає, отримати чужі гроші? чи може повторний виклик дати більший результат? чи змінитимуться інваріанти системи?

Перевірка контролю доступу: кожна функція запису повинна мати явний контроль доступу. onlyOwner, onlyRole, перевірка msg.sender. Часта помилка — забута перевірка у функції initialize оновлюваного контракту.

Перевірка інваріантів: для кожного контракту формулюються інваріанти — властивості, які завжди мають залишатися істинними. Наприклад: «сума балансів усіх користувачів ≤ totalSupply». Аудитор перевіряє, чи можна порушити кожний інваріант.

Автоматизований аналіз

Slither (Trail of Bits) — статичний аналізатор для Solidity. Запускається швидко, ловить типові паттерни вразливостей:

slither . --config-file slither.config.json \
  --exclude naming-convention,solc-version \
  --print human-summary

Slither виявляє: reentrancy через різні паттерни, неініціалізовані змінні у прокси-контрактах, небезпечні delegatecall дзвінки, переповнення арифметики (хоча Solidity 0.8.x додав вбудовані перевірки), затіненні змінні.

Mythril — символічне виконання. Аналізує всі можливі шляхи виконання. Повільніший за Slither, але ловить складніші паттерни:

myth analyze --solc-json mythril.json contracts/Protocol.sol \
  --execution-timeout 120 \
  --max-depth 22

Echidna — фаззер для тестування на основі властивостей. Ви описуєте інваріанти як функції Solidity, Echidna генерує мільйони випадкових транзакцій, намагаючись їх порушити:

// Echidna інваріант: totalSupply ніколи не перевищує MAX_SUPPLY
function echidna_total_supply_bound() public view returns (bool) {
    return token.totalSupply() <= token.MAX_SUPPLY();
}

Foundry інваріант-тестування — інтегровано у Foundry, подібний підхід:

// test/invariants/InvariantTest.sol
contract InvariantTest is Test {
    function invariant_solvency() public {
        assertLe(
            vault.totalDebt(),
            vault.totalAssets(),
            "Vault insolvent"
        );
    }
}

Fork-тестування

Для протоколів, що взаємодіють з існуючими DeFi примітивами (Uniswap, Aave, Compound), аудит включає fork-тестування на снимку mainnet. Це дозволяє тестувати реальні взаємодії з реальними станами протоколів.

# Foundry fork тест
forge test --fork-url https://eth-mainnet.g.alchemy.com/v2/KEY \
  --fork-block-number 19000000 \
  -vvv --match-contract AttackSimulation

Класифікація вразливостей

Критичні: прямі втрати коштів

Reentrancy: класична атака. Контракт викликає зовнішню адресу перед оновленням стану. Контракт атакувальника, отримавши ETH, повторно викликає вразливу функцію.

// Вразливий код
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool success,) = msg.sender.call{value: amount}(""); // зовнішній виклик
    require(success);
    balances[msg.sender] -= amount; // оновлення стану ПІСЛЯ дзвінка
}

// Правильно: паттерн Checks-Effects-Interactions
function withdraw(uint256 amount) external nonReentrant {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount; // спочатку оновити стан
    (bool success,) = msg.sender.call{value: amount}("");
    require(success);
}

Взлом DAO (2016, $60M), Lendf.Me (2020, $25M), багато інших — reentrancy. nonReentrant модифікатор з OpenZeppelin та паттерн CEI — стандарт. Немає виправдань не використовувати.

Маніпуляція цінового оракула: протокол використовує спотову ціну з AMM пула як оракул. Атакувальник через flash-loan маніпулює ціною пула, позичає або ліквідує за штучною ціною.

Захист: TWAP (Time-Weighted Average Price) замість спотової ціни. Uniswap v3 TWAP, Chainlink — прийнятні оракули. Спотова ціна з пула — ні.

Логічні помилки у фінансових розрахунках: неправильний порядок операцій при цілочисельному діленні, неправильний облік decimals різних токенів, округлення на користь користувача (не протоколу) — накопичені помилки.

Високі: значний ущерб у певних умовах

Обхід контролю доступу: обхід перевірок через неочевидні шляхи. Приклад: initialize() в оновлюваному контракті викликається без модифікатора initializer — можна переініціалізувати з іншим власником.

Front-running: атакувальник бачить транзакцію у mempool і вставляє свою перед нею. Класика: DEX атаки slippage, обхід перевірок deadline.

Маніпуляція ціни flash-loan: описано вище, але ширша категорія — будь-які маніпуляції через тимчасовий капітал.

Невідмічені значення повернення: token.transfer() для нестандартного ERC20 (USDT) повертає bool. Якщо не перевірити — мовчазна відмова.

// Небезпечно
token.transfer(recipient, amount);

// Правильно
require(token.transfer(recipient, amount), "Transfer failed");
// або використовувати SafeERC20:
token.safeTransfer(recipient, amount);

Середні: обмежена шкода або потребують специфічних умов

Заперечення обслуговування: gas griefing через великі масиви, блокування через revert у callback, необмежені цикли.

Залежність від часу блоку: використання block.timestamp для критичної логіки. Валідатор може маніпулювати timestamp в межах ~15 секунд.

Переповнення/недоповнення цілих чисел: у Solidity < 0.8.0 без SafeMath. У 0.8.x вбудовані перевірки, але блоки unchecked {} можуть створити проблему знову.

Низькі та інформаційні

Стилістичні проблеми, потенційні оптимізації, невідповідність документації коду, відсутні события для важливих операцій.

Специфіка аудиту оновлюваних контрактів

Прокси-паттерни (TransparentProxy, UUPS, Beacon) додають додаткову поверхню атаки:

Колізія storage: прокси та реалізація поділяють один простір storage. Якщо layout не збігається — змінні перезаписуються.

// OpenZeppelin рекомендує паттерн ERC-7201 namespace:
// keccak256(abi.encode(uint256(keccak256("myprotocol.main")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant MAIN_STORAGE_LOCATION = 0x...;

struct MainStorage {
    uint256 totalSupply;
    mapping(address => uint256) balances;
}

function _getMainStorage() private pure returns (MainStorage storage $) {
    assembly { $.slot := MAIN_STORAGE_LOCATION }
}

Неініціалізована реалізація: прямий виклик initialize() на контракті реалізації (не через прокси) може скомпрометувати систему. Рішення: _disableInitializers() у конструкторі реалізації.

UUPS: відсутня функція upgrade у новій реалізації: якщо задеплоїти реалізацію без upgradeTo — контракт назавжди втрачає можливість оновлення.

Типова програма аудиту

Підготовка: фінальна версія коду (заморозка функцій), документація архітектури, опис моделі загроз, тест-кейси. Без фінального коду аудит бессмислений — зміни після аудиту не покриваються.

Автоматизований аналіз (дні 1-2): Slither, Mythril, Echidna — збір автоматичних результатів, виключення хибних позитивів.

Ручний аналіз (дні 3-12): систематичний перегляд коду, моделювання атак, перевірка інваріантів, бізнес-логіка.

Тестування (дні 8-14, паралельно): PoC експлойти для знайдених вразливостей, fork-тестування взаємодій.

Звіт та виправлення (дні 14-20): детальний звіт з критичністю, PoC, рекомендаціями. Команда виправляє, аудитор перевіряє виправлення.

Перегляд виправлень (3-5 днів): перевірка, що виправлення не вводять нові вразливості.

Тип протоколу Обсяг коду Часова шкала аудиту
Простий ERC-20 + vesting < 500 рядків 1-2 тижні
DeFi протокол (кредитування/AMM) 1000-3000 рядків 3-5 тижнів
Складний протокол з прокси 3000-10000 рядків 5-8 тижнів
Крос-ланцюгові мости будь-який обсяг 6-12 тижнів

Що аудит не гарантує

Аудит зменшує ризик, але не усуває його повністю. Аудитори — люди; вони пропускають баги. Найвідоміші експлойти: Euler Finance ($197M, 2023), Nomad Bridge ($190M, 2022), Wormhole ($320M, 2022) — усі пройшли аудити.

Правильна стратегія: аудит + bug bounty (Immunefi, HackerOne) + поступовий rollout з ліміт TVL + мониторинг (Forta, OpenZeppelin Defender).

Аудит є необхідною, але недостатньою умовою для безпеки.