Разработка смарт-контрактов
Контракт задеплоен. Прошло две недели. Приходит сообщение: «У нас дренирован пул на $800k». Смотрим транзакцию в Tenderly: атакующий вызвал deposit() → внутри callback на ERC-777 вызвал withdraw() снова → баланс обновился только после второго выхода. Классическая reentrancy, но не через ETH transfer — через хук ERC-777. ReentrancyGuard стоял только на withdraw().
Такие случаи — не редкость. Смарт-контракт — это финансовая логика без возможности пропатчить её ночью.
Языки: Solidity, Rust, Move, Vyper — когда что выбирать
Solidity 0.8.x — стандарт для EVM-совместимых чейнов: Ethereum, Arbitrum, Optimism, Polygon, BSC, Avalanche C-Chain. Начиная с 0.8.0 встроен overflow protection, что убрало целый класс арифметических уязвимостей. Но это не значит, что арифметика безопасна автоматически — unchecked блоки, которые используют для gas optimization, отключают проверки обратно.
Rust + Anchor — для Solana. Принципиально другая модель: аккаунты и программы разделены, состояние хранится в аккаунтах, а не внутри контракта. Параллельное исполнение транзакций на Solana требует явного объявления всех аккаунтов, которые читает или пишет инструкция — это #[account] атрибуты в Anchor. Ошибка в объявлении аккаунтов — вектор для подмены данных.
Move (Aptos, Sui) — ресурсно-ориентированная модель. Ресурсы нельзя скопировать или случайно уничтожить — тип-система языка это запрещает на уровне компилятора. Линейные типы решают часть проблем, которые в Solidity решаются вручную через паттерны.
Vyper — Python-синтаксис, более ограниченный, чем Solidity: нет наследования, нет inline assembly по умолчанию, проще аудировать. Curve Finance пишет критические контракты на Vyper. В 2023 году компилятор Vyper содержал баг в reentrancy lock для версий 0.2.15–0.3.0, что привело к атакам на Curve pools с потерями ~$70M — уязвимость была в компиляторе, не в коде контракта.
Gas optimization: не преждевременная оптимизация, а архитектурное решение
Gas — это деньги пользователей. На Ethereum mainnet деплой плохо спроектированного контракта может стоить 2–5 ETH только из-за неоптимального storage layout.
Storage packing
В EVM каждый storage slot — 32 байта. Если хранить uint256, uint8, uint256 в таком порядке — это три слота. Если переупорядочить как uint256, uint256, uint8 — два слота, потому что uint8 упаковывается в конец второго слота. Казалось бы, мелочь. Но если это структура, которую читают в каждой транзакции, сэкономленные SLOAD операции (каждая стоит 2100 gas на холодный доступ) суммируются в тысячи долларов за месяц работы протокола.
Реальный кейс: контракт governance с 7 переменными в структуре Proposal, размещёнными в произвольном порядке, занимал 7 слотов. После переупаковки под slot boundaries — 4 слота. Экономия на операции vote: ~18k gas, или около $1.5 при gas price 30 gwei и ETH $3000.
Calldata vs memory vs storage
calldata — read-only, дешевле memory для аргументов функций. memory очищается после вызова. storage — персистентно, самое дорогое. Типичная ошибка: передавать большие массивы через memory аргументы вместо calldata в external функциях.
Custom errors vs require strings
require("Insufficient balance") хранит строку в bytecode и включает её в revert data. error InsufficientBalance(uint256 available, uint256 required) — custom error через revert InsufficientBalance(...) стоит на 50–200 gas меньше на каждый revert и даёт структурированные данные для фронтенда.
Паттерны апгрейдов
Три основных подхода к upgradeable контрактам:
| Паттерн | Механизм | Риск | Когда использовать |
|---|---|---|---|
| Transparent Proxy (OZ) | admin vs user caller разделение | storage collision, centralization | стандартные проекты |
| UUPS | логика апгрейда в implementation | забыть _authorizeUpgrade → навсегда сломан |
газ-оптимизированные проекты |
| Diamond (EIP-2535) | множество facets, один proxy | сложность, сложнее аудировать | крупные протоколы с 10+ контрактами |
| Beacon Proxy | один beacon для множества proxies | beacon = single point of failure | фабрики однотипных контрактов |
Storage collision — главная опасность прокси. Implementation v2 не должен добавлять переменные перед уже существующими. OpenZeppelin Upgrades plugin для Hardhat и Foundry проверяет это автоматически при запуске upgrades.upgradeProxy(), но только если вы используете его API, а не деплоите через new.
Timelock на апгрейды
Любой апгрейдируемый протокол с реальными деньгами должен иметь timelock. TimelockController из OpenZeppelin: операция предлагается → ждёт минимальный delay (48–72 часа для DeFi) → выполняется. За это время сообщество может заметить вредоносный апгрейд. Без timelock один скомпрометированный deployer wallet = потеря всего протокола.
Тестирование: Foundry как основной инструмент
Foundry сделал invariant testing и fuzz testing доступными без дополнительных фреймворков.
forge test --fuzz-runs 10000
forge test --match-test invariant
Фаззинг за 20 минут находит edge cases, которые ручные тесты пропускают месяцами. Реальный пример: AMM контракт с кастомной математикой для вычисления LP-токенов. 6 месяцев работы, 150 unit-тестов в Hardhat, все зелёные. Запустили Foundry fuzz с runs = 50000 — нашли integer division truncation при определённом соотношении резервов, которое приводило к тому, что пользователь получал на 1 wei меньше при withdraw. Безобидно само по себе, но открывало вектор для dust накопления на контракте.
Echidna для property-based fuzzing: определяете инварианты («сумма всех балансов никогда не превышает totalSupply»), Echidna ищет последовательность транзакций, которая их нарушает.
Slither — статический анализ без запуска кода. Находит reentrancy, неинициализированные переменные, dangerous delegatecall. Запускается в CI за 30 секунд.
MEV и front-running
На Ethereum mainnet транзакции в mempool видны всем. MEV-боты мониторят их и вставляют свои транзакции перед вашей (front-running) или после (back-running), или sandwich атаку. Для DEX это ожидаемо, для NFT-минтинга или governance — проблема.
Защита: commit-reveal scheme для аукционов и минтингов. Flashbots PROTECT RPC для приватной отправки транзакций (не попадают в public mempool). EIP-7702 и PBS (proposer-builder separation) меняют картину, но медленно.
Процесс разработки контрактов
Аналитика — спецификация функций, схема взаимодействий между контрактами, анализ edge cases. Без этого кодинг начинается вслепую.
Разработка — Solidity/Rust с тестами параллельно. Не после. Тест → код → рефакторинг.
Внутренний аудит — Slither + Echidna + ручной code review. Foundry invariant tests для протокольных инвариантов.
Внешний аудит — для протоколов с TVL от $1M обязателен. Trail of Bits, Consensys Diligence, OpenZeppelin, Code4rena (community audits). Срок: 2–4 недели.
Деплой — Foundry scripts или Hardhat Ignition для воспроизводимого деплоя. Verify на Etherscan автоматически. Gnosis Safe для ownership transfer сразу после деплоя.
Мониторинг — Tenderly alerts на подозрительные транзакции, OpenZeppelin Defender для автоматизации операций, Forta Network для on-chain детектирования атак.
Сроки
- ERC-20 token с базовыми функциями: 1–2 недели
- Vesting контракт с cliff/linear schedule: 2–3 недели
- NFT ERC-721/1155 с маркетплейсом: 4–6 недель
- AMM или lending протокол: 2–4 месяца
- Мультичейн протокол с bridge: 4–7 месяцев
Аудит добавляет 3–6 недель и идёт параллельно с финальным тестированием где возможно.







