1. 引言

Foundry 是以 Rust 编写的智能合约开发工具链,以其极快的编译速度、内置的测试框架和强大的命令行工具而闻名。相较于传统的 Hardhat 或 Truffle,Foundry 在开发体验和性能上都有显著提升,尤其适合追求高效开发的 Solidity 开发者。

本文将带您从零开始,完成一个完整的 Foundry 智能合约开发实战,涵盖环境搭建、项目创建、合约编写、测试编写、部署脚本以及常用高级功能。

2. 环境准备与安装

2.1 系统要求

  • Rust 工具链:Foundry 基于 Rust 构建,需要先安装 Rust。如果尚未安装,可以使用以下命令:
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
  • Git:用于版本控制和依赖管理。

2.2 安装 Foundry

Foundry 的安装非常简单,通过 foundryup 工具可以一键安装:

curl -L https://foundry.paradigm.xyz | bash

安装完成后,重启终端或运行 source ~/.bashrc(或对应 shell 的配置文件),然后执行:

foundryup

这将安装或更新 forgecastanvilchisel 等核心工具。

2.3 验证安装

运行以下命令验证安装是否成功:

forge --version
cast --version
anvil --version

如果都能正确输出版本号,说明 Foundry 已准备就绪。

3. 创建第一个 Foundry 项目

使用 forge init 命令创建一个新项目:

forge init hello-foundry
cd hello-foundry

项目结构如下:

hello-foundry/
├── foundry.toml      # 项目配置文件
├── script/           # 部署脚本
├── src/              # 合约源代码
│   └── Counter.sol   # 示例合约
├── test/             # 测试文件
│   └── Counter.t.sol # 示例测试
└── lib/              # 依赖库(如 OpenZeppelin)

4. 编写智能合约

让我们修改默认的 Counter 合约,实现一个简单的代币合约 SimpleToken

src/SimpleToken.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract SimpleToken is ERC20 {
    address public owner;

    constructor(uint256 initialSupply) ERC20("SimpleToken", "STK") {
        owner = msg.sender;
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }

    // 仅所有者可以增发代币
    function mint(address to, uint256 amount) external {
        require(msg.sender == owner, "Only owner can mint");
        _mint(to, amount);
    }

    // 销毁代币
    function burn(uint256 amount) external {
        _burn(msg.sender, amount);
    }
}

5. 编写测试

Foundry 的测试框架非常强大,支持 Solidity 编写测试,并内置了作弊码(Cheatcodes)用于模拟各种链上场景。

test/SimpleToken.t.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/SimpleToken.sol";

contract SimpleTokenTest is Test {
    SimpleToken public token;
    address public owner = address(0x1);
    address public user = address(0x2);

    function setUp() public {
        vm.startPrank(owner); // 使用作弊码模拟 owner 地址
        token = new SimpleToken(1000); // 初始发行 1000 个代币
        vm.stopPrank();
    }

    function testInitialSupply() public {
        assertEq(token.totalSupply(), 1000 * 10 ** token.decimals());
        assertEq(token.balanceOf(owner), 1000 * 10 ** token.decimals());
    }

    function testMintByOwner() public {
        vm.startPrank(owner);
        token.mint(user, 500 * 10 ** token.decimals());
        vm.stopPrank();

        assertEq(token.balanceOf(user), 500 * 10 ** token.decimals());
    }

    function testMintByNonOwnerShouldFail() public {
        vm.startPrank(user); // 非 owner 地址尝试 mint
        vm.expectRevert("Only owner can mint");
        token.mint(user, 100 * 10 ** token.decimals());
        vm.stopPrank();
    }

    function testBurn() public {
        uint256 initialBalance = token.balanceOf(owner);
        vm.startPrank(owner);
        token.burn(100 * 10 ** token.decimals());
        vm.stopPrank();

        assertEq(token.balanceOf(owner), initialBalance - 100 * 10 ** token.decimals());
    }
}

运行测试:

forge test

如果一切正常,您将看到所有测试通过。

6. 编译与部署

6.1 编译合约

forge build

编译后的合约字节码和 ABI 将生成在 out/ 目录下。

6.2 部署到本地测试网

首先启动一个本地 Anvil 节点(模拟以太坊网络):

anvil

Anvil 会启动一个本地节点并输出一些测试账户私钥和 RPC URL(默认为 http://127.0.0.1:8545)。

在新的终端中,使用 forge create 部署合约:

forge create --rpc-url http://127.0.0.1:8545 \
  --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
  src/SimpleToken.sol:SimpleToken \
  --constructor-args 1000

请将 --private-key 替换为 Anvil 输出的一个测试账户私钥。命令执行后会输出合约地址。

6.3 使用 Cast 与合约交互

Cast 是 Foundry 的 CLI 工具,用于与合约交互、查询链上数据等。

查询代币名称

cast call <合约地址> "name()(string)" --rpc-url http://127.0.0.1:8545

发送交易(mint)

cast send <合约地址> "mint(address,uint256)" <接收地址> 500 \
  --rpc-url http://127.0.0.1:8545 \
  --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

7. 高级功能与技巧

7.1 Gas 优化报告

Foundry 可以生成 Gas 使用报告,帮助优化合约:

forge test --gas-report

7.2 Fuzz Testing(模糊测试)

Foundry 内置了强大的模糊测试功能,可以自动生成随机输入来测试函数:

function testTransferFuzz(address to, uint256 amount) public {
    vm.assume(to != address(0)); // 排除零地址
    vm.assume(amount <= token.balanceOf(owner));

    vm.startPrank(owner);
    token.transfer(to, amount);
    vm.stopPrank();

    assertEq(token.balanceOf(to), amount);
}

7.3 Invariant Testing(不变性测试)

不变性测试用于验证合约在随机序列操作下某些属性始终成立:

function invariant_totalSupplyNeverDecreases() public {
    assertGe(token.totalSupply(), previousTotalSupply);
}

运行不变性测试:

forge test --invariant

7.4 使用 OpenZeppelin 等依赖

foundry.toml 中添加依赖:

[dependencies]
openzeppelin-contracts = "5.0.0"

然后安装:

forge install OpenZeppelin/openzeppelin-contracts

8. 部署到测试网

8.1 配置环境变量

创建 .env 文件:

PRIVATE_KEY=你的私钥
ETHERSCAN_API_KEY=你的Etherscan API Key
RPC_URL_SEPOLIA=https://sepolia.infura.io/v3/你的项目ID

8.2 编写部署脚本

script/DeploySimpleToken.s.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

contract DeploySimpleToken is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        SimpleToken token = new SimpleToken(1000);

        vm.stopBroadcast();
        console.log("SimpleToken deployed at:", address(token));
    }
}

8.3 执行部署

source .env
forge script script/DeploySimpleToken.s.sol:DeploySimpleToken \
  --rpc-url $RPC_URL_SEPOLIA \
  --broadcast \
  --verify \
  -vvvv

9. 总结

Foundry 以其卓越的性能和开发者体验,正在成为 Solidity 智能合约开发的首选工具链。通过本文的实战演练,您已经掌握了:

  1. Foundry 环境的安装与配置
  2. 项目的创建与结构
  3. 合约的编写与测试(包括常规测试、模糊测试)
  4. 本地节点的使用与合约部署
  5. 与合约的 CLI 交互
  6. 测试网部署与验证

建议您进一步探索 Foundry 的文档,了解更多高级功能,如分叉测试(Fork Testing)、代码覆盖率(Coverage)和基准测试(Benchmarking),以构建更安全、更高效的智能合约。

下一步学习建议

  • 阅读 Foundry Book 官方文档
  • 尝试集成 Chainlink 预言机等更复杂的合约模式
  • 使用 forge fmt 统一代码风格,forge doc 生成文档
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐