Настройка форка mainnet для тестирования

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Настройка форка mainnet для тестирования
Средняя
~1 рабочий день
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Настройка форка mainnet для тестирования

Мок Uniswap пула для тестирования свопов — это имитация интерфейса без имитации экономики. Реальный пул Uniswap V3 имеет конкретную ликвидность в конкретных тиках, конкретную историю свопов, конкретный fee growth. Тест, который проходит против мока, может падать против реального пула — потому что tick spacing не тот, или liquidity в нужном диапазоне оказалась нулевой.

Форк mainnet решает это: берём реальное состояние всех контрактов на конкретном блоке и запускаем тесты против него локально.

Как это работает технически

Hardhat и Foundry реализуют форкинг через JSON-RPC: при обращении к адресу или storage slot, которого нет в локальном кэше, делают eth_getStorageAt / eth_getCode к удалённому узлу (Alchemy, Infura, QuickNode) и кэшируют ответ. Последующие обращения к тому же slot идут из кэша.

Это значит, что первый прогон тест-сьюта будет медленным (много запросов к RPC), а последующие — быстрыми (всё в дисковом кэше). Foundry кэширует в ~/.foundry/cache/, Hardhat — в cache/hardhat-network-fork/.

Настройка в Hardhat

// hardhat.config.ts
networks: {
  hardhat: {
    forking: {
      url: process.env.ALCHEMY_MAINNET_URL!,
      blockNumber: 19750000, // фиксируем блок
      enabled: true,
    },
    chainId: 1, // важно: chainId должен совпадать с forked chain
  }
}

Фиксация blockNumber обязательна для детерминированных тестов. Без неё каждый запуск берёт разный блок, и тесты могут падать или проходить в зависимости от текущего состояния пулов.

Для тестов, которым нужны свежие данные (например, проверка оракула Chainlink), используем hardhat_reset с новым blockNumber прямо в тесте:

await network.provider.request({
  method: "hardhat_reset",
  params: [{
    forking: {
      jsonRpcUrl: process.env.ALCHEMY_MAINNET_URL,
      blockNumber: 19800000,
    }
  }]
});

Настройка в Foundry

# foundry.toml
[profile.default]
fork_url = "${ALCHEMY_MAINNET_URL}"
fork_block_number = 19750000

Или в тесте через vm.createFork() / vm.selectFork() для переключения между форками в одном тест-файле — удобно при тестировании cross-chain логики:

uint256 mainnetFork = vm.createFork(vm.envString("ALCHEMY_MAINNET_URL"), 19750000);
uint256 polygonFork = vm.createFork(vm.envString("ALCHEMY_POLYGON_URL"), 55000000);

vm.selectFork(mainnetFork);
// тест на Ethereum
vm.selectFork(polygonFork);
// тест на Polygon

Манипуляция состоянием форка

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

Дать ETH аккаунту:

await network.provider.send("hardhat_setBalance", [
  address, ethers.toQuantity(ethers.parseEther("100"))
]);

Дать ERC-20 токены — нужно найти storage slot балансового маппинга. Для большинства токенов это slot 0 или slot 1. Утилита hardhat-storage-layout или Foundry cast storage помогает найти нужный slot:

// для USDC балансы в slot 9
const slot = ethers.keccak256(
  ethers.concat([ethers.zeroPadValue(address, 32), ethers.zeroPadValue("0x09", 32)])
);
await network.provider.send("hardhat_setStorageAt", [
  USDC_ADDRESS, slot, ethers.toBeHex(amount, 32)
]);

Impersonate account — действовать от имени любого адреса, включая multisig или DAO:

await network.provider.request({
  method: "hardhat_impersonateAccount",
  params: [WHALE_ADDRESS]
});
const whale = await ethers.getSigner(WHALE_ADDRESS);
// теперь можно вызывать контракты от имени whale

Перемотать время:

await time.increase(86400 * 7); // +7 дней
await time.setNextBlockTimestamp(specificTimestamp);

Что тестировать через форк

Взаимодействие с AMM. Swap через Uniswap V3 с реальной ликвидностью, проверка slippage, price impact для крупных объёмов. Мок не воспроизведёт ситуацию, когда liquidity в нужном tick range исчезла.

Flash loan атаки. Берём flash loan через Aave V3 (реальный контракт), пробуем манипулировать ценой, проверяем что защита в нашем контракте работает.

Интеграция с Chainlink. Проверяем, что контракт корректно читает price feed и обрабатывает случай устаревших данных (staleness check).

Работа с реальными токенами. USDT без return value, stETH с rebasing, USDC с blacklist — все особенности реальных токенов присутствуют в форке автоматически.

RPC и кэш

Хороший RPC критичен. Alchemy и QuickNode дают архивные узлы с историей всех storage slots. Infura бесплатный план ограничен и может давать rate limit при большом тест-сьюте.

Для CI рекомендуем кэшировать Foundry/Hardhat cache между запусками — это экономит 70-80% времени:

- uses: actions/cache@v3
  with:
    path: ~/.foundry/cache
    key: foundry-fork-${{ env.FORK_BLOCK_NUMBER }}

Настройка среды с форком занимает 1 рабочий день. Стоимость рассчитывается индивидуально.