适用于: 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 目前不支持代理验证。
如果您使用的是较低版本的插件且无法升级,请使用下面的手动变通方法。
自动验证
该插件可验证三种代理模式:TransparentUpgradeableProxy、UUPSUpgradeableProxy 和 BeaconProxy。
在底层,该插件通过 @openzeppelin/hardhat-upgrades 扩展驱动 @nomicfoundation/hardhat-verify。设置 TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true 让插件为您填充 @nomicfoundation/hardhat-verify 的验证 URL。
TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true
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;
});
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 的 UUPSUpgradeable、TransparentUpgradeableProxy 和 BeaconProxy 替代方案的验证。
代理合约需要手动验证。像这样关闭自动验证:// use manual verification
tenderly.setup({ automaticVerifications: false });
要获取已部署实现的地址,请使用 @openzeppelin/upgrades-core 包和 getImplementationAddress 函数。
验证代理实现通常很简单;就像验证任何其他合约一样验证它。
await tenderly.verify({
// the new implementation contract
name: 'VaultV2',
// the address where implementation is deployed
address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
});
要验证代理实例,您需要完成以下两个准备步骤:
- 根据您使用的代理类型加载代理的确切智能合约,以便对其进行编译。您需要通过创建一个虚拟 .sol 文件将代理合约通过编译器导入。
- 修改
hardhat.config.ts 以指定 OpenZepplin 合约编译时使用的设置。
完成这些步骤后,您可以像验证任何其他合约一样继续验证代理。
await tenderly.verify({
name: 'ERC1967Proxy', // or TransparentUpgradeableProxy or BeaconProxy
address: await proxy.getAddress(),
});
git clone git@github.com:Tenderly/tenderly-examples.git
cd contract-verifications
npm i
brew tap tenderly/tenderly && brew install tenderly
tenderly login
在
hardhat.config.ts 中,将
tenderly.username 和
tenderly.project 设置为您的
项目和用户名 slug。
在
Tenderly Dashboard 中,打开
Virtual Environments 并创建一个新的。选择要 fork 的基础网络和一个 Chain ID,然后从 Virtual Environment 的详情页面复制
Admin RPC URL。
TENDERLY_VIRTUAL_TESTNET_RPC=https://virtual.<network>.rpc.tenderly.co/<your-uuid>
然后在 hardhat.config.ts 中引用它:
networks: {
virtualMainnet: {
url: process.env.TENDERLY_VIRTUAL_TESTNET_RPC!,
chainId: 73571, // the Chain ID you set when creating the Virtual Environment
},
},
rm -rf .openzeppelin && npx hardhat test --network virtualMainnet
重新部署合约时,先删除 .openzeppelin 文件夹。它缓存有关代理及其实现的信息。
要验证代理合约,请创建一个 DummyProxy.sol 文件并导入您正在使用的 OpenZepplin 代理合约。这样一来,这些合约被加载并已通过编译器,使您能够在验证期间引用代理的确切合约源代码。
// 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 {}
下面的覆盖映射是针对 @openzeppelin/contracts-upgradeable 版本 4.9.1 得出的。
它们可能与该包的其他版本有所不同。
编译 Openzepplin 的代理合约后,您还需要指定以下内容:
编译合约时使用的 Solidity 编译器版本
Openzepplin 的升级插件在执行代理部署/升级时使用的优化设置
hardhat-tenderly 插件同时使用智能合约的源代码和编译器设置进行验证。如果其中任何设置不正确,验证将失败。
在 Hardhat User 配置对象的 config.solidity 部分添加以下 overrides 映射。
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 插件两次:
- 要验证实现,您需要提供以下内容:
- 代理化合约的
name(在我们的例子中是 Vault)
- 使用来自
@openzeppelin/upgrades-core 的 getImplementationAddress 方法获取的合约部署地址。
- 要验证代理,请提供以下内容:
ERC1967Proxy 作为代理合约的 name
- 代理地址
proxy.address
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
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 用于代理本身。
await tenderly.verify(
{
name: 'Vault',
address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
},
{
name: 'TransparentUpgradeableProxy',
address: await proxy.getAddress(),
},
);
- 要验证实现,请提供以下内容:
- 代理化合约的
name(在我们的例子中是 Vault)
- 使用来自
@openzeppelin/upgrades-core 的 getImplementationAddress 方法获取的合约部署地址。
- 要验证代理,请提供以下内容:
TransparentUpgradeableProxy 作为代理合约的 name
- 代理地址
await proxy.getAddress()
完整代码示例
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:
await tenderly.verify(
{
name: 'Vault',
address: await getImplementationAddressFromBeacon(ethers.provider, await beacon.getAddress()),
},
{
name: 'UpgradeableBeacon',
address: await beacon.getAddress(),
},
);
完整代码示例
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),
});
});
});