导语:从 Remix 到生产级开发
Remix IDE 是学习 Solidity 的好地方,但生产级开发需要更强大的工具链:自动化测试、本地 EVM 网络、主网分叉、Gas 优化分析、部署流水线。
当前最主流的两大工具链:
- Hardhat(前身 Buidler):JavaScript/TypeScript 生态,插件丰富、社区庞大
- Foundry:Rust 编写,原生测试用 Solidity 写、执行速度极快
一句话总结:Hardhat 适合 JS 全栈团队快速搭建,Foundry 适合合约开发者追求极致性能和原生测试体验。
1. Hardhat 环境搭建
1.1 项目初始化
# 创建项目
mkdir defi-project && cd defi-project
npm init -y
npm install --save-dev hardhat
# 初始化 Hardhat
npx hardhat init
# → 选择 Create a TypeScript project
# → 自动安装:@nomicfoundation/hardhat-toolbox
# 目录结构
contracts/ # Solidity 合约
scripts/ # 部署脚本
test/ # 测试用例
hardhat.config.ts # 配置文件
1.2 hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";
dotenv.config();
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
forking: {
url: process.env.ALCHEMY_MAINNET_URL || "",
blockNumber: 18000000, // 固定区块号,保证测试确定性
},
},
goerli: {
url: process.env.ALCHEMY_GOERLI_URL,
accounts: [process.env.PRIVATE_KEY!],
},
mainnet: {
url: process.env.ALCHEMY_MAINNET_URL,
accounts: [process.env.PRIVATE_KEY!],
},
},
gasReporter: {
enabled: true,
currency: "USD",
coinmarketcap: process.env.CMC_API_KEY,
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
export default config;
1.3 编写与运行测试
// test/Token.test.ts
import { expect } from "chai";
import { ethers } from "hardhat";
import { MyToken } from "../typechain-types";
describe("MyToken", function () {
let token: MyToken;
let owner: any, addr1: any, addr2: any;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy("TestToken", "TTK", 1000000);
await token.waitForDeployment();
});
it("should assign total supply to owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
it("should transfer tokens between accounts", async function () {
await token.transfer(addr1.address, 100);
expect(await token.balanceOf(addr1.address)).to.equal(100);
await token.connect(addr1).transfer(addr2.address, 50);
expect(await token.balanceOf(addr2.address)).to.equal(50);
});
it("should fail if sender has insufficient balance", async function () {
await expect(
token.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("ERC20: insufficient balance");
});
});
# 运行测试(自动编译合约)
npx hardhat test
# 运行指定测试文件
npx hardhat test test/Token.test.ts
# 生成 Gas 报告
REPORT_GAS=true npx hardhat test
# 覆盖范围报告
npx hardhat coverage
1.4 部署脚本
// scripts/deploy.ts
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying with account:", deployer.address);
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy("MyToken", "MTK", 1000000);
await token.waitForDeployment();
console.log("Token deployed to:", await token.getAddress());
// 验证合约(可选)
await run("verify:verify", {
address: await token.getAddress(),
constructorArguments: ["MyToken", "MTK", 1000000],
});
}
main().catch(console.error);
# 部署到本地网络
npx hardhat run scripts/deploy.ts
# 部署到测试网
npx hardhat run scripts/deploy.ts --network goerli
一句话总结:Hardhat 通过 TypeScript 测试 + Ethers.js + 插件生态,提供了 Web3 开发最成熟的工具链。
2. Foundry 环境搭建
2.1 安装与项目初始化
# 安装 foundryup
curl -L https://foundry.paradigm.xyz | bash
foundryup
# 创建项目
forge init foundry-project
cd foundry-project
# 目录结构
src/ # 合约源码
test/ # Solidity 测试(!)
script/ # 部署脚本
lib/ # Git 子模块依赖
foundry.toml # 配置文件
2.2 foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.20"
optimizer = true
optimizer_runs = 200
# 主网分叉测试
[profile.default.fuzz]
runs = 256
[rpc_endpoints]
mainnet = "${ALCHEMY_MAINNET_URL}"
goerli = "${ALCHEMY_GOERLI_URL}"
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
2.3 Solidity 中写测试(Foundry 的独特优势)
// test/MyToken.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken token;
address owner = address(this);
address alice = makeAddr("alice");
address bob = makeAddr("bob");
function setUp() public {
token = new MyToken("TestToken", "TTK", 1_000_000);
// 给测试账户发代币
token.transfer(alice, 10_000);
token.transfer(bob, 10_000);
}
function test_InitialSupply() public {
assertEq(token.totalSupply(), 1_000_000 * 10**18);
assertEq(token.balanceOf(owner), 1_000_000 * 10**18 - 20_000);
}
function test_Transfer() public {
vm.prank(alice); // 模拟 alice 调用
token.transfer(bob, 1000);
assertEq(token.balanceOf(alice), 9000);
assertEq(token.balanceOf(bob), 11_000);
}
function test_RevertOnInsufficientBalance() public {
vm.prank(alice);
vm.expectRevert("ERC20: insufficient balance");
token.transfer(bob, 100_000); // alice 只有 10_000
}
function testFuzz_Transfer(uint256 amount) public {
amount = bound(amount, 0, token.balanceOf(owner));
token.transfer(alice, amount);
assertEq(token.balanceOf(alice), 10_000 + amount);
}
// 使用主网分叉测试
function testFork_RealTokenBalance() public {
vm.createSelectFork("mainnet", 18000000);
address usdcHolder = 0x...; // 真实主网地址
address usdc = 0xA0b86a33E6449...; // USDC 合约地址
uint256 balance = IERC20(usdc).balanceOf(usdcHolder);
assertGt(balance, 0);
}
}
# 运行测试
forge test
# 详细输出
forge test -vvv
# 运行指定测试
forge test --match-test test_Transfer
# Gas 快照(生成 Gas 使用基线)
forge snapshot
# 生成 Gas 差异报告
forge snapshot --diff
# 格式化代码
forge fmt
# 静态分析
forge build
2.4 部署脚本
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/MyToken.sol";
contract DeployScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
MyToken token = new MyToken("MyToken", "MTK", 1_000_000);
vm.stopBroadcast();
}
}
# 模拟部署(dry run)
forge script script/Deploy.s.sol --rpc-url goerli --broadcast --verify -vvvv
# 实际部署
forge script script/Deploy.s.sol --rpc-url mainnet --broadcast --verify
一句话总结:Foundry 最大的创新是用 Solidity 写测试——测试者站在合约内部视角,可以使用作弊码(cheatcodes)操纵时间、区块号、余额,测试效率极高。
3. Hardhat vs Foundry 对比
| 维度 | Hardhat | Foundry |
|---|---|---|
| 语言 | TypeScript/JavaScript | Rust(工具)+ Solidity(测试) |
| 测试写法 | JS/TS(外部调用合约) | Solidity(从合约内部测试) |
| 执行速度 | 中等(JS VM) | 极快(Rust EVM) |
| 分叉测试 | hardhat_impersonateAccount | vm.createSelectFork |
| Gas 报告 | 内置插件 | forge snapshot |
| 调试 | console.log | forge test -vvv |
| 类型生成 | typechain(自动生成) | 手动或 cast |
| 社区规模 | 更大 | 快速增长 |
| 学习曲线 | 低(JS 开发者友好) | 中(需学习 Solidity 测试模式) |
| 最佳场景 | 全栈 DApp 开发、团队以 JS 为主 | 合约审计、复杂协议开发、速度优先 |
一句话总结:Hardhat 生态成熟插件众多,Foundry 原生测试极致高效——许多团队采用"Hardhat 做前端集成 + Foundry 做合约测试"的混合方案。
4. CI/CD 集成
GitHub Actions + Foundry
# .github/workflows/test.yml
name: test
on: [push, pull_request]
jobs:
check:
name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests
run: forge test -vvv
- name: Run snapshot
run: forge snapshot --check
# 如果 Gas 使用量超出基线会失败
GitHub Actions + Hardhat
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx hardhat test
- run: npx hardhat coverage
一句话总结:将合约测试集成到 CI 流水线是安全开发的基本要求——任何人提交代码时自动运行全套测试,防止回归问题。
5. 调试技巧与常见问题
开发过程中经常遇到以下问题,掌握排查方法能大幅提升效率:
| 问题现象 | 根因 | 解决 |
|---|---|---|
out of gas | 合约函数消耗超过区块 gas 上限 | 优化循环、减少 storage 写入、分批处理 |
revert 无原因 | Solidity 0.8+ 的 require 未提供消息字符串 | 使用 revert CustomError() 或带消息的 require |
| 本地测试通过,部署失败 | 不同网络的 gas limit、EIP 支持度不同 | 使用相同 Solidity 版本,在测试网上复测 |
| Metamask 估算 gas 偏低 | 复杂逻辑(如循环依赖 calldata 大小) | 手动调高 gas limit |
| 事件未触发 | topics 不匹配或地址错误 | 用 Etherscan 直接查看原始日志 |
Hardhat 调试利器:
# 打印合约内部 console.log(需安装 hardhat/console.sol)
npx hardhat test --logs
# 查看具体交易的 trace
npx hardhat node
# 然后在另一个终端
npx hardhat run scripts/debug.ts --network localhost
Foundry 调试利器:
# 详细的执行 trace
forge test -vvv --match-test testName
# 生成测试覆盖热力图
forge coverage --report lcov
# 用 VS Code 的 Coverage Gutters 插件可视化
# 分析合约存储布局
forge inspect ContractName storage-layout
一句话总结:调试智能合约需要熟悉 trace、日志和存储分析工具,Hardhat 的 console.log 和 Foundry 的
-vvv是最常用的两把利器。
5. 总结
| 工具 | 核心优势 | 推荐场景 |
|---|---|---|
| Hardhat | TS 生态、丰富插件、大社区 | DApp 全栈开发、团队主力 JS |
| Foundry | 原生 Solidity 测试、极快 | 协议开发、审计、Gas 敏感项目 |
| Ethers.js | 最成熟的前端库 | DApp 前端与合约交互 |
| viem | 轻量类型安全版 ethers | 新项目、TypeScript 优先 |
| Wagmi/RainbowKit | React hooks + 钱包连接 | 前端集成 |
延伸阅读:
- Solidity 智能合约开发入门 — 语言基础
- 智能合约安全审计与常见漏洞 — 安全测试的最佳实践
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。