跳转到主要内容
使用 Foundry 或 Hardhat 将 Solidity 合约部署到 Virtual Environment。部署在 Virtual Environment 上的合约被称为虚拟合约(virtual contracts),会出现在 Contracts 部分的 Virtual Contracts 选项卡下。 在部署时验证合约。经过验证的合约会解锁 Debugger、Gas Profiler、Simulator 以及浏览器中更丰富的交易视图。

开始之前

在 Virtual Environment 上进行验证不需要单独的 access key:RPC URL 本身即用于身份验证,验证器端点就是该 URL 后附加 /verify
默认情况下,在 Virtual Environment 上验证的合约是私有的。根据公共浏览器的可见性设置,代码可能对外可见。在验证任何您想保密的内容之前,请检查公共浏览器开关和验证可见性

部署与验证

配置 foundry.toml

Tenderly 的验证器会将 Solidity 编译器附加到已部署字节码上的元数据哈希与您提交的源代码进行匹配。通过显式设置 cbor_metadatabytecode_hash,将元数据保留在编译输出中:
foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.24"
optimizer = true
optimizer_runs = 200

# Required for Tenderly contract verification:
# keep the CBOR metadata so the verifier can match the on-chain bytecode hash.
cbor_metadata = true
bytecode_hash = "ipfs"
cbor_metadata = truebytecode_hash = "ipfs" 是 Foundry 的默认值,但某些模板会剥离它们;bytecode_hash = "none" 会破坏验证。固定 solc_version 和 optimizer 设置,以便重新验证时编译出相同的字节码。如果您的项目使用像 OpenZeppelin 这样的库,请在 remappings.txt 中显式声明 remappings:
remappings.txt
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
forge-std/=lib/forge-std/src/
使用自动检测的 remappings 时,验证载荷可能包含与构建时使用的不同的 remapping 字符串,从而导致字节码不匹配的失败。

设置环境变量

showLineNumbers
export TENDERLY_VIRTUAL_TESTNET_RPC=...   # paste your RPC URL
export TENDERLY_VERIFIER_URL=$TENDERLY_VIRTUAL_TESTNET_RPC/verify
export PRIVATE_KEY=...                    # deployer private key
在部署之前进行连通性检查。cast chain-id 应打印 Virtual Environment 的链 ID,部署者余额应为非零:
cast chain-id --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC
cast balance <DEPLOYER_ADDRESS> --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC

使用 forge create 部署

带上 verify 标志运行 forge create。构造函数参数作为原始值传入;Foundry 会对它们进行 ABI 编码,并将编码后的形式提交给验证器:
showLineNumbers
forge create src/Counter.sol:Counter \
  --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC \
  --private-key $PRIVATE_KEY \
  --broadcast \
  --verify \
  --verifier custom \
  --verifier-url $TENDERLY_VERIFIER_URL \
  --constructor-args 42
--constructor-args 放在最后。它会贪婪地吞掉命令行的其余部分,因此放在它之后的任何标志都会被视为另一个构造函数参数。

使用 Foundry 脚本部署

使用 forge script 在一次运行中部署多个合约。脚本中的每个 new Foo(...) 都会自动提交进行验证,构造函数参数会从广播日志中正确获取。导入会被透明处理:验证器会收到一个标准 JSON 载荷,其中包含编译器接触过的每个源文件,因此本地导入和库导入(例如 OpenZeppelin)无需额外步骤即可完成验证。使用 --slow 让交易一次发送一笔。不加它,广播批处理可能在上一笔交易被确认之前提交下一笔,这在托管 RPC 上不稳定,并且可能与验证步骤发生竞争。
showLineNumbers
forge script script/Counter.s.sol:CounterScript \
  --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC \
  --private-key $PRIVATE_KEY \
  --broadcast \
  --slow \
  --verify \
  --verifier custom \
  --verifier-url $TENDERLY_VERIFIER_URL

验证已有的合约

已经部署的合约可以使用 forge verify-contract 验证。与 forge create 不同,此命令期望构造函数参数已经过 ABI 编码;请使用 cast abi-encode 生成它们:
showLineNumbers
forge verify-contract $COUNTER_ADDRESS src/Counter.sol:Counter \
  --verifier custom \
  --verifier-url $TENDERLY_VERIFIER_URL \
  --constructor-args $(cast abi-encode "constructor(uint256)" 42) \
  --watch
--watch 会轮询验证器直到验证完成并打印结果。对于多合约部署,请为每个已部署地址运行一次 forge verify-contract

检查仪表板

在 Tenderly Dashboard 中,打开 Contracts 并选择 Virtual Contracts 选项卡。您新部署的合约会显示在那里,并可看到每个合约的验证状态。

故障排除

症状可能原因修复
验证器返回 unauthorized / -32004 响应验证器 URL 错误。常见错误是附加了 /verify/etherscan 而不是 /verify确保验证器 URL 精确地以 /verify 结尾。
Bytecode does not match deployed contract提交的源代码编译结果与部署的不同:optimizer_runs 不匹配、solc_version 不同或元数据哈希被剥离。foundry.toml 中固定 solc_versionoptimizeroptimizer_runsbytecode_hash。重新验证前运行 forge clean && forge build
Failed to deserialize content验证器返回了一个 Foundry 无法解析的错误字符串,通常包裹着上游身份验证或路径错误。重新检查 URL 并使用 -vvvv 重新运行以查看原始响应。
forge createDry run enabled, not broadcasting transaction未传入 --broadcast,或被 --constructor-args 吞掉。--constructor-args 移到最后一个标志。
forge script 只有第一个合约被验证RPC 竞态:第二笔交易在第一笔收据之前就已提交。添加 --slow
验证器报告 OK,但浏览器仍显示未验证UI 缓存。硬刷新浏览器页面。API 响应中的状态是权威的。

后续步骤