mcpField
S
solidity-deploy

solidity-deploy

github.com/0xlayerghost云与 DevOps4129 次安装2 周前更新
代码仓库

关于 solidity-deploy

solidity-deploy 是面向使用 Foundry 部署和升级 Solidity 合约开发者的工作流技能,提供部署前后检查、签名与广播安全规则,并指导使用 OpenZeppelin Foundry Upgrades Plugin 处理可升级合约。

主要功能

  1. 01Foundry 部署前检查清单
  2. 02模拟运行与部署后步骤
  3. 03签名和广播安全规则
  4. 04OpenZeppelin 代理部署指引
  5. 05升级验证与存储布局检查

适用场景

  1. 01准备 Foundry 合约部署
  2. 02广播前模拟部署流程
  3. 03部署或升级可升级代理
  4. 04检查部署配置和链上状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/0xlayerghost/solidity-agent-kit --skill solidity-deploy

安装后在 Claude Code 中输入/solidity-deploy确认已加载。

详细介绍

部署工作流

语言规则

  • 始终使用用户所用的语言回复。 如果用户用中文提问,就用中文回复;如果用英文提问,就用英文回复。

部署前检查清单(必须全部通过)

步骤命令 / 操作
格式化代码forge fmt
运行所有测试forge test — 必须零失败
检查 Gas 报告forge test --gas-report — 检查关键函数
验证配置手动检查 config/*.json 参数
试运行forge script <Script> --fork-url <RPC_URL> -vvvv(不加 --broadcast)
检查余额cast balance <DEPLOYER> --rpc-url <RPC_URL> — Gas 是否充足?
设置 Gas 限制部署命令必须包含 --gas-limit

部署决策规则

情况规则
默认部署不加 --verify — 默认不在区块浏览器上验证合约
用户要求验证在命令中添加 --verify 和 --etherscan-api-key
部署后验证将 forge verify-contract 作为单独步骤使用
多链部署每条链使用独立脚本,绝不要在一个脚本中批量部署到多条链
代理部署先部署实现合约,再部署代理 — 分别验证两者
可升级合约使用 OpenZeppelin Upgrades Plugin(见下文)— 绝不要手动编写代理部署脚本

部署后操作(必须全部完成)

  1. 更新 config/*.json 和 deployments/latest.env 中的地址
  2. 测试关键函数:使用 cast call 验证链上状态是否正确
  3. 在 docs/CHANGELOG.md 中记录变更
  4. 提交 PR,并附上部署交易哈希链接
  5. 如需验证,单独运行 forge verify-contract

关键安全规则

  • 广播前,让开发者选择签名方式:
    • Foundry Keystore(推荐): 使用 --account <KEYSTORE_NAME>。
    • 环境文件: 将 PRIVATE_KEY 保存在已加入 gitignore 的 .env 文件中,运行 source .env,然后使用 --private-key "$PRIVATE_KEY"。
  • 绝不要在命令、脚本、日志、对话或已提交文件中写入明文私钥。 绝不要读取或打印开发者的 .env。
  • 模板中绝不要包含 --broadcast。 用户准备好部署时必须显式添加该参数。

命令模板

# Dry-run (simulation only, no on-chain execution)
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url <RPC_URL> \
  --gas-limit 5000000 \
  -vvvv

# When the developer is ready to deploy, ask them to choose one signer:
#   Keystore (recommended): add --account <KEYSTORE_NAME> --broadcast
#   .env: source .env, then add --private-key "$PRIVATE_KEY" --broadcast

# Verify existing contract separately
forge verify-contract <ADDRESS> <CONTRACT> \
  --chain-id <CHAIN_ID> \
  --etherscan-api-key <API_KEY> \
  --constructor-args $(cast abi-encode "constructor(address)" <ARG>)

# Quick on-chain read test after deployment
cast call <CONTRACT_ADDRESS> "functionName()" --rpc-url <RPC_URL>

可升级合约部署(OpenZeppelin Upgrades Plugin)

对于任何可升级合约(UUPS、Transparent、Beacon),请使用 OpenZeppelin Foundry Upgrades Plugin,而不要手动编写代理部署脚本。

为什么使用此插件
手动方式使用插件
约 30 行:部署实现合约 → 部署代理 → 编码初始化函数 → 完成连接1 行:Upgrades.deployUUPSProxy(...)
约 20 行:部署新的实现合约 → 验证存储 → 升级代理1 行:Upgrades.upgradeProxy(...)
存储布局兼容性:人工检查自动检查,不兼容的布局会被拒绝
忘记了 _disableInitializers()?不会有警告自动验证
安装
forge install OpenZeppelin/openzeppelin-foundry-upgrades
forge install OpenZeppelin/openzeppelin-contracts-upgradeable

添加到 remappings.txt:

@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
部署脚本模板(UUPS)
// script/Deploy.s.sol
import {Script, console} from "forge-std/Script.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {MyContract} from "../src/MyContract.sol";

contract DeployScript is Script {
    function run() public {
        vm.startBroadcast();

        // One line: deploys impl + proxy + calls initialize
        address proxy = Upgrades.deployUUPSProxy(
            "MyContract.sol",
            abi.encodeCall(MyContract.initialize, (msg.sender))
        );

        console.log("Proxy:", proxy);
        console.log("Impl:", Upgrades.getImplementationAddress(proxy));

        vm.stopBroadcast();
    }
}
升级脚本模板
// script/Upgrade.s.sol
import {Script, console} from "forge-std/Script.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";

contract UpgradeScript is Script {
    function run() public {
        address proxy = vm.envAddress("PROXY_ADDRESS");
        vm.startBroadcast();

        // One line: validates storage layout + deploys new impl + upgrades proxy
        Upgrades.upgradeProxy(proxy, "MyContractV2.sol", "");

        console.log("Upgraded. New impl:", Upgrades.getImplementationAddress(proxy));

        vm.stopBroadcast();
    }
}

在 V2 合约中添加 @custom:oz-upgrades-from MyContract 注解,以自动引用原合约:

/// @custom:oz-upgrades-from MyContract
contract MyContractV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
    // ...
}
命令
# Deploy proxy (dry-run) — --ffi is required for storage layout checks
forge script script/Deploy.s.sol --rpc-url <RPC_URL> --ffi -vvvv

# Deploy proxy (broadcast) with Foundry Keystore
forge script script/Deploy.s.sol --rpc-url <RPC_URL> --ffi --account <KEYSTORE_NAME> --broadcast

# Or deploy with PRIVATE_KEY from a gitignored .env
source .env
forge script script/Deploy.s.sol --rpc-url <RPC_URL> --ffi --private-key "$PRIVATE_KEY" --broadcast

# Upgrade proxy (dry-run)
PROXY_ADDRESS=0x... forge script script/Upgrade.s.sol --rpc-url <RPC_URL> --ffi -vvvv

# Upgrade proxy (broadcast) with Foundry Keystore
PROXY_ADDRESS=0x... forge script script/Upgrade.s.sol --rpc-url <RPC_URL> --ffi --account <KEYSTORE_NAME> --broadcast

# Or upgrade with PRIVATE_KEY from a gitignored .env
source .env
PROXY_ADDRESS=0x... forge script script/Upgrade.s.sol --rpc-url <RPC_URL> --ffi --private-key "$PRIVATE_KEY" --broadcast

# Validate upgrade without deploying (useful for CI)
# Use Upgrades.validateUpgrade("MyContractV2.sol", opts) in a test
插件 API 快速参考
函数用途
Upgrades.deployUUPSProxy(contract, data)部署 UUPS 代理和实现合约,并执行初始化
Upgrades.deployTransparentProxy(contract, admin, data)部署 Transparent 代理和实现合约,并执行初始化
Upgrades.upgradeProxy(proxy, newContract, data)验证、部署新的实现合约并升级
Upgrades.validateUpgrade(contract, opts)仅验证,不部署(用于 CI / 测试)
Upgrades.getImplementationAddress(proxy)获取当前实现合约地址
Upgrades.prepareUpgrade(contract, opts)验证并部署新的实现合约,返回地址(用于多签)
关键规则
  • 始终使用 --ffi 标志 — 插件需要它来验证存储布局
  • 升级时始终添加 --sender <ADDRESS> — 必须与代理所有者一致,否则会出现 OwnableUnauthorizedAccount
  • 在脚本中使用 Upgrades,仅在测试中使用 UnsafeUpgrades — UnsafeUpgrades 会跳过所有安全检查
  • 升级时将 V1 源代码保留在项目中 — 插件需要它来比较存储布局。或者使用 @custom:oz-upgrades-from 注解
  • 此插件可用时,绝不要手动编写代理部署代码 — 仅存储布局检查就能防止严重错误

评论

支持 Markdown