跳转到主要内容
适用于: Virtual Environments 和公共网络(主网和测试网)。同一插件同时处理两者;请在 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 目前不支持代理验证。 如果您使用的是较低版本的插件且无法升级,请使用下面的手动变通方法

自动验证

该插件可验证三种代理模式:TransparentUpgradeableProxyUUPSUpgradeableProxyBeaconProxy
1
设置自动填充标志
2
在底层,该插件通过 @openzeppelin/hardhat-upgrades 扩展驱动 @nomicfoundation/hardhat-verify。设置 TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true 让插件为您填充 @nomicfoundation/hardhat-verify 的验证 URL。
3
TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true
4
编写部署脚本
5
如需更清晰的视图,您可以查看此 GitHub 仓库并转到 scripts/proxy/ 查看完整示例。
6
Automatic
使用来自 @openzeppelin/hardhat-upgrades 扩展的 deployProxy 像往常一样部署代理。
您必须捕获 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;
});
Manual
使用手动验证方法验证代理是在代理合约部署完成后,通过调用 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
Public network
部署到公共主网或测试网时,请指定您要进行公开还是私密验证。
TENDERLY_AUTOMATIC_VERIFICATION=true \
TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true \
TENDERLY_PRIVATE_VERIFICATION=true \
npx hardhat run scripts/deploy.ts --network mainnet_base

较低版本的变通方法

在使用版本 < 1.10.0< 2.1.0@tenderly/hardhat-tenderly 时,此变通方法将启用自动验证。 您需要验证以下内容:
  • 代理合约(例如 OpenZeppelin 的代理)
  • 代理背后的实现
  • 实现的任何依赖项
  • 通过升级部署的新实现实例
验证过程根据代理合约类型和实现而有所不同。

概述

在本指南中,我们将使用一个示例 Hardhat 项目@tenderly/hardhat-tenderly 插件来演示 OpenZeppelin 的 UUPSUpgradeableTransparentUpgradeableProxyBeaconProxy 替代方案的验证。
代理合约需要手动验证。像这样关闭自动验证:
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.usernametenderly.project 设置为您的项目和用户名 slug
8
创建 Virtual Environment
9
部署和验证合约最快的方式是在 Virtual Environment 上进行。
10
Tenderly Dashboard 中,打开 Virtual Environments 并创建一个新的。选择要 fork 的基础网络和一个 Chain ID,然后从 Virtual Environment 的详情页面复制 Admin RPC URL
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 编译器版本
  • Openzepplin 的升级插件在执行代理部署/升级时使用的优化设置
  • 26
    hardhat-tenderly 插件同时使用智能合约的源代码和编译器设置进行验证。如果其中任何设置不正确,验证将失败。
    27
    在 Hardhat User 配置对象的 config.solidity 部分添加以下 overrides 映射。
    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 三种代理模式的实现和代理。这些示例使用一个引用 ERC-20 代币(TToken)的代理化 Vault 合约。

    UUPS 代理

    要验证 UUPS 代理及其底层信息,请调用 hardhat-tenderly 插件两次:
    1. 要验证实现,您需要提供以下内容:
    • 代理化合约的 name(在我们的例子中是 Vault
    • 使用来自 @openzeppelin/upgrades-coregetImplementationAddress 方法获取的合约部署地址。
    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 合约初始化
    • 验证部署在 await proxy.getAddress() 的代理(ERC1967Proxy)实例
    • 验证部署在 getImplementationAddress(ethers.provider, await proxy.getAddress()) 的实现实例 Vault
    • 将代理升级到 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
    • 使用来自 @openzeppelin/upgrades-coregetImplementationAddress 方法获取的合约部署地址。
    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(实现)和 OpenZepplin 的 UpgradableBeacon
    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),
        });
      });
    });