智能合约开发框架已经从"可选工具"变成"基础设施"。2024 年的生态中,Foundry 是协议工程师的首选,Hardhat 是 DApp 全栈开发的标准,Truffle 已退出历史舞台。
13.1.1 三大框架的历史与现状
| 框架 | 语言 | 测试 | 性能 | 定位 | 2024 状态 |
|---|---|---|---|---|---|
| Truffle | Node.js | Mocha + Chai | 慢 | 教学/遗产 | 已弃用 |
| Hardhat | Node.js/TS | Ethers.js + Chai | 中 | DApp 全栈 | 主流 |
| Foundry | Rust | 内建 fuzzing | 极快 | 协议/审计 | 增长最快 |
核心差异的架构根源
graph LR
subgraph Truffle[Truffle Architecture]
TC[Truffle Config] --> TB[Build Pipeline]
TB --> TM[Migrations JS]
TM --> TE[Test: Mocha/Chai]
TE --> TG[Gas Reporter]
end
subgraph Hardhat[Hardhat Architecture]
HH[Hardhat Runtime] --> HP[Plugin System]
HP --> HE[Ethers.js Provider]
HP --> HT[Hardhat Network]
HT --> HN[npx hardhat test]
end
subgraph Foundry[Foundry Architecture]
F[Forge: Rust Binary] --> FC[Compile]
FC --> FT[Test in Solidity]
FT --> FF[Fuzzing Engine]
F --> FCa[Cast CLI]
FCa --> FA[Anvil Local Node]
FA --> FCH[Chisel REPL]
end
style Truffle fill:#ffebee
style Hardhat fill:#fff3e0
style Foundry fill:#e8f5e9
13.1.2 Hardhat:灵活与生态
核心组件:
- Hardhat Runtime Environment (HRE):脚本和任务的全局对象
- Hardhat Network:内置本地 EVM,支持快照、时间操纵、impersonate
- 插件:ethers, waffle, verify, deploy, gas-reporter 等无缝集成
typescript
/**
* Hardhat 风格的部署与测试脚本(TypeScript)
*/
interface HardhatRuntime {
network: { name: string; config: object };
ethers: {
getSigners(): Promise<{ address: string; provider: any }[]>;
getContractFactory(name: string): Promise<any>;
};
run(task: string, args: object): Promise<any>;
}
// 模拟 hardhat run 的部署任务
async function deployToken(hre: HardhatRuntime) {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying with:", deployer.address);
const Token = await hre.ethers.getContractFactory("ERC20Token");
const token = await Token.deploy("MockToken", "MTK", 1000000n);
// 在 Hardhat 网络中,部署即挖到区块
console.log("Token deployed to:", token.address);
return token;
}
// 测试结构:beforeEach + fixture 模式
class HardhatTestSuite {
private async fixture(): Promise<{ token: MockToken; owner: string; user: string }> {
const token = await deployToken(this.hre);
const [owner, user] = (await this.hre.ethers.getSigners()).map(s => s.address);
return { token, owner, user };
}
async testTransfer() {
const { token, owner, user } = await this.fixture();
const tx = await token.transfer(user, 100n);
const receipt = await tx.wait();
const balance = await token.balanceOf(user);
console.assert(balance === 100n, "Transfer failed");
console.log("Gas used:", receipt.gasUsed);
}
}
// 模拟 HRE
const hre: HardhatRuntime = {
network: { name: "hardhat", config: {} },
ethers: {
async getSigners() {
return [
{ address: "0xDeployer...", provider: null },
{ address: "0xUser1...", provider: null },
{ address: "0xUser2...", provider: null },
];
},
async getContractFactory(name: string) {
return {
async deploy(...args: any[]) {
return { address: "0xToken...", async transfer(to: string, amt: bigint) {
return { wait: async () => ({ gasUsed: 52000n }) };
}, async balanceOf(addr: string) { return 100n; }};
}
};
}
},
async run(task, args) { return; }
};
new HardhatTestSuite(hre).testTransfer().then(() => console.log("Hardhat test passed"));13.1.3 Foundry:Rust 驱动的极速工具链
四大工具:
- Forge:编译 + 测试 + Gas 快照。Solidity 中直接写
invariant和fuzz测试。 - Cast:CLI 与链交互的工具,类似
curl对于 HTTP。 - Anvil:本地 EVM 节点,支持 fork 主网。
- Chisel:Solidity 交互式 REPL(即时尝试)。
Foundry 测试的革命性:在 Solidity 中测试 Solidity
solidity
// Foundry 测试示例(Solidity 语法)
contract FoundryTest is Test {
MockToken token;
function setUp() public {
token = new MockToken(1000000e18);
}
// 模糊测试:Foundry 自动生成 256 个随机值
function testFuzzTransfer(address to, uint256 amount) public {
vm.assume(to != address(0)); // 过滤无效输入
vm.assume(amount <= token.balanceOf(address(this)));
token.transfer(to, amount);
assertEq(token.balanceOf(to), amount);
}
// 不变式测试
function invariantTotalSupply() public {
assertEq(token.totalSupply(), 1000000e18);
}
}typescript
/**
* 模拟 Foundry 模糊测试引擎的核心逻辑
*/
interface FuzzCase {
inputs: (bigint | string | boolean)[];
failed: boolean;
error?: string;
}
class FuzzEngine {
private seed: number;
constructor(seed: number = 42) { this.seed = seed; }
private randomBigInt(max: bigint = 2n**256n - 1n): bigint {
this.seed = (this.seed * 16807 + 0) % 2147483647;
return BigInt(this.seed) % max;
}
private randomAddress(): string {
return "0x" + Array(40).fill(0).map(() =>
(this.randomBigInt(16n) as unknown as number).toString(16)
).join("");
}
fuzz<E>(
cases: number,
testFn: (inputs: (bigint | string)[]) => void,
invariants: { filter: (inputs: (bigint | string)[]) => boolean }[],
): FuzzCase[] {
const results: FuzzCase[] = [];
for (let i = 0; i < cases; i++) {
const inputs: (bigint | string)[] = [
this.randomAddress(),
this.randomBigInt(1000000n),
];
// 应用不变式过滤器
const valid = invariants.every(inv => inv.filter(inputs));
if (!valid) { i--; continue; }
try {
testFn(inputs);
results.push({ inputs, failed: false });
} catch (e) {
results.push({ inputs, failed: true, error: String(e) });
}
}
return results;
}
// 最小化反例(shrinking)
shrink(failure: FuzzCase, testFn: any, invariants: any): FuzzCase {
// 尝试减小输入值,寻找最小失败案例
let [addr, amount] = failure.inputs;
let shrunk = [addr, (amount as bigint) / 2n];
try {
testFn(shrunk);
return failure; // 无法缩小
} catch {
return { ...failure, inputs: shrunk };
}
}
}
// 模拟 fuzzing ERC20 transfer
const fuzzer = new FuzzEngine(42);
const results = fuzzer.fuzz(
100,
([to, amount]) => {
if (to === "0x" + "0".repeat(40)) throw new Error("Zero address not allowed");
if ((amount as bigint) > 1000000n) throw new Error("Exceeds balance");
// 正常逻辑
},
[{ filter: ([to, amount]) => (amount as bigint) <= 1000000n }],
);
const failures = results.filter(r => r.failed);
console.log(`Fuzzed 100 cases, ${failures.length} failures`);
if (failures.length > 0) {
console.log("First failure:", failures[0].error, "inputs:", failures[0].inputs);
}13.1.4 选择建议
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| DApp 全栈(前端 + 合约) | Hardhat | JS 生态无缝,插件丰富 |
| 协议开发 / 审计 | Foundry | 模糊测试、速度、Solidity-native |
| 快速 PoC / 教学 | Hardhat | 文档最完整,社区最大 |
| 遗留项目维护 | Truffle | ——— 已弃用,建议迁移 |
| 需要 CI 流水线 | 两者皆可 | Foundry 更快,Hardhat 更灵活 |
> ← 上一章 ch12 总结 | 前往 → 13.2 本地开发与主网分叉 |*
评论
0评论加载中…