进阶 getfoundry.sh 2026-10-10 09:32:11 · 9 阅读
第7章 多链部署
多链部署
本指南介绍如何在多条链上部署合约,涵盖地址一致性、链特定配置以及协调部署脚本。
各链 RPC 配置
在 foundry.toml 中定义 RPC 端点:
[rpc_endpoints]
mainnet = "https://ethereum.reth.rs/rpc"
optimism = "https://mainnet.optimism.io"
arbitrum = "https://arb1.arbitrum.io/rpc"
base = "https://mainnet.base.org"
sepolia = "https://sepolia.drpc.org"
在脚本中引用这些端点:
$ forge script script/Deploy.s.sol --broadcast --rpc-url mainnet
$ forge script script/Deploy.s.sol --broadcast --rpc-url optimism
感知链的部署脚本
使用 block.chainid 配置链特定参数:
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract DeployScript is Script {
function run() public {
Config memory config = getConfig();
vm.startBroadcast();
Token token = new Token(config.admin, config.initialSupply);
vm.stopBroadcast();
console.log("Chain:", block.chainid);
console.log("Token:", address(token));
}
struct Config {
address admin;
uint256 initialSupply;
address weth;
}
function getConfig() internal view returns (Config memory) {
if (block.chainid == 1) {
return Config({
admin: 0x1234567890123456789012345678901234567890,
initialSupply: 1_000_000 ether,
weth: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
});
} else if (block.chainid == 10) {
return Config({
admin: 0x1234567890123456789012345678901234567890,
initialSupply: 500_000 ether,
weth: 0x4200000000000000000000000000000000000006
});
} else if (block.chainid == 42161) {
return Config({
admin: 0x1234567890123456789012345678901234567890,
initialSupply: 500_000 ether,
weth: 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
});
} else {
revert("Unsupported chain");
}
}
}
使用 CREATE2 实现确定性地址
利用 CREATE2 在所有链上部署到相同地址。关于完整的配置要求,请参阅《确定性部署》指南。
// script/DeployCreate2.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
interface ICreateX {
function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address);
function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external view returns (address);
}
contract DeployCreate2 is Script {
// CreateX 在所有链上部署于相同地址
ICreateX constant CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
bytes32 constant SALT = keccak256("my-protocol-v1");
function run() public {
bytes memory initCode = abi.encodePacked(
type(Token).creationCode,
abi.encode("MyToken", "MTK") // 构造参数在各链间必须保持一致
);
// 计算预期地址
address expected = CREATEX.computeCreate2Address(SALT, keccak256(initCode));
console.log("Expected address:", expected);
vm.startBroadcast();
address deployed = CREATEX.deployCreate2(SALT, initCode);
vm.stopBroadcast();
require(deployed == expected, "Address mismatch");
console.log("Deployed at:", deployed);
}
}
部署到所有链:
$ forge script script/DeployCreate2.s.sol --broadcast --rpc-url mainnet
$ forge script script/DeployCreate2.s.sol --broadcast --rpc-url optimism
$ forge script script/DeployCreate2.s.sol --broadcast --rpc-url arbitrum
使用环境变量配置链参数
为获得更多灵活性,可使用环境变量:
function getConfig() internal view returns (Config memory) {
return Config({
admin: vm.envAddress("ADMIN_ADDRESS"),
initialSupply: vm.envOr("INITIAL_SUPPLY", uint256(1_000_000 ether)),
weth: vm.envAddress(string.concat("WETH_", vm.toString(block.chainid)))
});
}
ADMIN_ADDRESS=0x1234567890123456789012345678901234567890
INITIAL_SUPPLY=1000000000000000000000000
WETH_1=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
WETH_10=0x4200000000000000000000000000000000000006
WETH_42161=0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
部署清单
通过 JSON 文件追踪各链的部署状态:
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract DeployScript is Script {
function run() public {
vm.startBroadcast();
Token token = new Token("MyToken", "MTK");
vm.stopBroadcast();
// 将部署信息写入 JSON
string memory json = vm.serializeAddress("deployment", "token", address(token));
json = vm.serializeUint("deployment", "chainId", block.chainid);
json = vm.serializeUint("deployment", "blockNumber", block.number);
string memory path = string.concat("deployments/", vm.toString(block.chainid), ".json");
vm.writeJson(json, path);
}
}
批量部署脚本
使用 Shell 脚本自动化多链部署:
# scripts/deploy-all.sh
#!/bin/bash
set -e
CHAINS=("mainnet" "optimism" "arbitrum" "base")
for chain in "${CHAINS[@]}"; do
echo "Deploying to $chain..."
forge script script/Deploy.s.sol \
--broadcast \
--verify \
--rpc-url "$chain" \
--account deployer
done
echo "All deployments complete!"
跨链验证
为每条链配置 Etherscan API 密钥:
# foundry.toml
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
optimism = { key = "${OPTIMISM_ETHERSCAN_API_KEY}", url = "https://api-optimistic.etherscan.io/api" }
arbitrum = { key = "${ARBISCAN_API_KEY}", url = "https://api.arbiscan.io/api" }
base = { key = "${BASESCAN_API_KEY}", url = "https://api.basescan.org/api" }
在部署时执行验证:
$ forge script script/Deploy.s.sol --broadcast --verify --rpc-url optimism
最佳实践
| 实践 | 说明 |
|---|---|
| 使用 CREATE2 | 确保跨链地址一致 |
| 链无关字节码 | 避免在构造函数中使用链特定的不可变变量 |
| 一致的盐值 | 记录并版本化你的 CREATE2 盐值 |
| 部署清单 | 在版本控制中追踪所有部署 |
| 立即验证 | 部署时使用 --verify 标志 |
| 先在测试网测试 | 主网部署前先部署到 Sepolia/Goerli |
这篇内容有帮助吗?在 GitHub 上提出修改建议