Тестирование смарт-контрактов (интеграционные тесты)

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

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

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

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

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

Тестирование смарт-контрактов (интеграционные тесты)

Unit-тесты показывают, что функция transfer() корректно обновляет балансы. Интеграционные тесты показывают, что эта функция в связке с approve(), allowance() и контрактом-агрегатором не создаёт race condition при параллельных вызовах в одном блоке. Разница принципиальная, особенно когда протокол взаимодействует с Uniswap V3, Aave V3 и Chainlink в одной транзакции.

Где unit-тесты заканчиваются и начинаются проблемы

Классический сценарий: контракт стейкинга с reward-дистрибьюцией прошёл unit-тесты на 100%. Каждая функция работает корректно в изоляции. На mainnet через неделю после деплоя обнаруживается: если пользователь вызывает compound() и withdraw() в одном блоке (через batch-транзакцию или через контракт-агрегатор), reward-расчёт берёт snapshot баланса до compound(), но применяет его после — пользователь получает двойное вознаграждение за один epoch.

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

Ещё один паттерн: контракт корректно работает с ERC-20 токенами, которые следуют стандарту. Но в реальном DeFi-ландшафте встречаются fee-on-transfer токены (USDT на некоторых чейнах), rebase-токены (stETH), токены с blacklist (USDC). Интеграционный тест должен проверить, что контракт не делает предположений о том, что amount в Transfer event равен фактически полученному количеству.

Как строится интеграционное тестирование

Mainnet fork как основа среды

Реалистичные интеграционные тесты требуют реальных состояний протоколов. Мы форкаем mainnet через Hardhat или Foundry с конкретным block number и тестируем взаимодействие с живыми контрактами Uniswap, Aave, Curve — не с моками.

// hardhat.config.ts
networks: {
  hardhat: {
    forking: {
      url: process.env.ALCHEMY_URL,
      blockNumber: 19500000, // фиксируем блок для воспроизводимости
    }
  }
}

Фиксация block number критична. Без неё тесты недетерминированы: цены, ликвидность, состояние пулов меняются, и тест может проходить сегодня и падать завтра по причинам, не связанным с кодом.

Сценарии взаимодействия, которые тестируем

Multi-step DeFi сценарии. Например, для yield aggregator: deposit → approve LP-токен → stake в gauge → harvest → compound. Каждый шаг вызывает внешние контракты. Тест проверяет финальное состояние, а не промежуточные шаги.

Атаки через flash loan. Имитируем flash loan attack через Aave V3 flashLoanSimple() прямо в тесте: занимаем токены, пробуем манипулировать ценой в AMM, вызываем целевой контракт, возвращаем займ. Если контракт использует spot price из DEX без TWAP — он уязвим. Тест должен это зафиксировать.

Reentrancy через callback. ERC-721 имеет onERC721Received(), ERC-1155 — onERC1155Received(). Если контракт делает state change после transferFrom(), а callback вызывает контракт снова — это reentrancy. Пишем специальный контракт-атакер в тесте, который реализует этот callback злонамеренно.

Sandwich атаки и MEV. Тестируем слипедж-защиту: что происходит, если между approve() и swap() кто-то двигает цену в пуле. Используем hardhat_setStorageAt для прямой манипуляции состоянием пула в тесте.

Тип теста Инструмент Что покрывает
Unit Hardhat / Foundry Изолированная логика функций
Integration (local mock) Hardhat Взаимодействие между своими контрактами
Integration (mainnet fork) Hardhat / Foundry Взаимодействие с реальными протоколами
Fuzzing Echidna / Foundry forge fuzz Инвариантные нарушения
Formal verification Certora Prover Математические свойства

Foundry vs Hardhat для интеграционных тестов

Foundry быстрее: интеграционный тест-сьют на 200 кейсов выполняется за 15-30 секунд против 3-5 минут в Hardhat. Это важно при TDD и частых итерациях.

Hardhat удобнее для сложных сценариев с JavaScript-логикой: манипуляция блоками через evm_mine, точный контроль газа через eth_estimateGas с overrides, интеграция с реальными SDK протоколов (Uniswap SDK, Aave SDK).

Мы используем оба. Foundry — для быстрого прогона property-based тестов и fuzz. Hardhat — для сложных многошаговых сценариев с реальными протоколами.

Типичные ошибки, которые выловили в реальных проектах

Предположение о порядке событий в одном блоке. Если контракт использует block.number для расчёта наград, а два вызова происходят в одном блоке — block.number одинаков для обоих. Контракт должен использовать block.timestamp или отдельный счётчик.

Моки вместо реальных токенов. Мок-токен всегда возвращает true на transfer(). USDT на Ethereum не возвращает значение вовсе (не соответствует ERC-20 стандарту). Тест с моком пройдёт, деплой на mainnet с USDT — нет.

Игнорирование gas limit на функции. Интеграционный тест должен замерять gas consumption для каждого сценария. Если агрегатор вызывает 10 пулов Curve в одной транзакции, это может упереться в block gas limit при определённых состояниях пулов.

Нет тестов на edge case токенов. Fee-on-transfer (PAXG, STA), rebase (stETH, aTokens), pausable (USDC), с blacklist — каждая категория требует отдельного тест-сьюта.

Процесс и сроки

Интеграционное тестирование существующего протокола: 2-3 рабочих дня. Включает: анализ контрактов, определение критических путей взаимодействия, написание тест-сьюта, прогон с форком mainnet, отчёт по найденным проблемам.

Параллельная разработка интеграционных тестов вместе с контрактами — закладываем в общую смету проекта, обычно 30-40% от времени разработки. Стоимость рассчитывается индивидуально.