Перейти к основному содержанию
Работает на: Virtual Environments и публичных сетях (mainnet и testnet). Один и тот же плагин работает и там, и там; укажите нужную сеть в hardhat.config.ts.
Это руководство проведёт вас по верификации прокси-контрактов с помощью @tenderly/hardhat-tenderly.

Требования

Автоматическая верификация прокси работает «из коробки» в @tenderly/hardhat-tenderly:
  • >= 1.10.0 (линия Ethers 5)
  • >= 2.1.0 (линия Ethers 6)
npm update @tenderly/hardhat-tenderly
Она работает, только когда вы разворачиваете прокси через @openzeppelin/hardhat-upgrades. Hardhat Ignition не поддерживается для верификации прокси пока что. Если вы используете более раннюю версию плагина и не можете обновиться, воспользуйтесь обходным решением ниже.

Автоматическая верификация

Плагин верифицирует три паттерна прокси: TransparentUpgradeableProxy, UUPSUpgradeableProxy и BeaconProxy.
1
Установите флаг автоматического заполнения
2
Под капотом плагин управляет @nomicfoundation/hardhat-verify с расширением @openzeppelin/hardhat-upgrades. Установка TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true позволяет плагину автоматически заполнить URL верификации @nomicfoundation/hardhat-verify.
3
TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true
4
Напишите скрипт деплоя
5
Для более наглядного примера можете заглянуть в этот GitHub-репозиторий и перейти в scripts/proxy/, чтобы увидеть полный пример.
6
Автоматически
Разверните прокси как обычно, используя deployProxy из расширения @openzeppelin/hardhat-upgrades.
Вы должны захватить объект, возвращаемый функцией waitForDeployment, чтобы взаимодействовать с ним в дальнейшем.
Под капотом автоматическая верификация реализована как обёртка вокруг deployProxy и ожидание завершения деплоя.

async function main() {
    console.log(
        "🖖🏽[ethers] Deploying TransparentUpgradeableProxy with VotingLogic as implementation on Tenderly.",
    );

    const VotingLogic = await ethers.getContractFactory("VotingLogic");
    let proxyContract = await upgrades.deployProxy(VotingLogic);
    proxyContract = await proxyContract.waitForDeployment();

    const proxyAddress = await proxyContract.getAddress();

    console.log("VotingLogic proxy deployed to:", proxyAddress);
    console.log(
        "VotingLogic impl deployed to:",
        await getImplementationAddress(ethers.provider, proxyAddress),
    );
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
Вручную
Верификация прокси вручную выполняется после того, как прокси-контракт развёрнут, — путём вызова функции tenderly.verify().

async function main() {
    console.log(
        "🖖🏽[ethers] Deploying TransparentUpgradeableProxy with VotingLogic as implementation on Tenderly.",
    );

    const VotingLogic = await ethers.getContractFactory("VotingLogic");
    let proxyContract = await upgrades.deployProxy(VotingLogic);
    proxyContract = await proxyContract.waitForDeployment();

    const proxyAddress = await proxyContract.getAddress();

    console.log("VotingLogic proxy deployed to:", proxyAddress);
    console.log(
        "VotingLogic impl deployed to:",
        await getImplementationAddress(ethers.provider, proxyAddress),
    );

    await tenderly.verify({
        name: ProxyPlaceholderName,
        address: proxyAddress,
    });
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
7
Запустите скрипт
8
Virtual Environment
TENDERLY_AUTOMATIC_VERIFICATION=true \
TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true \
npx hardhat run scripts/deploy.ts
Публичная сеть
При деплое в публичный mainnet или testnet укажите, нужна ли публичная или приватная верификация.
TENDERLY_AUTOMATIC_VERIFICATION=true \
TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true \
TENDERLY_PRIVATE_VERIFICATION=true \
npx hardhat run scripts/deploy.ts --network mainnet_base

Обходное решение для более ранних версий

При использовании @tenderly/hardhat-tenderly версий < 1.10.0 и < 2.1.0 это обходное решение включает автоматическую верификацию. Необходимо верифицировать следующее:
  • Прокси-контракт (например, прокси от OpenZeppelin)
  • Реализацию за прокси
  • Любые зависимости реализации
  • Новые экземпляры реализации, развёрнутые при апгрейдах
Процесс верификации различается в зависимости от типа прокси-контракта и реализации.

Обзор

В этом руководстве мы будем использовать пример Hardhat-проекта и плагин @tenderly/hardhat-tenderly для демонстрации верификации альтернатив OpenZeppelin: UUPSUpgradeable, TransparentUpgradeableProxy и BeaconProxy.
Прокси-контракты нужно верифицировать вручную. Отключите автоматическую верификацию так:
example.ts
// use manual verification
tenderly.setup({ automaticVerifications: false });
Для получения адреса развёрнутой реализации используйте пакет @openzeppelin/upgrades-core и функцию getImplementationAddress. Верифицировать реализацию прокси обычно несложно; верифицируйте её так же, как любой другой контракт.
example.ts
await tenderly.verify({
  // the new implementation contract
  name: 'VaultV2',
  // the address where implementation is deployed
  address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
});
Чтобы верифицировать экземпляр прокси, необходимо выполнить два предварительных шага:
  1. Загрузить точный смарт-контракт прокси в зависимости от типа используемого прокси, чтобы он был скомпилирован. Вам нужно будет импортировать прокси-контракты через компилятор, создав фиктивный .sol-файл.
  2. Изменить hardhat.config.ts, чтобы указать настройки, с которыми компилировались контракты OpenZepplin.
После выполнения этих шагов вы можете верифицировать прокси так же, как любой другой контракт.
example.ts
await tenderly.verify({
  name: 'ERC1967Proxy', // or TransparentUpgradeableProxy or BeaconProxy
  address: await proxy.getAddress(),
});
1
Клонируйте пример репозитория
2
git clone git@github.com:Tenderly/tenderly-examples.git
cd contract-verifications
npm i
3
Настройте Tenderly CLI
4
brew tap tenderly/tenderly && brew install tenderly
tenderly login
5
Для вариантов установки не через Homebrew см. репозиторий Tenderly CLI.
6
Настройте Hardhat
7
В hardhat.config.ts задайте tenderly.username и tenderly.project в соответствии с вашими slug’ами проекта и пользователя.
8
Создайте Virtual Environment
9
Самый быстрый способ развернуть и верифицировать контракты — использовать Virtual Environment.
10
В Tenderly Dashboard откройте Virtual Environments и создайте новую среду. Выберите базовую сеть, от которой сделать форк, и Chain ID, затем скопируйте Admin RPC URL со страницы деталей Virtual Environment.
11
Добавьте RPC URL в файл .env:
12
TENDERLY_VIRTUAL_TESTNET_RPC=https://virtual.<network>.rpc.tenderly.co/<your-uuid>
13
Затем сошлитесь на него в hardhat.config.ts:
14
networks: {
  virtualMainnet: {
    url: process.env.TENDERLY_VIRTUAL_TESTNET_RPC!,
    chainId: 73571, // the Chain ID you set when creating the Virtual Environment
  },
},
15
Полное пошаговое руководство см. в быстром старте Virtual Environment.
16
Запустите тесты
17
rm -rf .openzeppelin && npx hardhat test --network virtualMainnet
18
При повторном деплое контрактов сначала удалите папку .openzeppelin. Она кеширует информацию о прокси и их реализациях.
19
Загрузите прокси-контракты
20
Чтобы верифицировать прокси-контракт, создайте файл DummyProxy.sol и импортируйте прокси-контракты OpenZepplin, с которыми работаете. Тем самым эти контракты будут загружены и пройдут через компилятор, что даст возможность сослаться на точный исходный код прокси при верификации.
21
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;


abstract contract ERC1967ProxyAccess is ERC1967Proxy {}
abstract contract UpgradableBeaconAccess is UpgradeableBeacon {}
abstract contract BeaconProxyAccess is BeaconProxy {}
abstract contract TransparentUpgradeableProxyAccess is TransparentUpgradeableProxy {}
22
Настройте переопределения компилятора Solidity
23
Приведённая ниже карта переопределений выведена для @openzeppelin/contracts-upgradeable версии 4.9.1. Для других версий пакета она может отличаться.
24
После компиляции прокси-контрактов Openzepplin необходимо также указать:
25
  • Версию компилятора Solidity, которая использовалась для компиляции контрактов
  • Настройки оптимизации, использованные плагином upgrades от Openzepplin при деплое/апгрейде прокси
  • 26
    Плагин hardhat-tenderly использует для верификации как исходный код смарт-контрактов, так и настройки компилятора. Если что-то из этого указано неверно, верификация не пройдёт.
    27
    Добавьте следующую карту overrides в раздел config.solidity объекта Hardhat User config.
    28
    const config: HardhatUserConfig = {
      solidity: {
        compilers: [{ version: '0.8.18' } /* OTHER COMPILER VERSIONS*/],
        overrides: {
          '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol': {
            version: '0.8.9',
            settings: {
              optimizer: {
                enabled: true,
                runs: 200,
              },
            },
          },
          '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol': {
            version: '0.8.9',
            settings: {
              optimizer: {
                enabled: true,
                runs: 200,
              },
            },
          },
    
          '@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol': {
            version: '0.8.9',
            settings: {
              optimizer: {
                enabled: true,
                runs: 200,
              },
            },
          },
          '@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol': {
            version: '0.8.9',
            settings: {
              optimizer: {
                enabled: true,
                runs: 200,
              },
            },
          },
          'contracts/proxy.sol': {
            version: '0.8.9',
            settings: {
              optimizer: {
                enabled: true,
                runs: 200,
              },
            },
          },
        },
      },
      /* OTHER CONFIG */
    };
    

    Верификация по типу прокси

    Эти примеры кода показывают, как верифицировать реализацию и прокси для трёх паттернов прокси OpenZeppelin. Примеры используют проксированный контракт Vault, ссылающийся на ERC-20 токен (TToken).

    UUPS-прокси

    Чтобы верифицировать UUPS-прокси и связанную информацию, вызовите плагин hardhat-tenderly дважды:
    1. Для верификации реализации необходимо указать:
    • name вашего проксированного контракта (в нашем случае Vault)
    • Адрес, по которому был развёрнут контракт, полученный методом getImplementationAddress из @openzeppelin/upgrades-core.
    1. Для верификации прокси укажите:
    • ERC1967Proxy в качестве name прокси-контракта
    • Адрес прокси proxy.address
    example.ts
    await tenderly.verify(
      {
        name: 'Vault',
        address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
      },
      {
        name: 'ERC1967Proxy',
        address: await proxy.getAddress(),
      },
    );
    

    Полный пример кода

    Ниже приведён полный тест Hardhat, который делает следующее:
    • Разворачивает TToken (нужен для vault)
    • Разворачивает Vault как прокси, инициализированный контрактом TToken
    • Верифицирует экземпляр прокси (ERC1967Proxy), развёрнутый по адресу await proxy.getAddress()
    • Верифицирует экземпляр реализации Vault, развёрнутый по адресу getImplementationAddress(ethers.provider, await proxy.getAddress())
    • Апгрейдит прокси до VaultV2
    example.ts
    
    describe('Vault', () => {
      it('uups proxy deployment and verification', async () => {
        const VaultFactory = await ethers.getContractFactory('Vault');
        const TokenFactory = await ethers.getContractFactory('TToken');
    
        let token = await ethers.deployContract('TToken');
        token = await token.waitForDeployment();
        const tokenAddress = await token.getAddress();
    
        await tenderly.verify({
          name: 'TToken',
          address: tokenAddress,
        });
    
        let proxy = await upgrades.deployProxy(VaultFactory, [tokenAddress], {
          kind: 'uups',
        });
        await proxy.waitForDeployment();
        const proxyAddress = await proxy.getAddress();
    
        console.log('Deployed UUPS ', {
          proxy: proxyAddress,
          implementation: await getImplementationAddress(ethers.provider, proxyAddress),
        });
    
        await tenderly.verify(
          {
            name: 'Vault',
            address: await getImplementationAddress(ethers.provider, proxyAddress),
          },
          {
            name: 'ERC1967Proxy',
            address: proxyAddress,
          },
        );
    
        // upgrade
        const vaultV2Factory = await ethers.getContractFactory('VaultV2');
        proxy = (await upgrades.upgradeProxy(proxy, vaultV2Factory, {
          kind: 'uups',
        })) as Vault;
    
        await proxy.waitForDeployment();
    
        console.log('Upgraded UUPS ', {
          proxy: proxyAddress,
          implementation: await getImplementationAddress(ethers.provider, proxyAddress),
        });
    
        await tenderly.verify({
          name: 'VaultV2',
          address: await getImplementationAddress(ethers.provider, proxyAddress),
        });
      });
    });
    

    Transparent-прокси

    Чтобы верифицировать UUPS-прокси и связанную информацию, вызовите hardhat-tenderly, передав два контракта: Vault для реализации и TransparentUpgradeableProxy для самого прокси.
    example.ts
    await tenderly.verify(
      {
        name: 'Vault',
        address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
      },
      {
        name: 'TransparentUpgradeableProxy',
        address: await proxy.getAddress(),
      },
    );
    
    1. Для верификации реализации укажите:
    • name вашего проксированного контракта (в нашем случае Vault)
    • Адрес, по которому был развёрнут контракт, полученный методом getImplementationAddress из @openzeppelin/upgrades-core.
    1. Для верификации прокси укажите:
    • TransparentUpgradeableProxy в качестве name прокси-контракта
    • Адрес прокси await proxy.getAddress()

    Полный пример кода

    example.ts
    
    describe('Vault', () => {
      it('transparent upgradable proxy deployment and verification', async () => {
        const VaultFactory = await ethers.getContractFactory('Vault');
        const TokenFactory = await ethers.getContractFactory('TToken');
    
        let token = await ethers.deployContract('TToken');
        token = await token.waitForDeployment();
        const tokenAddress = await token.getAddress();
    
        await tenderly.verify({
          name: 'TToken',
          address: tokenAddress,
        });
    
        let proxy = await upgrades.deployProxy(VaultFactory, [tokenAddress], {
          kind: 'transparent',
        });
        await proxy.waitForDeployment();
        const proxyAddress = await proxy.getAddress();
    
        console.log('Deployed transparent', {
          proxy: proxyAddress,
          implementation: await getImplementationAddress(ethers.provider, proxyAddress),
        });
    
        await tenderly.verify(
          {
            name: 'Vault',
            address: await getImplementationAddress(ethers.provider, proxyAddress),
          },
          {
            name: 'TransparentUpgradeableProxy',
            address: proxyAddress,
          },
        );
    
        // upgrade
        const vaultV2Factory = await ethers.getContractFactory('VaultV2');
    
        proxy = (await upgrades.upgradeProxy(proxy, vaultV2Factory, {
          kind: 'transparent',
        })) as Vault;
    
        await proxy.waitForDeployment();
    
        console.log('Upgraded transparent ', {
          proxy: proxyAddress,
          implementation: await getImplementationAddress(ethers.provider, proxyAddress),
        });
    
        await tenderly.verify({
          name: 'VaultV2',
          address: await getImplementationAddress(ethers.provider, proxyAddress),
        });
      });
    });
    

    Beacon-прокси

    Чтобы верифицировать Beacon-прокси и связанную информацию, необходимо верифицировать два контракта: Vault (реализация) и UpgradableBeacon от OpenZepplin:
    example.ts
    await tenderly.verify(
      {
        name: 'Vault',
        address: await getImplementationAddressFromBeacon(ethers.provider, await beacon.getAddress()),
      },
      {
        name: 'UpgradeableBeacon',
        address: await beacon.getAddress(),
      },
    );
    

    Полный пример кода

    example.ts
    
    describe('Vault', () => {
      it('beacon proxy deployment and verification', async () => {
        const VaultFactory = await ethers.getContractFactory('Vault');
        const TokenFactory = await ethers.getContractFactory('TToken');
    
        let token = await ethers.deployContract('TToken');
        token = await token.waitForDeployment();
        const tokenAddress = await token.getAddress();
    
        await tenderly.verify({
          name: 'TToken',
          address: tokenAddress,
        });
    
        let beacon = (await upgrades.deployBeacon(VaultFactory)) as UpgradeableBeacon;
    
        await beacon.waitForDeployment();
        const beaconAddress = await beacon.getAddress();
    
        let vault = await upgrades.deployBeaconProxy(beacon, VaultFactory, [tokenAddress], {
          initializer: 'initialize',
        });
        await vault.waitForDeployment();
    
        console.log('Deployed beacon ', {
          proxy: beaconAddress,
          implementation: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
          beacon: beaconAddress,
        });
    
        await tenderly.verify(
          {
            name: 'Vault',
            address: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
          },
          {
            name: 'UpgradeableBeacon',
            address: beaconAddress,
          },
        );
    
        const vaultV2Factory = await ethers.getContractFactory('VaultV2');
    
        // upgrade
        vault = await upgrades.deployBeaconProxy(beacon, vaultV2Factory, [tokenAddress]);
    
        await upgrades.upgradeBeacon(beaconAddress, vaultV2Factory, {});
    
        console.log('Upgraded beacon ', {
          proxy: beaconAddress,
          implementation: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
          beacon: beaconAddress,
        });
    
        await tenderly.verify({
          name: 'VaultV2',
          address: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
        });
      });
    });