进阶 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 上提出修改建议

评论 (0)