手动部署合约已经不可持续。现代开发使用确定性部署脚本,相同的代码在不同链上产生相同地址,配合多签或硬件钱包,构建可信的部署流水线。
13.4.1 部署脚本的架构
graph LR
subgraph Script[部署脚本]
S1[编译字节码]
S2[计算 salt]
S3[CREATE2 部署]
S4[记录地址]
S5[验证源码]
end
subgraph Chains[目标链]
C1[Ethereum Mainnet]
C2[Sepolia Testnet]
C3[Arbitrum One]
C4[Polygon PoS]
end
S1 --> S2 --> S3 --> S4 --> S5
S5 --> C1
S5 --> C2
S5 --> C3
S5 --> C4
style Script fill:#e3f2fd
CREATE2:确定性地址部署
标准 CREATE 地址 = keccak256(发送者, nonce) —— nonce 变化导致地址变化。CREATE2 使用 salt:
\text{address} = \text{keccak256}(0xFF, \text{deployer}, \text{salt}, \text{keccak256(initCode))[12:]
相同的 initCode + salt → 相同的地址,任何 EVM 链上都一样。
typescript
/**
* CREATE2 地址计算(纯 TypeScript,EVM 等效)
*/
function computeCreate2Address(
deployer: string,
salt: string,
initCodeHash: string,
): string {
// 地址 = keccak256(0xFF + deployer(20B) + salt(32B) + initCodeHash(32B))[12:]
const prefix = "0xFF";
const deployerPadded = deployer.toLowerCase().replace("0x", "").padStart(40, '0');
const saltPadded = salt.toLowerCase().replace("0x", "").padStart(64, '0');
const hashPadded = initCodeHash.toLowerCase().replace("0x", "").padStart(64, '0');
const data = prefix + deployerPadded + saltPadded + hashPadded;
// 简化的 hash 计算(实际应用 keccak256)
let hash = 0;
for (let i = 0; i < data.length; i++) {
hash = ((hash << 5) - hash + data.charCodeAt(i)) | 0;
}
// 取低 20 字节
const addrHex = (hash >>> 0).toString(16).padStart(40, '0');
return "0x" + addrHex;
}
// 示例:在以太坊和 Arbitrum 上部署到同一地址
const factory = "0x4e59b44847b379578588920cA78FbF26c0B4956C"; // 标准 CREATE2 工厂
const salt = "0x" + "42".repeat(32);
const initCodeHash = "0x" + "ab".repeat(32); // 实际为 keccak256(creationBytecode + constructorArgs)
const addrEth = computeCreate2Address(factory, salt, initCodeHash);
const addrArb = computeCreate2Address(factory, salt, initCodeHash);
console.log("Same address on both chains:", addrEth === addrArb, addrEth);
// 输出: true, 0x...(相同的地址!)13.4.2 多链网络配置
typescript
interface NetworkConfig {
rpcUrl: string;
chainId: number;
verificationApi: string;
deployed: { [contract: string]: string };
// 安全:多签 / 硬件钱包
deployerType: "EOA" | "GnosisSafe" | "HardwardWallet";
}
const networks: Record<string, NetworkConfig> = {
mainnet: {
rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/...",
chainId: 1,
verificationApi: "https://api.etherscan.io/api",
deployed: {},
deployerType: "GnosisSafe",
},
sepolia: {
rpcUrl: "https://eth-sepolia.g.alchemy.com/v2/...",
chainId: 11155111,
verificationApi: "https://api-sepolia.etherscan.io/api",
deployed: {},
deployerType: "EOA", // 测试网用 EOA 方便
},
arbitrum: {
rpcUrl: "https://arb-mainnet.g.alchemy.com/v2/...",
chainId: 42161,
verificationApi: "https://api.arbiscan.io/api",
deployed: {},
deployerType: "GnosisSafe",
},
polygon: {
rpcUrl: "https://polygon-mainnet.g.alchemy.com/v2/...",
chainId: 137,
verificationApi: "https://api.polygonscan.com/api",
deployed: {},
deployerType: "GnosisSafe",
},
};
// 部署任务选择网络
console.log("Available networks:", Object.keys(networks).join(", "));13.4.3 硬件钱包与多签部署
生产部署绝不使用本地私钥:
| 场景 | 工具 | 流程 |
|---|---|---|
| 单签硬件 | Ledger / Trezor | 脚本广播 → 硬件签回 → 广播 |
| 多签 | Gnosis Safe | Propose → Signers 确认 → Execute |
| 自动化 | Defender / OpenZeppelin | 预设策略,自动执行 |
sequenceDiagram
participant Dev as 开发者
participant Script as 部署脚本
participant Safe as Gnosis Safe (3/5)
participant RPC as 节点 RPC
participant Chain as 区块链
Dev ->> Script: npx hardhat deploy --network mainnet
Script ->> Safe: 创建 Proposal tx
Safe ->> Signer1: 请求签名
Safe ->> Signer2: 请求签名
Safe ->> Signer3: 请求签名
Signer1 -->> Safe: 签名 1/3
Signer2 -->> Safe: 签名 2/3
Signer3 -->> Safe: 签名 3/3 → 达到阈值
Safe ->> RPC: broadcast execute
RPC ->> Chain: 合约已部署
Chain -->> Safe: receipt
> ← 13.3 测试策略 | 前往 → 13.5 合约验证 |*
评论
0评论加载中…