Перейти к основному содержимому
Блокчейн35ГЛАВА 35hardhat.config.ts: настройки и сборка контрактовconfigpluginssoliditynetworksabibytecodebuild-info2253 → 1115 байт

hardhat.config.ts: настройки и сборка контрактов

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

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

Файл настроек — единственное, что отличает папку с установленным пакетом от проекта. Без него любая команда упирается в HHE3; с ним работает всё остальное.

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

Вторая половина — про сборку: что происходит между .sol и адресом в сети, какие файлы появляются на диске и зачем они нужны.

Файл целиком​

import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable, defineConfig } from "hardhat/config";

export default defineConfig({
plugins: [hardhatToolboxMochaEthersPlugin],
solidity: {
profiles: {
default: { version: "0.8.34" },
production: {
version: "0.8.34",
settings: { optimizer: { enabled: true, runs: 200 } },
},
},
},
networks: {
hardhatMainnet: { type: "edr-simulated", chainType: "l1" },
local: {
type: "http",
chainType: "l1",
url: "http://127.0.0.1:8545",
accounts: ["0x59c6…8690d"],
},
sepolia: {
type: "http",
chainType: "l1",
url: configVariable("SEPOLIA_RPC_URL"),
accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
},
},
});
ЧЕТЫРЕ БЛОКА НАСТРОЕК И ЧТО ОТВАЛИВАЕТСЯ БЕЗ КАЖДОГОpluginsподключаемые частисборка пройдёт, но ethers в скриптах не будетsolidity.profilesверсия компилятора и настройкиHHE909: версия не подходит под pragmanetworksкуда подключатьсянекуда развернуть контрактconfigVariableсекреты вне файлаHHE7: переменная не найденафайл настроек — единственное, что отличает папку с пакетом от проектабез него любая команда упирается в HHE3, с ним — работает всё остальное
Четыре блока настроек; для каждого — конкретная ошибка, которая приходит, если он неверен

Четыре верхних ключа и есть весь файл. Разберём по одному.

plugins: то, чего без них не будет​

import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
plugins: [hardhatToolboxMochaEthersPlugin],

Подключаемая часть (plugin) — пакет, который добавляет Hardhat новые возможности. Взятый нами набор приносит сразу несколько: работу с сетью через ethers, запуск тестов через mocha, проверку утверждений, сбор статистики по газу.

Коварство в том, что без него ничего не ломается сразу. Прогон убирает строку plugins из файла и собирает проект:

── в конфиге забыли подключить набор инструментов ──
сборка: Compiled 2 Solidity files with solc 0.8.34 (evm target: osaka)
а теперь попросим у него ethers в скрипте:
ethers есть? false

Сборка прошла как ни в чём не бывало. А в скрипте объекта ethers просто нет — и падение случится далеко от причины, на строке ethers.deployContract, где искать будут что угодно, кроме файла настроек.

Совет

Правило поиска: если в скрипте «не существует» что-то, чего вы не писали сами, — сначала откройте файл настроек и проверьте список подключаемых частей. Это дешевле, чем читать стек вызовов.

solidity: версия компилятора и профили сборки​

solidity: {
profiles: {
default: { version: "0.8.34" },
production: {
version: "0.8.34",
settings: { optimizer: { enabled: true, runs: 200 } },
},
},
},
Нажми на часть выражения

Версия компилятора и pragma в контракте — две разные вещи, которые обязаны договориться. pragma solidity ^0.8.28 означает «собери меня любым компилятором 0.8.x не ниже 0.8.28». Если в настройках стоит 0.8.20, договориться не получится:

Downloading solc 0.8.20
Error HHE909: Failed to create compilation job for file ".../contracts/Counter.sol"
using the build profile "default".
No solc version enabled in this profile is compatible with this file.

Обратите внимание: компилятор он сначала скачал и только потом обнаружил несовместимость. Это нормально — проверка делается уже установленной версией.

А если версии не существует вовсе:

Error HHE903: Solidity version 0.8.99 is invalid or hasn't been released yet.
If you are certain it has been released, run "npx hardhat clean --global" and try again
Отвечено вопросов: 0 из 2
Убрали plugins из настроек. Что произойдёт при сборке?
В контракте pragma ^0.8.28, в настройках version: "0.8.20". Что будет?
Отвечено верно: 0 из 2

Оптимизатор: вдвое короче при том же поведении​

Прогон собирает один и тот же контракт двумя профилями и меряет результат:

обычная сборка : deployedBytecode 2253 байт
production-сборка: deployedBytecode 1115 байт

Вдвое. Поведение при этом не изменилось ни на шаг: оптимизатор переписывает машинные команды, а не логику.

Зачем это нужно, если места вроде бы хватает:

  • Предел на контракт в сети — 24 576 байт. Пока контракт учебный, до предела далеко. Как только появляется наследование от нескольких библиотек, предел оказывается неожиданно близко, и первое, что делают, — включают оптимизатор.
  • Дешевле развёртывание. За каждый байт кода при развёртывании платят газом.
  • Дешевле вызовы. Короткий код — меньше работы на каждый вызов.
Важно

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

Что делает сборка​

npx hardhat build
Downloading solc 0.8.34
Downloading solc 0.8.34 (WASM build)
Compiled 2 Solidity files with solc 0.8.34 (evm target: osaka)

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

evm target: osaka — набор машинных команд, на который рассчитан результат. Он должен быть не новее того, что понимает сеть; для нашего geth 1.17 это так.

artifacts/artifacts.d.ts
artifacts/build-info/solc-0_8_34-94ca57….json
artifacts/build-info/solc-0_8_34-94ca57….output.json
artifacts/contracts/Vault.sol/Vault.json
artifacts/contracts/Vault.sol/artifacts.d.ts
ОДИН .SOL — ТРИ РАЗНЫХ РЕЗУЛЬТАТАVault.solто, что написал человекbuildabi13 записей: функции, события, ошибкипо нему с контрактом говорят снаружиbytecode2476 байтто, что отправляют при развёртыванииbuild-info93 КБисходники и настройки сборки целикомв сеть уезжает только байт-код: ни имён, ни комментариев, ни названий переменных там нетоптимизатор ужал тело контракта с 2253 до 1115 байт — вдвое, при том же поведениипредел на контракт в сети — 24 576 байт; о нём вспоминают, когда до него уже дошли
Из одного исходника рождаются описание для внешнего мира, байт-код для сети и полный слепок сборки

Артефакт: что внутри​

── ключи файла артефакта ──
_format, contractName, sourceName, abi, bytecode, deployedBytecode,
linkReferences, deployedLinkReferences, immutableReferences, buildInfoId

── байт-код ──
bytecode (для развёртывания) : 4954 символов = 2476 байт
deployedBytecode (что в сети) : 4508 символов = 2253 байт
начало bytecode : 0x6080604052348015600e575f5ffd5b50604051 …
предел на контракт в сети : 24576 байт

Два байт-кода, а не один. Первый — то, что отправляют в транзакции развёртывания; он длиннее, потому что содержит ещё и конструктор с его работой. Второй — то, что останется лежать по адресу навсегда. Конструктор выполняется один раз и в сеть не попадает — отсюда разница в 223 байта.

В байт-коде нет имён. Ни deposits, ни withdraw, ни комментариев. Всё это существует только в исходнике; сеть видит только команды. Именно поэтому в главе про прокси слоты нумеровались, а не назывались.

ABI: единственный способ поговорить с контрактом​

── что в ABI ──
constructor: (uint256 _rate) [nonpayable]
error: NotOwner()
error: NothingToWithdraw()
event: Deposited(address who, uint256 amount)
event: Withdrawn(address who, uint256 amount)
function: deposit() [payable]
function: deposits(address ) → uint256 [view]
function: interestOf(address who) → uint256 [view]
function: owner() → address [view]
function: rate() → uint256 [view]
function: setRate(uint256 _rate) [nonpayable]
function: totalDeposits() → uint256 [view]
function: withdraw() [nonpayable]

ABI (application binary interface) — список всего, что у контракта есть снаружи: функции с типами аргументов и результатов, события, свои ошибки, конструктор.

Три наблюдения по этому списку.

Публичные поля стали функциями. deposits, totalDeposits, rate, owner мы объявляли как поля — в ABI они превратились в функции чтения. Так работает public у полей: компилятор дописывает функцию за вас.

Свои ошибки попали в ABI. Поэтому библиотека и умеет показать NothingToWithdraw вместо непонятного набора байтов: она находит совпадение по списку.

Ничего лишнего. Приватных полей в ABI нет — но это не значит, что их не видно в сети: читать хранилище по номеру слота может кто угодно. ABI прячет их от удобного доступа, а не от любого.

Интересный факт

ABI — это всё, что нужно, чтобы говорить с контрактом. Исходники для этого не требуются: по адресу и ABI библиотека соберёт любой вызов. Отсюда и работают обозреватели сети с чужими контрактами.

build-info: слепок сборки целиком​

── файл со всеми подробностями сборки ──
размер build-info output : 93 КБ
что в нём хранится : _format, id, solcVersion, solcLongVersion,
compilerType, userSourceNameMap, input
версия solc : 0.8.34
настройки, с которыми собирали : {"evmVersion":"osaka","outputSelection":…}

93 килобайта на два маленьких контракта. Внутри — исходники целиком, точная версия компилятора и все настройки, с которыми собирали.

Зачем такой объём: чтобы сборку можно было повторить в точности. Подтверждение исходников в обозревателе сети устроено именно так — обозреватель собирает ваш код теми же настройками и сравнивает байт-код. Совпал — подпись «исходники подтверждены».

Заодно это объясняет, почему после сборки двумя профилями в папке оказалось два набора: настройки разные — значит, и слепки разные.

networks: симулятор и настоящий узел​

networks: {
hardhatMainnet: { type: "edr-simulated", chainType: "l1" },
local: {
type: "http",
chainType: "l1",
url: "http://127.0.0.1:8545",
accounts: ["0x59c6…8690d"],
},
},
Нажми на часть выражения

Прогон запускает один и тот же скрипт в обеих сетях:

hardhatMainnet chainId 31337 блок 0 счетов 20 первый 0xf39Fd6e5…b92266
баланс первого: 10000.0 ETH
local chainId 1337 блок 306 счетов 1 первый 0x70997970…dc79C8
баланс первого: 496.999983918662483 ETH
ОДИН СКРИПТ, ДВЕ СЕТИ ИЗ ОДНОГО ФАЙЛА НАСТРОЕКtype: "edr-simulated"chainId 31337 · блок 0счетов 20 · по 10000 ETHподнимается на время командыпосле неё исчезает без следаtype: "http"chainId 1337 · блок 306счетов 1 · 496.99998 ETHэто наш geth из прошлой главыбаланс — тот, что остался после переводовсчета в симуляторе даёт он сам; в своей сети — ровно те ключи, что вы вписали в настройкиотсюда «почему getSigners вернул один адрес вместо двадцати»: вы уже не в симуляторе
Симулятор раздаёт двадцать счетов по 10 000; своя сеть отдаёт ровно то, что в ней есть

Каждая строка тут о чём-то говорит.

Счетов 20 против 1. Симулятор раздаёт двадцать счетов с круглыми балансами. В своей сети счета — ровно те ключи, которые вы вписали в accounts. Отсюда знаменитое «почему getSigners вернул один адрес вместо двадцати»: вы уже не в симуляторе.

Баланс 496,999983918662483. Это не случайное число: столько осталось у счёта B после переводов из прошлой главы. Сеть — та же самая, папка та же, история на месте.

Блок 306. Узел с прошлой главы так и остался работать с блоками по таймеру и за это время напечатал триста пустых блоков. В симуляторе блок всегда 0: он только что родился.

Отвечено вопросов: 0 из 2
В симуляторе getSigners даёт 20 счетов, в своей сети — один. Почему?
Зачем в артефакте два байт-кода?
Отвечено верно: 0 из 2

Секреты: почему ключ не пишут в файл настроек​

Заготовка проекта пришла с третьей сетью, и написана она иначе:

sepolia: {
type: "http",
url: configVariable("SEPOLIA_RPC_URL"),
accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
},

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

Error HHE7: Configuration Variable "SEPOLIA_RPC_URL" not found.

You can define it using a plugin like hardhat-keystore, or set it as an
environment variable.

Значение берётся либо из переменной окружения, либо из встроенного зашифрованного хранилища:

npx hardhat keystore set SEPOLIA_PRIVATE_KEY

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

В нашей local ключ вписан строкой сознательно: это публичный учебный ключ от приватной сети на своей машине. Правило простое — строкой можно только то, что не жалко опубликовать.

Отвечено вопросов: 0 из 2
Почему приватный ключ не пишут строкой в hardhat.config.ts?
Зачем в проекте два профиля сборки, а не один?
Отвечено верно: 0 из 2

Типичные ошибки​

HHE3: No Hardhat config file found. Запустили не из папки проекта.

HHE909: No solc version enabled in this profile is compatible with this file. Версия в настройках не подходит под pragma контракта. Смотреть надо на оба места сразу: иногда проще поправить pragma, иногда — настройки.

HHE903: Solidity version … is invalid or hasn't been released yet. Опечатка в номере версии — или версия действительно ещё не вышла.

HHE7: Configuration Variable … not found. Сеть требует значение снаружи, а его не задали.

«ethers is not defined» в скрипте. Не подключён набор инструментов. Сборка при этом проходит — в том и подвох.

Собрали без оптимизатора и упёрлись в предел размера. Сообщение приходит не от компилятора, а от сети при развёртывании, и выглядит непонятно. Первое действие — собрать профилем production и сравнить размер.

Слова главы​

  • Файл настроек — единственное, что отличает проект от папки с пакетом.
  • Подключаемая часть — пакет с новыми возможностями; без неё сборка идёт, а скрипты падают.
  • Профиль сборки — набор настроек компилятора; выбирается флагом, по умолчанию default.
  • Оптимизатор — переписывает байт-код короче и дешевле, не трогая поведение.
  • Артефакт — результат сборки одного контракта: ABI и два байт-кода.
  • ABI — список всего, что у контракта есть снаружи; по нему с ним и говорят.
  • Слепок сборки — исходники и настройки целиком; нужен для повторяемости и подтверждения.
  • Значение снаружи — способ не писать ключи и адреса в файл, который лежит в репозитории.

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

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

export default defineConfig({ plugins: [] });

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

Файл настроек — первое, что открывает проверяющий, и первое, что стоит привести в порядок.

Своя сеть должна быть в networks под понятным именем. Не hardhatMainnet, не localhost по умолчанию, а именно та, которую вы подняли: адрес, номер сети, ключи. Тогда любая команда с --network local работает без объяснений.

Ключи от приватной сети — строкой, всё остальное — снаружи. Это читается как осознанное решение, а не как небрежность. Одна строка комментария рядом снимает вопрос совсем.

Профиль production — не украшение. Если контракт большой, разворачивать его придётся именно им, и узнать об этом лучше заранее.

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

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

  • runs в оптимизаторе — как выбор числа влияет на размер кода и цену вызовов.
  • Несколько версий компилятора в одном проекте — когда контракты требуют разных.
  • Подтверждение исходников — как обозреватель сети сверяет байт-код со слепком сборки.
  • Свои задачи Hardhat — добавить в набор команд собственную.
  • npx hardhat flatten — собрать контракт со всеми зависимостями в один файл; иногда этого просят при сдаче.

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

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

Видео по теме

ВидеоSolidity и Ethereum, урок #5 (ОБНОВЛЁННЫЙ 2023) | Hardhat, автотесты, ethersIlya Krukowski
ВидеоЧто такое ByteCode? | Байткод в EVMFuture Markt
ВидеоSolidity и Ethereum, урок #38 | Разбор байткода, opcodes, деплойIlya Krukowski
ВидеоCompiling Contracts and Inspecting Hardhat Build ArtifactsForge College
ВидеоWhat is Solidity Compiler, Bytecode and ABI?Blockchain Training

Комментарии

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