适用于: 公共网络(主网和测试网)。若要在 Virtual Environment 上验证合约,请参阅部署并验证合约和使用 Foundry 验证代理合约。
forge create、forge script 和 forge verify-contract 命令部署的智能合约。验证可以是私密的(源代码仅在您的 Tenderly 项目内可见)或公开的(任何拥有链接的人都可以查看)。
开始之前
您需要 Tenderly 账户和项目的 slug、一个 Tenderly 访问密钥(Dashboard → Account Settings → Authorization),以及目标网络上有资金的账户。 Tenderly 的验证器会将 Solidity 编译器附加到部署字节码的元数据哈希与您提交的源代码进行匹配。请保留编译输出中的元数据,并固定编译器设置,使部署时和验证时之间不会发生变化:foundry.toml
cbor_metadata = true 和 bytecode_hash = "ipfs" 是 Foundry 的默认值,但某些模板会剥离它们;bytecode_hash = "none" 会破坏验证。部署和验证之间的编译器设置漂移会导致 Bytecode does not match deployed contract 失败。
以下示例使用 Base Sepolia(链 ID 84532)。设置环境:
.env
验证器 URL
每个 Foundry 验证命令都需要指向 Tenderly 验证 API 的--verifier-url,以及用于认证的 --etherscan-api-key $TENDERLY_ACCESS_KEY:
| 段 | 含义 |
|---|---|
account/$TENDERLY_ACCOUNT | 您的 Tenderly 账户(用户或组织)slug。区分大小写。 |
project/$TENDERLY_PROJECT | 已验证合约归属的项目。 |
network/$NETWORK_ID | 合约部署的链,以十进制链 ID 表示(1 表示 Ethereum Mainnet,8453 表示 Base,84532 表示 Base Sepolia)。 |
| 无后缀 | 私密验证:合约仅在您的项目内可见。 |
/public 后缀 | 公开验证:合约源代码对任何拥有链接的人可见,无需 Tenderly 登录。 |
私密验证
私密验证的合约仅对项目成员可见,位于 Dashboard 中的 Contracts 下。showLineNumbers
forge verify-contract 期望构造函数参数已进行 ABI 编码;使用 cast abi-encode 生成它们。--watch 会轮询验证器直到验证完成并打印结果。
Foundry 在成功时会输出一行
URL: https://etherscan.io/address/...。这是 Etherscan 兼容流程的显示怪癖;合约实际上是在 Tenderly 验证的,而非 Etherscan。请在 Dashboard 的 Contracts 下确认。公开验证
将验证器 URL 换成带/public 后缀的变体;其他不变。已验证的源代码页面对任何拥有链接的人可访问,无需 Tenderly 登录。
showLineNumbers
一步完成部署和验证
forge create 和 forge script 内联接受相同的标志,因此部署和验证作为一条命令运行。与 forge verify-contract 不同,两者都以原始值形式接受构造函数参数,并为您进行 ABI 编码:
showLineNumbers
--slow 的 forge script;脚本部署的每个合约都会自动验证,其构造函数参数从广播日志中获取。没有 --slow,广播批处理可能会在前一个交易确认之前就提交下一个交易,这可能与验证步骤竞争:
showLineNumbers
验证您未部署的合约
当合约已在公共浏览器(Etherscan、Basescan)上验证但未在您的 Tenderly 项目中验证时,请使用forge clone 将已验证的源代码克隆到本地,然后通过 Tenderly 的 API 重新验证:
showLineNumbers
forge clone 从源链的兼容 Etherscan 的 API 下载已验证的源代码(每条链请使用相应浏览器的密钥),重建项目布局,并固定原始部署者使用的编译器设置。在克隆目录中运行 forge build 以确认它可以编译;导入解析错误通常追溯到 remappings.txt。
然后针对 Tenderly 进行验证:
showLineNumbers
Bytecode does not match deployed contract,请显式传递原始编译器设置。所有这些设置都列在合约已经验证的浏览器页面上,位于合约源代码部分下:
showLineNumbers
位于 lib 目录中的合约
forge build 只编译从 src/ 可达的内容。一个仅存在于 lib/ 依赖中的合约(一个代理、一个标准 ERC 实现)永远不会进入构建缓存,forge verify-contract 会失败并显示:
src/Imports.sol 来导入该合约,然后重新构建:
src/Imports.sol
Imports.sol 永远不会被发送到验证器;它只强制编译器缓存该合约及其依赖项。然后使用合约的完整 lib/ 路径进行验证,因为源代码物理上位于那里。
对于您未部署的 TransparentUpgradeableProxy,构造函数是 (address _logic, address initialOwner, bytes _data),其中大部分可以从链状态重建:
showLineNumbers
foundry.toml 或部署脚本。
故障排查
| 症状 | 可能原因 | 修复方法 |
|---|---|---|
unauthorized | 缺少或错误的 --etherscan-api-key,或访问密钥已被撤销。 | 生成新的访问密钥并重新设置 TENDERLY_ACCESS_KEY。 |
not found | 验证器 URL 中的账户 slug、项目 slug 或链 ID 错误。 | 重新检查每个 URL 段。账户 slug 区分大小写。 |
Bytecode does not match deployed contract | 提交的源代码与已部署的代码编译不同:optimizer_runs 不匹配、solc_version 或 EVM 版本不同,或元数据哈希被剥离。 | 在 foundry.toml 中固定 solc_version、optimizer、optimizer_runs 和 bytecode_hash,或显式传递 --compiler-version --optimizer-runs --evm-version。重新验证前运行 forge clean && forge build。 |
Failed to deserialize content | 验证器返回了 Foundry 无法解析的错误字符串,通常包装了上游认证或路径错误。 | 重新检查 URL,并使用 -vvvv 重新运行以查看原始响应。 |
Failed to get standard json input - cannot resolve file at lib/... | 该合约从未从 src/ 导入,因此从构建缓存中缺失。 | 创建 src/Imports.sol 导入它,然后运行 forge build --force。请参阅位于 lib 目录中的合约。 |
在 forge create 上显示 Dry run enabled, not broadcasting transaction | 未传递 --broadcast,或被 --constructor-args 吞没。 | 将 --constructor-args 移至最后一个标志。 |
在 forge script 上只有第一个合约被验证 | RPC 竞争:第二个交易在第一个收据之前提交。 | 添加 --slow。 |
| 验证成功但合约未出现在项目中 | 验证器 URL 中的项目 slug 错误。 | 使用正确的 slug 重新验证。合约会归档到 URL 指向的任何项目下。 |