Перейти к основному содержимому
Блокчейн37ГЛАВА 37Развёртывание: скрипты, Ignition и своя сетьto: —bytecode0x8464135c…nonce 0586 384 gas

Развёртывание: скрипты, Ignition и своя сеть

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

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

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

Эта глава закрывает блок инструментов: два способа развернуть, файл с адресами, работа с уже развёрнутым — и четыре отказа, которые встречают на этом шаге чаще всего. Все они про сеть, а не про контракт.

Развёртывание — это транзакция без получателя​

Обычная транзакция идёт кому-то: есть адрес получателя, есть сумма. У транзакции развёртывания поле получателя пустое, а в поле данных лежит байт-код.

Сеть, увидев такое, поступает так: считает адрес для нового контракта, выполняет код развёртывания (в нём отрабатывает конструктор) и кладёт по этому адресу результат — тело контракта.

ТРАНЗАКЦИЯ БЕЗ ПОЛУЧАТЕЛЯчто уходит в сетьto: пустоdata: 2476 байт байт-кодаnonce: 0газ: 586 384что появляется в сетиадрес 0x8464135c…730C318bCкод по адресу: 2253 байтадо этого по адресу было пустоконструктор отработал и исчезадрес не случайный: он считается из адреса отправителя и номера его транзакцииnonce 0 → 0x8464135c…, nonce 2 → 0x948B3c65… — оба совпали с тем, что реально развернулосьзначит, адрес будущего контракта можно узнать до того, как он появится
Пустой получатель и байт-код в данных; адрес считается заранее из отправителя и номера транзакции

Отсюда важное следствие. Адрес не случаен: он вычисляется из адреса отправителя и номера его транзакции. Проверка:

nonce 0 → 0x8464135c8F25Da09e49BC8782676a84730C318bC
nonce 1 → 0x71C95911E9a5D330f4D621842EC243EE1343292e
nonce 2 → 0x948B3c65b89DF0B4894ABE91E6D02FE579834F8F
nonce 3 → 0x712516e61C8B383dF4A63CFe83d7701Bce54B03e

фактически развёрнуто скриптом : 0x8464135c8F25Da09e49BC8782676a84730C318bC
фактически развёрнуто Ignition : 0x948B3c65b89DF0B4894ABE91E6D02FE579834F8F

Первое развёртывание было нулевой транзакцией счёта — адрес совпал со строкой nonce 0. Между ними прошёл ещё один вызов (вклад), поэтому второе развёртывание стало третьей транзакцией, и адрес совпал со строкой nonce 2.

Номер транзакции (nonce) — счётчик отправленных этим счётом транзакций. Он же не даёт повторить одну и ту же транзакцию дважды.

Скрипт развёртывания​

import { network } from "hardhat";

const { ethers } = await network.connect();

const [deployer] = await ethers.getSigners();
const vault = await ethers.deployContract("Vault", [500n]);
const tx = vault.deploymentTransaction()!;
await vault.waitForDeployment();
const addr = await vault.getAddress();
const rc = await ethers.provider.getTransactionReceipt(tx.hash);
Нажми на часть выражения

Запускается с указанием сети:

npx hardhat run scripts/deploy.ts --network local

Прогон в свою сеть​

── куда и чем ──
сеть : local chainId 1337
разворачиваем с: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
баланс до : 500.0 ETH
высота цепи до : 0

── развернули ──
адрес контракта: 0x8464135c8F25Da09e49BC8782676a84730C318bC
хеш транзакции : 0x9d47c30f735412f6fe851e13f2fcfa36e12503e6197c5f5149406c446eabe283
номер (nonce) : 0
блок : 1
газа истрачено : 586384
цена газа : 875000001 wei
заплачено : 0.000513086000586384 ETH

── что теперь лежит по адресу ──
длина кода : 2253 байт
начало : 0x60806040526004361061007a …
до развёртывания по этому адресу было: 0x (пусто)

── контракт отвечает ──
owner() : 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
rate() : 500
totalDeposits(): 0

── положили 1 ETH ──
блок : 2
газа : 67481
totalDeposits(): 1.0 ETH
баланс контракта: 1.0 ETH

Четыре числа здесь встречались раньше, и это не совпадение.

586 384 газа — ровно столько же показала таблица газа в тестах. Развёртывание стоит одинаково в симуляторе и в настоящей сети: правила одни.

2253 байта — длина тела контракта из артефакта в главе про настройки. То, что собрал компилятор, и то, что лежит в сети, — один и тот же набор байтов.

67 481 газа на вклад — снова та же цифра из таблицы.

owner() — адрес развернувшего. Конструктор записал msg.sender, а отправителем был наш счёт.

Важно

Строка «до развёртывания по этому адресу было пусто» — не украшение. Пока транзакция не попала в блок, по адресу ничего нет, и любой вызов туда вернёт пустоту вместо ошибки. Отсюда классическая жалоба «контракт развёрнут, а функции не отвечают»: не дождались подтверждения.

Отвечено вопросов: 0 из 2
Чем транзакция развёртывания отличается от обычной?
Почему адрес контракта можно узнать до развёртывания?
Отвечено верно: 0 из 2

Ignition: описание вместо шагов​

Скрипт — это список действий. Когда контрактов несколько и они зависят друг от друга, список становится хрупким: упало на третьем шаге — что уже сделано, что нет?

Второй способ описывает желаемый результат, а порядок действий выводится сам:

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

export default buildModule("VaultModule", (m) => {
const rate = m.getParameter("rate", 500n);
const vault = m.contract("Vault", [rate]);
m.call(vault, "setRate", [700n]);
return { vault };
});
Нажми на часть выражения
npx hardhat ignition deploy ignition/modules/Vault.ts --network local
? Confirm deploy to network local (1337) › yes

Hardhat Ignition 🚀

Deploying [ VaultModule ]

Batch #1
Executed VaultModule#Vault

Batch #2
Executed VaultModule#Vault.setRate

[ VaultModule ] successfully deployed 🚀

Deployed Addresses

VaultModule#Vault - 0x948B3c65b89DF0B4894ABE91E6D02FE579834F8F
ДВА СПОСОБА РАЗВЕРНУТЬ ОДНО И ТО ЖЕscripts/deploy.tsпошаговый список действийвсё видно, ничего не скрытоповторный запуск развернёт ещё разупал на середине — состояние неизвестноадреса надо записывать самомуignition/modules/Vault.tsописание желаемого результатасам разбивает работу на пачкиповтор: Nothing new to deployупал — продолжит с места остановкиадреса сохраняет в файл самодиночный контракт удобнее скриптом; связку из нескольких — описаниемфайл с адресами лежит рядом и читается следующими скриптами: адрес больше не переписывают руками
Скрипт перечисляет шаги и не помнит о них; описание хранит состояние и повторный запуск ничего не делает

Работа разбита на пачки. В первой — развёртывание, во второй — вызов, который от него зависит. Независимые действия попали бы в одну пачку и ушли бы одновременно.

Подтверждение спрашивается. Для любой сети, кроме симулятора: развёртывание стоит денег, и случайный запуск не туда — дорогая ошибка.

Повторный запуск: ничего нового​

=== повторный запуск того же модуля ===
[ VaultModule ] Nothing new to deploy based on previous execution
stored in ./ignition/deployments/chain-1337

Deployed Addresses
VaultModule#Vault - 0x948B3c65b89DF0B4894ABE91E6D02FE579834F8F

Второй запуск ничего не развернул и назвал тот же адрес. Состояние хранится на диске:

ignition/deployments/chain-1337/deployed_addresses.json
ignition/deployments/chain-1337/journal.jsonl
ignition/deployments/chain-1337/artifacts/VaultModule#Vault.json
ignition/deployments/chain-1337/build-info/solc-0_8_34-111f9245….json
{ "VaultModule#Vault": "0x948B3c65b89DF0B4894ABE91E6D02FE579834F8F" }

Папка названа по номеру сети — chain-1337. Для каждой сети своё состояние: одно и то же описание разворачивается в свою сеть и в чужую независимо.

Журнал — построчная запись всего, что происходило. Если развёртывание оборвалось на середине, следующий запуск читает журнал и продолжает с места остановки, а не начинает заново.

Артефакт и слепок сборки лежат рядом. То есть в папке развёртывания хранится не только адрес, но и чем именно этот адрес был собран.

Работать с уже развёрнутым​

Адрес больше не нужно переписывать руками — он лежит в файле:

const addresses = JSON.parse(
readFileSync("ignition/deployments/chain-1337/deployed_addresses.json", "utf8"),
);
const vault = await ethers.getContractAt("Vault", addresses["VaultModule#Vault"]);
── состояние контракта в сети ──
owner() : 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
rate() : 700 ← модуль вызвал setRate(700) сразу после развёртывания
totalDeposits(): 0.0 ETH

── положили 2 ETH ──
блок : 57
totalDeposits(): 2.0 ETH
проценты по 7 %: 0.14 ETH
баланс контракта: 2.0 ETH

── все события Deposited с начала цепочки ──
блок 57 | 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | 2.0 ETH
Нажми на часть выражения

Ставка 700, а не 500. Модуль после развёртывания вызвал setRate(700) — и это состояние в сети, а не в скрипте.

Блок 57. Узел работает с блоками по таймеру, и за время работы их накопилось полсотни. Номер блока — не «сколько было транзакций», а «сколько прошло блоков».

Отвечено вопросов: 0 из 2
Повторный запуск того же модуля Ignition. Что произойдёт?
Что делает getContractAt?
Отвечено верно: 0 из 2

Четыре отказа, и все про сеть​

ЧЕТЫРЕ ОТКАЗА ПРИ РАЗВЁРТЫВАНИИузел не отвечаетHHE703: Cannot connect to the networkузел не запущен или порт другойchainId не тотHHE708: set to use chain id 9999, connected to 1337поправить номер в настройкахна счету нольinsufficient funds for transferвписать адрес в alloc и поднять сеть зановосвои транзакции мешаютHHE10402: wait until they get 5 confirmationsблоки по таймеру: --dev.periodвсе четыре — про сеть, а не про контракт: код при этом верен и собирается
Контракт при этом верен и собирается: ломается подключение, номер сети, баланс или очередь транзакций

Узел не отвечает.

Error HHE703: Cannot connect to the network "dead".
Please make sure your node is running, and check your internet connection
and networks config

Узел не запущен, порт другой или адрес в настройках с опечаткой. Проверяется одним запросом высоты цепи.

Номер сети не совпал.

Error HHE708: Hardhat was set to use chain id "9999", but connected to
a chain with id "1337".

Отдельно ценно, что это ловится до отправки. Если бы не ловилось, транзакция ушла бы и была бы отвергнута узлом с невнятным invalid sender — тем самым, что мы видели в главе про свой узел.

На счету ноль.

адрес: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC баланс: 0.0 ETH
ProviderError: insufficient funds for transfer

Ключ в настройках верный, адрес существует, но денег на нём нет. Лечится не в проекте, а в genesis.json: адрес вписывают в alloc и поднимают сеть заново.

Свои же транзакции мешают.

Error HHE10402 in plugin hardhat-ignition: You have sent transactions from
0x70997970… and they interfere with Hardhat Ignition. Please wait until they
get 5 confirmations before running Hardhat Ignition again.

Этот отказ был получен по-настоящему и объясняется не сразу.

Почему Ignition потребовал пять подтверждений​

Ignition отслеживает свои транзакции по номерам. Если тем же счётом кто-то отправил транзакции мимо него — а мы именно это и сделали скриптом развёртывания, — его представление о номерах может разойтись с действительностью. Поэтому он требует, чтобы посторонние транзакции отстоялись пять блоков, и только потом берётся за работу.

И тут выяснилось неприятное: узел был запущен без расписания блоков. Новых блоков нет — значит, подтверждения не набираются никогда, и Ignition будет отказываться вечно.

Лечение — то самое расписание из главы про свой узел:

geth --datadir ./chain --dev --dev.period 2 …

Через несколько секунд подтверждения набрались, и развёртывание прошло.

Совет

Общее правило: для работы инструментов узел должен печатать блоки сам. Режим «блок только под транзакцию» удобен в опытах руками и мешает всему, что ждёт подтверждений. Двух секунд на блок достаточно.

Что показывать на демонстрации​

Задание требует показать не «контракт развёрнут», а цепочку событий. Порядок, который читается с экрана без пояснений:

  1. Узел работает — высота цепи растёт сама.
  2. Развёртывание — адрес, хеш транзакции, номер блока, истраченный газ.
  3. По адресу действительно код — длина кода ненулевая; до развёртывания там было пусто.
  4. Контракт отвечает — чтение полей: владелец, параметры.
  5. Транзакция меняет состояние — было, стало, номер блока.
  6. Событие нашлось — поиск по цепочке показывает, кто и сколько.
  7. Адрес не изменился — для шага с обновлением логики из главы про прокси.

Всё это — один скрипт, который печатает по-человечески. Писать его лучше заранее: на демонстрации думать некогда, а вывод, который сам себя объясняет, экономит половину времени защиты.

Отвечено вопросов: 0 из 2
Развёртывание падает с «insufficient funds for transfer». Где это чинят?
Почему инструмент развёртывания завис в ожидании пяти подтверждений?
Отвечено верно: 0 из 2

Слова главы​

  • Транзакция развёртывания — без получателя, с байт-кодом в данных.
  • Номер транзакции (nonce) — счётчик у счёта; вместе с адресом задаёт адрес будущего контракта.
  • Подтверждение (receipt) — блок, газ, цена; появляется только после попадания в блок.
  • Дождаться развёртывания — до этого по адресу пусто, и вызовы возвращают пустоту.
  • Описание развёртывания — желаемый результат вместо списка шагов; порядок выводится сам.
  • Состояние развёртывания — папка по номеру сети: адреса, журнал, артефакты.
  • Взять по адресу — связать ABI с уже существующим контрактом, ничего не разворачивая.
  • Пять подтверждений — условие Ignition после посторонних транзакций тем же счётом.

Тренажёр: набрать вслепую​

Цель: скорость ≥ 95 зн/мин, точность ≥ 88%

const vault = await ethers.deployContract("Vault", [500n]);

На чемпионате​

Одна команда на всё. Разворачивать руками из консоли — потерять время и сбиться. Скрипт или описание запускается одной строкой и повторяется сколько угодно раз.

Адреса — только из файла. Переписанный руками адрес рано или поздно окажется от прошлого развёртывания, и полчаса уйдёт на «почему функция возвращает ноль».

Узел с расписанием блоков. Иначе половина инструментов встанет в ожидании подтверждений, которых не будет.

Сохраните вывод развёртывания. Адрес, хеш транзакции, номер блока — это доказательства, которые пригодятся при защите, и восстановить их потом сложнее, чем скопировать сразу.

И про порядок работы: развернуть в симулятор проще и быстрее, но проверять надо в своей сети. Разница между ними — не в контракте, а во всём остальном: счетах, подтверждениях, времени блока. Ошибки живут именно там.

Куда углубиться​

  • Развёртывание по заранее известному адресу — способ, при котором адрес не зависит от номера транзакции.
  • Подтверждение исходников — как связать адрес в сети с исходником в обозревателе.
  • Продолжение оборванного развёртывания — журнал и команда состояния.
  • Развёртывание связки контрактов — зависимости, параметры, пачки.
  • Обновление логики через прокси — как это выглядит из описания развёртывания.

Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Экзамен по блоку «Инструменты»​

Пять глав: от genesis-блока своей сети до развёрнутого контракта, отвечающего на вызовы. Проверка — по всему блоку.

Экзамен по блоку: Узел и инструменты

Сводная проверка по 5 главам блока: 15 вопросов, 10:00 на всё, по одному вопросу, без возврата. Пересдавать можно сколько угодно раз.

Готовность: экзамены глав сданы 0 из 5. Блок можно сдавать и так, но шанс выше после всех глав.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоHARDHAT 3.0 | Гайд и Обзор Обновлений. Деплой, Верификация, СкриптыБлокчейн Разработчик
ВидеоКак задеплоить смарт-контракт через HardHat? Deploy в любой EVM сетиFuture Markt
ВидеоДеплой смарт-контракта с помощью Hardhat. Верификация контрактов с аргументами. #solidity #hardhatDima HideCrypto - Web3 | Solidity
ВидеоHardhat Ignition: Smart Contract Deployment TutorialSoft.Tomatoes
ВидеоМеняем код задеплоенного контракта. Create | Create2 Низкоуровневые инструкции в #solidity #deployDima HideCrypto - Web3 | Solidity

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.