关于 solidity-deploy
solidity-deploy 是面向使用 Foundry 部署和升级 Solidity 合约开发者的工作流技能,提供部署前后检查、签名与广播安全规则,并指导使用 OpenZeppelin Foundry Upgrades Plugin 处理可升级合约。
主要功能
- 01Foundry 部署前检查清单
- 02模拟运行与部署后步骤
- 03签名和广播安全规则
- 04OpenZeppelin 代理部署指引
- 05升级验证与存储布局检查
适用场景
- 01准备 Foundry 合约部署
- 02广播前模拟部署流程
- 03部署或升级可升级代理
- 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(见下文)— 绝不要手动编写代理部署脚本 |
部署后操作(必须全部完成)
- 更新
config/*.json和deployments/latest.env中的地址 - 测试关键函数:使用
cast call验证链上状态是否正确 - 在
docs/CHANGELOG.md中记录变更 - 提交 PR,并附上部署交易哈希链接
- 如需验证,单独运行
forge verify-contract
关键安全规则
- 广播前,让开发者选择签名方式:
- Foundry Keystore(推荐): 使用
--account <KEYSTORE_NAME>。 - 环境文件: 将
PRIVATE_KEY保存在已加入 gitignore 的.env文件中,运行source .env,然后使用--private-key "$PRIVATE_KEY"。
- Foundry Keystore(推荐): 使用
- 绝不要在命令、脚本、日志、对话或已提交文件中写入明文私钥。 绝不要读取或打印开发者的
.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