Solidity入门(2):Foundry框架开发指南

Foundry简介与核心理念

Foundry 是一个用 Rust 编写的现代化、快速的以太坊开发工具包。它与Hardhat的主要区别在于:完全用Solidity编写测试,无需JavaScript/TypeScript,为Solidity开发者提供了原生开发体验。

核心优势

  1. 极速测试:Rust编写的测试运行器比JavaScript测试快10-100倍

  2. 内置Fuzzing测试:自动生成随机输入测试你的合约

  3. 强大的作弊码(Cheatcodes) :提供vm对象模拟各种区块链状态

  4. 命令行工具集成:Cast(合约交互)、Anvil(本地节点)、Forge(测试/构建)

安装Foundry

1. 系统要求

  • Rust环境(Foundry基于Rust,但安装时会自动处理)

  • Git(用于克隆模板和安装)

  • 命令行终端

2. 一键安装(推荐)

打开终端,执行以下命令:

bash 复制代码
# 使用foundryup一键安装(macOS/Linux)
curl -L https://foundry.paradigm.xyz | bash
# 然后重启终端,或运行:
source ~/.bashrc  # 或 source ~/.zshrc
# 最后运行安装
foundryup

# Windows用户使用PowerShell
powershell -c "irm https://foundry.paradigm.xyz | iex"

3. 验证安装

bash 复制代码
forge --version
cast --version
anvil --version

看到版本号输出即表示安装成功。

Foundry项目结构详解

让我们创建一个完整的Foundry项目并理解其结构:

bash 复制代码
# 1. 初始化新项目
forge init hello-foundry
cd hello-foundry

# 2. 查看生成的项目结构
tree -L 2

典型的Foundry项目结构如下:

bash 复制代码
hello-foundry/
├── src/                    # 智能合约源代码
│   └── Counter.sol        # 示例合约
├── test/                  # 测试文件(也用Solidity写!)
│   └── Counter.t.sol     # 对应测试合约
├── script/               # 部署脚本
│   └── Counter.s.sol    # 部署脚本示例
├── lib/                  # 依赖库(git submodules)
├── foundry.toml          # 项目配置文件
└── .gitmodules           # Git子模块配置

核心工具实战演示

1. Forge - 构建与测试工具

bash 复制代码
# 编译合约
forge build

# 运行所有测试
forge test

# 详细模式运行测试(查看Gas消耗、追踪)
forge test -vvv

# 运行特定测试合约
forge test --match-contract CounterTest

# 运行特定测试函数
forge test --match-test testIncrement

2. Anvil - 本地开发节点

bash 复制代码
# 启动本地测试节点(默认端口8545)
anvil
# 或自定义端口
anvil --port 8546

# 启动时预分配测试账户和ETH
anvil --mnemonic "test test test test test test test test test test test junk"

3. Cast - 合约交互工具

bash 复制代码
# 调用只读函数
cast call <合约地址> "functionName()"

# 发送交易
cast send <合约地址> "functionName(uint256)" 123 --private-key <私钥>

# 编码/解码数据
cast calldata "increment()"
cast --from-utf8 "Hello Foundry"

完整开发流程DEMO:计数器合约

步骤1:编写智能合约

创建 src/Counter.sol

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title 一个简单的计数器合约
/// @notice 演示Foundry开发流程
contract Counter {
    uint256 private count;
    address public owner;
    
    // 事件
    event Incremented(address indexed sender, uint256 newCount);
    event Reset(address indexed sender);
    
    // 错误定义
    error NotOwner();
    
    constructor(uint256 _initialCount) {
        count = _initialCount;
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }
    
    /// @notice 获取当前计数值
    function get() public view returns (uint256) {
        return count;
    }
    
    /// @notice 计数器加1
    function inc() public {
        count += 1;
        emit Incremented(msg.sender, count);
    }
    
    /// @notice 计数器加指定值
    function incBy(uint256 x) public {
        count += x;
        emit Incremented(msg.sender, count);
    }
    
    /// @notice 重置计数器(仅所有者)
    function reset() public onlyOwner {
        count = 0;
        emit Reset(msg.sender);
    }
}

步骤2:编写Solidity测试

创建 test/Counter.t.sol(Foundry测试约定使用.t.sol后缀):

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";  // 引入Foundry测试库
import "../src/Counter.sol";

contract CounterTest is Test {
    Counter counter;
    address owner = address(0x123);
    address user = address(0x456);
    
    // 在每个测试用例前运行
    function setUp() public {
        vm.prank(owner);  // 作弊码:模拟下一个调用者
        counter = new Counter(10);  // 初始值10
    }
    
    // 测试1:验证初始状态
    function testInitialCount() public {
        assertEq(counter.get(), 10, "初始值应为10");
        assertEq(counter.owner(), owner, "所有者应正确设置");
    }
    
    // 测试2:测试inc()函数
    function testIncrement() public {
        vm.prank(user);
        counter.inc();
        assertEq(counter.get(), 11, "inc()后应变为11");
    }
    
    // 测试3:测试incBy()函数
    function testIncrementBy() public {
        vm.prank(user);
        counter.incBy(5);
        assertEq(counter.get(), 15, "incBy(5)后应变为15");
    }
    
    // 测试4:测试权限(只有所有者能reset)
    function testResetPermission() public {
        // 非所有者调用应失败
        vm.prank(user);
        vm.expectRevert(Counter.NotOwner.selector);
        counter.reset();
        
        // 所有者调用应成功
        vm.prank(owner);
        counter.reset();
        assertEq(counter.get(), 0, "reset后应变为0");
    }
    
    // 测试5:Fuzzing测试 - 自动生成随机输入
    function testFuzzIncrementBy(uint256 x) public {
        vm.assume(x <= type(uint256).max - counter.get()); // 避免溢出
        uint256 initial = counter.get();
        
        vm.prank(user);
        counter.incBy(x);
        
        assertEq(counter.get(), initial + x, "incBy(x)应正确增加");
    }
    
    // 测试6:测试事件发射
    function testEventEmission() public {
        vm.prank(user);
        
        // 预期会发出Incremented事件
        vm.expectEmit(true, false, false, false);
        emit Incremented(user, 11);
        
        counter.inc();
    }
    
    // 测试7:Gas消耗测试
    function testGasCost() public {
        vm.prank(user);
        
        uint256 gasStart = gasleft();
        counter.inc();
        uint256 gasUsed = gasStart - gasleft();
        
        console.log("Gas used for inc():", gasUsed);
        assertLt(gasUsed, 50000, "inc()的Gas消耗应小于50000");
    }
}

步骤3:运行测试

bash 复制代码
# 运行所有测试
forge test

# 带颜色和详细输出
forge test -vv

# 只运行特定测试
forge test --match-test testIncrement

# 带Gas报告
forge test --gas-report

步骤4:编写部署脚本

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Script.sol";
import "../src/Counter.sol";

contract CounterScript is Script {
    function run() public {
        // 获取部署者私钥
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        
        // 开始广播交易(模拟真实部署)
        vm.startBroadcast(deployerPrivateKey);
        
        // 部署合约,初始值为100
        Counter counter = new Counter(100);
        
        // 记录部署地址
        console.log("Counter deployed at:", address(counter));
        
        vm.stopBroadcast();
    }
}

步骤5:部署到测试网

bash 复制代码
# 1. 首先设置环境变量(私钥和RPC URL)
export PRIVATE_KEY=0x你的私钥
export RPC_URL=https://sepolia.infura.io/v3/你的项目ID

# 2. 部署到Sepolia测试网
forge script script/Counter.s.sol:CounterScript \
    --rpc-url $RPC_URL \
    --private-key $PRIVATE_KEY \
    --broadcast \
    --verify \
    -vvvv

# 3. 或者使用交互模式(更安全)
forge script script/Counter.s.sol:CounterScript \
    --rpc-url $RPC_URL \
    --interactive

步骤6:与已部署合约交互

bash 复制代码
# 假设合约地址为 0x742d35Cc6634C0532925a3b844Bc9e...
export CONTRACT_ADDRESS=0x742d35Cc6634C0532925a3b844Bc9e...

# 1. 调用只读函数get()
cast call $CONTRACT_ADDRESS "get()" --rpc-url $RPC_URL

# 2. 发送交易调用inc()
cast send $CONTRACT_ADDRESS "inc()" \
    --private-key $PRIVATE_KEY \
    --rpc-url $RPC_URL

# 3. 编码调用数据
cast calldata "incBy(uint256)" 25

oundry配置文件解析

创建 foundry.toml 进行个性化配置:

XML 复制代码
[profile.default]
# 编译器设置
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.19"
optimizer = true
optimizer_runs = 200
via_ir = false  # 是否启用IR优化器

# 测试设置
fuzz_runs = 256      # Fuzzing测试运行次数
invariant_runs = 256 # 不变性测试运行次数

# 格式器设置
auto_detect_solhint = true
auto_detect_remappings = true

# 网络设置(在脚本和测试中使用)
[rpc_endpoints]
sepolia = "https://sepolia.infura.io/v3/${INFURA_API_KEY}"
mainnet = "https://mainnet.infura.io/v3/${INFURA_API_KEY}"
localhost = "http://localhost:8545"

[fmt]
line_length = 120

Foundry vs Hardhat 对比

特性 Foundry Hardhat
测试语言 Solidity JavaScript/TypeScript
测试速度 ⚡ 极快(Rust) 中等(Node.js)
Fuzzing测试 ✅ 内置支持 ❌ 需要插件
调试体验 优秀的堆栈追踪 良好的堆栈追踪
学习曲线 较陡(需懂Solidity测试) 平缓(对前端友好)
社区生态 快速增长 成熟完善
部署脚本 Solidity JavaScript

最佳实践建议

  1. 1、充分利用作弊码

javascript 复制代码
// 常用作弊码示例
vm.startPrank(user);  // 模拟用户地址
vm.deal(user, 10 ether); // 给用户10 ETH
vm.warp(block.timestamp + 1 days); // 时间旅行
vm.roll(block.number + 100); // 增加区块号

2、组织测试结构
3.

javascript 复制代码
contract ComplexTest is Test {
    using stdStorage for StdStorage;
    
    function setUp() public {
        // 初始化复杂状态
    }
    
    function testFunction1() public { /* ... */ }
    function testFunction2() public { /* ... */ }
    
    // 使用内联汇编进行低级测试
    function testAssembly() public {
        bytes memory code = hex"600a600052";
        address addr;
        assembly {
            addr := create(0, add(code, 0x20), mload(code))
        }
    }
}

3、性能优化技巧
4.

bash 复制代码
# 并行运行测试(利用多核CPU)
forge test --mt testIncrement --fork-url $RPC_URL -j 4

# 缓存编译结果
forge build --force --via-ir

# 生成覆盖率报告
forge coverage --report lcov

常见问题排查

bash 复制代码
# 1. 如果遇到"error: no such subcommand: `foundryup`"
# 手动安装:
curl -L https://foundry.paradigm.xyz | bash
foundryup

# 2. 解决依赖问题
forge install openzeppelin/openzeppelin-contracts

# 3. 更新所有依赖
forge update

# 4. 清理构建缓存
forge clean

# 5. 查看测试追踪(调试失败测试)
forge test -vvv --match-test testFailing

Foundry是一个强大且发展迅速的工具链。它最大的优势是让Solidity开发者能够用同一种语言完成开发、测试和部署的全流程。从简单的计数器开始,逐步尝试更复杂的合约和测试模式,你会发现它的效率远超传统工具。

接下来,你可以尝试用Foundry实现一个ERC20代币、一个多签钱包或一个简单的DEX,这些都是很好的练习项目。

相关推荐
DICOM医学影像10 小时前
8. go语言从零实现以太坊响应端 - 查询区块链账户余额
golang·区块链·以太坊·web3.0·响应端·从零实现
TechubNews10 小时前
当跨链协议开始将业务从「搭桥」拓展至「swap」
区块链
oMcLin10 小时前
如何在 SUSE Linux Enterprise Server 15 上通过配置 Nginx 与 PHP‑FPM 提升大流量电商平台的服务器响应能力
centos·去中心化·区块链
oMcLin12 小时前
如何在 CentOS 7.9 上部署基于区块链的数字资产交易平台,确保交易透明度与去中心化安全性
centos·去中心化·区块链
MicroTech202512 小时前
后量子密码算法集成:微算法科技(NASDAQ: MLGO)构建区块链安全防护的量子盾牌
科技·安全·区块链
链科天下14 小时前
赵长鹏投资去中心化交易平台,想要打造“链上币安”?
区块链
老蒋每日coding14 小时前
Solidity入门(1)- Hardhat框架
区块链
视***间14 小时前
视程空间AIR算力开发平台:以边缘智能之核,驱动机器人产业迈入全域自动化时代
大数据·人工智能·机器人·区块链·边缘计算·视程空间
oMcLin14 小时前
如何在RHEL 8.6上部署并优化区块链节点,确保跨境支付平台的高安全性与快速交易验证
区块链