CounterV1) за ERC1967Proxy в Virtual Environment, верифицирует обе за один запуск forge script, обновляет имплементацию до CounterV2 и рассказывает, как повторно верифицировать прокси, задеплоенный без верификации. Для эквивалента в Hardhat см. Verifying Proxy Contracts.
Прежде чем начать
- Выполните настройку из Деплой и верификация контрактов: Virtual Environment, пополненный адрес деплойера и
foundry.tomlсcbor_metadata = trueиbytecode_hash = "ipfs". - Установите переменные окружения:
- Установите upgradeable-контракты OpenZeppelin рядом с базовой библиотекой:
- Добавьте оба remapping в
remappings.txt:
remappings.txt
openzeppelin-contracts предоставляет прокси (ERC1967Proxy), а openzeppelin-contracts-upgradeable — строительные блоки имплементации (Initializable, UUPSUpgradeable, OwnableUpgradeable). Upgradeable-версии заменяют конструкторы функциями инициализации, потому что конструкторы не выполняются, когда состояние живёт за прокси.
Контракт имплементации
src/CounterV1.sol
constructor() { _disableInitializers(); }блокирует имплементацию. Только прокси должен держать состояние; этот конструктор делает так, что прямой вызовinitializeпо адресу имплементации завершается revert.initialize(...)заменяет конструктор. Прокси делает по нему delegatecall один раз, в той же транзакции, что деплоит прокси._authorizeUpgrade(...)— обязательный UUPS-хук для авторизации. Он выполняется при каждомupgradeToAndCall; если он делает revert, апгрейд блокируется.
Deploy-скрипт
script/DeployProxy.s.sol
initialize с помощью abi.encodeCall (который проверяет типы аргументов на этапе компиляции, в отличие от abi.encodeWithSignature) и деплоит прокси. Конструктор прокси сохраняет адрес имплементации в слоте имплементации ERC-1967 и делает delegatecall с initData против неё, атомарно инициализируя хранилище прокси.
Деплой и верификация обоих контрактов
Прокси и имплементация — это два контракта в одном скрипте, поэтому команда имеет ту же формуforge script, что и любое развёртывание с несколькими контрактами:
showLineNumbers
lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy, контракт OpenZeppelin из вашего дерева lib/, а не контракт в src/. Foundry разрешает это автоматически, потому что broadcast log скрипта записывает каждое new и его байткод.
Подтвердите связь прокси
Обозреватель декодирует вызовы через прокси, когда и прокси, и имплементация верифицированы. Чтобы подтвердить, что прокси действительно делегирует, прочитайте слот имплементации ERC-1967 напрямую:showLineNumbers
ERC1967Proxy, тег прокси со связанным адресом имплементации и представление ABI, включающее функции имплементации. Транзакции, вызывающие proxy.increment() или proxy.setNumber(...), декодируются в call trace с именами функций и меток параметров имплементации.
Если обозреватель показывает прокси, но не декодирует вызовы с помощью ABI имплементации, самая частая причина — что верифицирован только один из двух контрактов. Перезапустите
forge verify-contract на том, который отсутствует.Апгрейд имплементации
Апгрейды UUPS выполняются черезupgradeToAndCall(newImpl, callData), унаследованный от UUPSUpgradeable. Добавьте V2:
src/CounterV2.sol
- Наследуйтесь от V1 или копируйте схему хранилища V1 буквально. Слоты хранилища позиционные; добавление новой переменной состояния перед существующей повреждает состояние прокси.
- Новые переменные состояния идут после существующих переменных V1, никогда между ними.
- Не изменяйте типы существующих переменных и не переупорядочивайте объявления.
script/UpgradeProxy.s.sol
showLineNumbers
CounterV2 (в выводе указано (1) contracts). Адрес прокси не меняется; меняется только его слот ERC-1967. После возврата скрипта:
showLineNumbers
CounterV2. Старая имплементация CounterV1 остаётся верифицированной по своему оригинальному адресу, но больше не является активной имплементацией.
Верификация уже задеплоенного прокси
Если вы задеплоили без--verify, запустите forge verify-contract для каждого контракта отдельно.
Имплементация была задеплоена через new CounterV1() без аргументов конструктора, поэтому флаг --constructor-args не нужен:
showLineNumbers
showLineNumbers
- Путь контракта использует расположение
lib/, а неsrc/. Прокси — это код OpenZeppelin, поэтому исходник, с которым сравнивает верификатор, лежит вlib/openzeppelin-contracts/....forge script --verifyразрешает это автоматически;forge verify-contract— нет. initDataдолжен точно совпадать сinitDataво время деплоя. Даже если состояние изменилось после развёртывания, аргумент конструктора — это оригинальная calldatainitialize(owner, 42), с которой был сконструирован байткод прокси.- Сигнатура конструктора прокси —
(address, bytes), аforge verify-contractожидает уже ABI-кодированную форму, которую и производитcast abi-encode.
Дальнейшие шаги
- Деплой и верификация контрактов: базовый процесс верификации Foundry и Hardhat в Virtual Environment.
- Верификация смарт-контрактов с помощью Foundry: верификация контрактов в публичных сетях через API верификации Tenderly.
- Верификация прокси-контрактов: процесс с плагином Hardhat для UUPS-, Transparent- и Beacon-прокси.