Аудит безопасности смарт-контрактов
Смарт-контракт — это immutable код, управляющий реальными деньгами. Одна ошибка в логике, некорректная проверка доступа или непредвиденная последовательность вызовов могут привести к потере средств пользователей без возможности отката. В 2023-2024 годах совокупные потери от эксплойтов смарт-контрактов составили более $2 млрд. Аудит — не формальность и не страховка: это единственный систематический способ обнаружить уязвимости до того, как это сделают атакующие.
Что происходит во время аудита
Аудит — это не просто запуск автоматических инструментов. Это комбинация ручного анализа, автоматизации и моделирования угроз, специфичных для данного протокола. Слепое доверие к инструментальным отчётам даёт ложное чувство безопасности.
Ручной анализ кода
Аудитор читает код как злоумышленник. Ключевые вопросы для каждой функции: может ли вызывающий получить средства не своё? может ли повторный вызов дать больший результат? изменятся ли инварианты системы?
Проверка access control: каждая write-функция должна иметь явный контроль доступа. onlyOwner, onlyRole, проверка msg.sender. Частая ошибка — забытая проверка в initialize функции upgradeable контракта.
Проверка инвариантов: для каждого контракта формулируются инварианты — свойства, которые должны оставаться истинными всегда. Например: «сумма балансов всех пользователей ≤ totalSupply». Аудитор проверяет, можно ли нарушить каждый инвариант.
Автоматизированный анализ
Slither (Trail of Bits) — статический анализатор для Solidity. Запускается быстро, ловит паттерны типичных уязвимостей:
slither . --config-file slither.config.json \
--exclude naming-convention,solc-version \
--print human-summary
Slither детектирует: reentrancy через различные паттерны, неинициализированные переменные в proxy контрактах, небезопасные вызовы delegatecall, арифметические переполнения (хотя Solidity 0.8.x добавил встроенные checks), shadowed переменные.
Mythril — символическое исполнение. Анализирует все возможные пути выполнения. Медленнее Slither, но ловит более сложные паттерны:
myth analyze --solc-json mythril.json contracts/Protocol.sol \
--execution-timeout 120 \
--max-depth 22
Echidna — фаззер для свойственного тестирования (property-based testing). Вы описываете инварианты как Solidity функции, Echidna генерирует миллионы случайных транзакций пытаясь нарушить их:
// Echidna invariant: totalSupply никогда не превышает MAX_SUPPLY
function echidna_total_supply_bound() public view returns (bool) {
return token.totalSupply() <= token.MAX_SUPPLY();
}
Foundry invariant testing — интегрирован в 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 testing на mainnet snapshot. Это позволяет тестировать реальные взаимодействия с реальным состоянием протоколов.
# Foundry fork test
forge test --fork-url https://eth-mainnet.g.alchemy.com/v2/KEY \
--fork-block-number 19000000 \
-vvv --match-contract AttackSimulation
Классификация уязвимостей
Critical: прямая потеря средств
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);
}
The DAO hack (2016, $60M), Lendf.Me (2020, $25M), множество других — reentrancy. nonReentrant modifier от OpenZeppelin и CEI паттерн — стандарт. Нет оправданий не использовать.
Price oracle manipulation: протокол использует spot price из AMM пула как oracle. Атакующий через flash loan манипулирует ценой в пуле, занимает или ликвидирует по искусственной цене.
Защита: TWAP (Time-Weighted Average Price) вместо spot price. Uniswap v3 TWAP, Chainlink — приемлемые оракулы. Spot price из пула — нет.
Logic errors в финансовых расчётах: неправильный порядок операций при целочисленном делении, некорректный учёт decimals разных токенов, рounding в пользу пользователя (не протокола) — накопленные ошибки.
High: значительный ущерб при определённых условиях
Access control bypass: обход проверок через неочевидные пути. Пример: initialize() в upgradeable контракте вызывается без initializer модификатора — можно переинициализировать с другим owner.
Front-running: атакующий видит транзакцию в mempool и вставляет свою перед ней. Классика: DEX slippage атаки, обход deadline проверок.
Flash loan price manipulation: описано выше, но более широкая категория — любые манипуляции через временный капитал.
Unchecked return values: token.transfer() для non-standard ERC20 (USDT) возвращает bool. Если не проверить — silent failure.
// Опасно
token.transfer(recipient, amount);
// Правильно
require(token.transfer(recipient, amount), "Transfer failed");
// или использовать SafeERC20:
token.safeTransfer(recipient, amount);
Medium: ограниченный ущерб или требующие специфических условий
Denial of Service: gas griefing через большие массивы, блокировка через revert в callback, unbounded loops.
Timestamp dependence: использование block.timestamp для критической логики. Валидатор может манипулировать timestamp в пределах ~15 секунд.
Integer overflow/underflow: в Solidity < 0.8.0 без SafeMath. В 0.8.x встроенные checks, но unchecked {} блоки могут снова создать проблему.
Low и Informational
Стилистические проблемы, потенциальные оптимизации, несоответствие документации коду, отсутствие событий для важных операций.
Специфика аудита upgradeable контрактов
Proxy паттерны (TransparentProxy, UUPS, Beacon) добавляют дополнительный attack surface:
Storage collision: proxy и implementation используют одно storage пространство. Если layout не совпадает — переменные перезаписываются.
// OpenZeppelin рекомендует использовать ERC-7201 namespace pattern:
// 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 }
}
Uninitialized implementation: прямой вызов initialize() на implementation контракте (не через proxy) может скомпрометировать систему. Решение: _disableInitializers() в constructor implementation.
UUPS: отсутствие upgrade функции в новой реализации: если задеплоить implementation без upgradeTo — контракт навсегда теряет возможность обновления.
Типичная программа аудита
Подготовка: финальная версия кода (feature freeze), документация архитектуры, описание threat model, тест-кейсы. Без финального кода аудит бессмысленен — изменения после аудита не покрываются.
Автоматизированный анализ (день 1-2): Slither, Mythril, Echidna — сбор автоматических findings, исключение false positives.
Ручной анализ (день 3-12): систематический просмотр кода, моделирование атак, проверка инвариантов, бизнес-логика.
Тестирование (день 8-14, параллельно): PoC эксплойты для найденных уязвимостей, fork testing взаимодействий.
Отчёт и remediation (день 14-20): подробный отчёт с severity, PoC, рекомендациями. Команда исправляет, аудитор проверяет fix'ы.
Fix Review (3-5 дней): верификация что исправления не вводят новых уязвимостей.
| Тип протокола | Объём кода | Срок аудита |
|---|---|---|
| Простой ERC-20 + vesting | < 500 строк | 1-2 недели |
| DeFi протокол (lending/AMM) | 1000-3000 строк | 3-5 недель |
| Комплексный протокол с proxy | 3000-10000 строк | 5-8 недель |
| Cross-chain мосты | любой объём | 6-12 недель |
Что аудит не гарантирует
Аудит снижает риск, но не устраняет его полностью. Аудиторы — люди, они пропускают баги. Самые известные эксплойты: Euler Finance ($197M, 2023), Nomad Bridge ($190M, 2022), Wormhole ($320M, 2022) — все проходили аудиты.
Правильная стратегия: аудит + bug bounty (Immunefi, HackerOne) + постепенный rollout с лимитами TVL + monitoring (Forta, OpenZeppelin Defender).
Аудит — необходимое, но не достаточное условие безопасности.







