跳转到内容

Foundry 智能合约开发教程

Foundry 智能合约开发教程

Foundry 是用 Rust 编写的高性能以太坊开发工具链,以极快的编译速度和强大的测试功能著称。与 Hardhat 相比,Foundry 用 Solidity 编写测试,无需 JavaScript,深受智能合约开发者喜爱。

Foundry 简介

Foundry 包含以下核心工具:

工具说明
Forge智能合约编译、测试和部署工具
Cast与链交互的命令行工具
Anvil本地以太坊节点(类似 Hardhat Node)
ChiselSolidity REPL 交互环境

安装

Terminal window
# 安装 Foundryup(Foundry 安装管理器)
curl -L https://foundry.paradigm.xyz | bash
# 安装最新版本的 Foundry
foundryup
# 验证安装
forge --version
cast --version
anvil --version

创建项目

Terminal window
# 创建新项目
forge init my-foundry-project
cd my-foundry-project
# 项目结构
ls -la
my-foundry-project/
├── src/ # 智能合约源文件
│ └── Counter.sol
├── test/ # 测试文件(用 Solidity 编写)
│ └── Counter.t.sol
├── script/ # 部署脚本
│ └── Counter.s.sol
├── lib/ # 依赖库(git submodules)
│ └── forge-std/
└── foundry.toml # 配置文件

配置文件

foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.24"
# 优化设置
optimizer = true
optimizer_runs = 200
# 测试设置
verbosity = 2
ffi = false
# Gas 报告
gas_reports = ["*"]
[profile.default.rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"
[profile.default.etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
sepolia = { key = "${ETHERSCAN_API_KEY}", url = "https://api-sepolia.etherscan.io/api" }

编写合约

src/Token.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Token is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000 ether;
error ExceedsMaxSupply(uint256 requested, uint256 available);
event TokensMinted(address indexed to, uint256 amount);
constructor(string memory name, string memory symbol, address initialOwner)
ERC20(name, symbol)
Ownable(initialOwner)
{
_mint(initialOwner, 100_000 ether);
}
function mint(address to, uint256 amount) external onlyOwner {
uint256 remaining = MAX_SUPPLY - totalSupply();
if (amount > remaining) {
revert ExceedsMaxSupply(amount, remaining);
}
_mint(to, amount);
emit TokensMinted(to, amount);
}
}

安装依赖

Terminal window
# 安装 OpenZeppelin
forge install OpenZeppelin/openzeppelin-contracts
# 或通过 npm 包映射
# 在 foundry.toml 中添加:
# remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]

编译合约

Terminal window
forge build
# 输出编译结果
[⠒] Compiling...
[⠢] Compiling 15 files with 0.8.24
[⠆] Solc 0.8.24 finished in 2.34s
Compiler run successful!

编写测试(Forge Test)

Foundry 的独特之处在于测试完全用 Solidity 编写:

test/Token.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenTest is Test {
Token public token;
address public owner;
address public alice;
address public bob;
// 每个测试前执行
function setUp() public {
owner = makeAddr("owner");
alice = makeAddr("alice");
bob = makeAddr("bob");
vm.prank(owner); // 模拟 owner 发起下一个调用
token = new Token("My Token", "MTK", owner);
}
// ==================== 基础功能测试 ====================
function test_InitialState() public view {
assertEq(token.name(), "My Token");
assertEq(token.symbol(), "MTK");
assertEq(token.owner(), owner);
assertEq(token.totalSupply(), 100_000 ether);
assertEq(token.balanceOf(owner), 100_000 ether);
}
function test_Transfer() public {
uint256 amount = 1000 ether;
vm.prank(owner);
token.transfer(alice, amount);
assertEq(token.balanceOf(alice), amount);
assertEq(token.balanceOf(owner), 100_000 ether - amount);
}
// ==================== 铸造测试 ====================
function test_Mint() public {
uint256 amount = 5000 ether;
vm.prank(owner);
vm.expectEmit(true, false, false, true);
emit Token.TokensMinted(alice, amount);
token.mint(alice, amount);
assertEq(token.balanceOf(alice), amount);
assertEq(token.totalSupply(), 100_000 ether + amount);
}
function test_RevertWhen_NonOwnerMints() public {
vm.prank(alice);
vm.expectRevert(); // 期望 revert
token.mint(bob, 1000 ether);
}
function test_RevertWhen_MintExceedsMaxSupply() public {
uint256 overAmount = token.MAX_SUPPLY() - token.totalSupply() + 1;
vm.prank(owner);
vm.expectRevert(
abi.encodeWithSelector(
Token.ExceedsMaxSupply.selector,
overAmount,
token.MAX_SUPPLY() - token.totalSupply()
)
);
token.mint(alice, overAmount);
}
// ==================== 模糊测试(Fuzz Testing) ====================
function testFuzz_Transfer(uint256 amount) public {
// bound 限制随机值的范围
amount = bound(amount, 0, token.balanceOf(owner));
vm.prank(owner);
token.transfer(alice, amount);
assertEq(token.balanceOf(alice), amount);
}
function testFuzz_MintAndTransfer(address to, uint256 mintAmount) public {
vm.assume(to != address(0));
vm.assume(to != owner);
mintAmount = bound(mintAmount, 1, token.MAX_SUPPLY() - token.totalSupply());
vm.prank(owner);
token.mint(to, mintAmount);
assertGe(token.balanceOf(to), mintAmount);
}
// ==================== 不变量测试(Invariant Testing) ====================
// 在 test/invariant/ 目录中编写不变量测试
// ==================== Gas 测试 ====================
function test_GasTransfer() public {
vm.prank(owner);
uint256 gasBefore = gasleft();
token.transfer(alice, 1000 ether);
uint256 gasAfter = gasleft();
console.log("Transfer Gas:", gasBefore - gasAfter);
}
}

运行测试

Terminal window
# 运行所有测试
forge test
# 详细输出
forge test -vvv
# 运行特定测试
forge test --match-test test_Mint
# 运行特定合约的测试
forge test --match-contract TokenTest
# 运行模糊测试(增加运行次数)
forge test --fuzz-runs 10000
# Gas 快照
forge snapshot
# 与上次快照对比
forge snapshot --diff

输出示例:

Running 8 tests for test/Token.t.sol:TokenTest
[PASS] testFuzz_MintAndTransfer(address,uint256) (runs: 256, μ: 95432, ~: 95432)
[PASS] testFuzz_Transfer(uint256) (runs: 256, μ: 45231, ~: 45231)
[PASS] test_GasTransfer() (gas: 48234)
[PASS] test_InitialState() (gas: 12345)
[PASS] test_Mint() (gas: 67890)
[PASS] test_RevertWhen_MintExceedsMaxSupply() (gas: 23456)
[PASS] test_RevertWhen_NonOwnerMints() (gas: 18901)
[PASS] test_Transfer() (gas: 48234)
Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.34s

部署脚本

script/DeployToken.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Script.sol";
import "../src/Token.sol";
contract DeployToken is Script {
function run() external returns (Token token) {
// 从环境变量获取部署者私钥
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
console.log("部署者地址:", deployer);
console.log("部署者余额:", deployer.balance);
// 开始广播交易
vm.startBroadcast(deployerPrivateKey);
token = new Token("My Token", "MTK", deployer);
vm.stopBroadcast();
console.log("Token 合约地址:", address(token));
}
}

执行部署

Terminal window
# 部署到本地 Anvil
forge script script/DeployToken.s.sol --rpc-url http://localhost:8545 --broadcast
# 部署到 Sepolia 测试网
forge script script/DeployToken.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast \
--verify \
-vvvv
# 模拟部署(不广播)
forge script script/DeployToken.s.sol --rpc-url $SEPOLIA_RPC_URL

Cast 命令行工具

Cast 是与区块链交互的强大命令行工具:

Terminal window
# 查询余额
cast balance 0xAddress --rpc-url $MAINNET_RPC_URL
# 发送交易
cast send 0xContractAddress "transfer(address,uint256)" 0xTo 1000ether \
--private-key $PRIVATE_KEY \
--rpc-url $SEPOLIA_RPC_URL
# 调用只读函数
cast call 0xTokenAddress "balanceOf(address)(uint256)" 0xAddress \
--rpc-url $MAINNET_RPC_URL
# 解码 ABI
cast abi-decode "transfer(address,uint256)(bool)" 0x0000...0001
# 计算函数选择器
cast sig "transfer(address,uint256)"
# 输出: 0xa9059cbb
# 转换单位
cast to-wei 1 ether
cast from-wei 1000000000000000000
# 查询区块信息
cast block latest --rpc-url $MAINNET_RPC_URL
# 获取交易信息
cast tx 0xTxHash --rpc-url $MAINNET_RPC_URL

Anvil 本地节点

Terminal window
# 启动本地节点
anvil
# 分叉主网
anvil --fork-url $MAINNET_RPC_URL
# 指定区块分叉
anvil --fork-url $MAINNET_RPC_URL --fork-block-number 19000000
# 自定义账户数量和余额
anvil --accounts 10 --balance 1000
# 设置区块时间
anvil --block-time 12

合约验证

Terminal window
# 验证已部署的合约
forge verify-contract \
0xDeployedAddress \
src/Token.sol:Token \
--constructor-args $(cast abi-encode "constructor(string,string,address)" "My Token" "MTK" "0xOwner") \
--chain sepolia \
--etherscan-api-key $ETHERSCAN_API_KEY

Chisel(Solidity REPL)

Terminal window
# 启动 Chisel
chisel
# 在 REPL 中测试 Solidity 代码
> uint256 x = 100
> x * 2
200
> address(0x1234)
0x0000000000000000000000000000000000001234

Foundry vs Hardhat 对比

特性FoundryHardhat
编写语言Solidity(测试)+ RustJavaScript/TypeScript
编译速度极快较慢
测试速度极快较慢
模糊测试内置支持需要插件
插件生态较少丰富
学习曲线需要熟悉 SolidityJS 开发者友好
调试体验强大强大

总结

Foundry 是现代以太坊开发的优选工具:

  • Forge:编译、测试、部署一站式解决方案
  • Cast:命令行链上交互,无需编写脚本
  • Anvil:高性能本地节点,支持主网分叉
  • 模糊测试:内置属性测试,自动发现边界问题
  • 速度:Rust 编写,比 Hardhat 快 10-100 倍

推荐从 Foundry 开始学习智能合约开发,配合 forge-std 标准库可以高效编写安全的合约和测试。