Foundry 基础使用操作

一、创建项目

1
forge init hello_foundry

构建项目:

1
forge build

运行测试

1
forge test

out目录为合约工件
cache目录被forge用来记录

在现有项目工作:

1
2
3
4
$ git clone https://github.com/PaulRBerg/foundry-template
$ cd foundry-template
$ forge install
$ bun install # 安装 Solhint, Prettier, 以及其他 Node.js 依赖项。

 forge install 用来安装项目中的子模块依赖项。

二、项目结构

image.png

  • src:智能合约源代码(Counter.sol)
  • test:单元测试文件(Counter.t.sol)
  • script:部署和交互脚本(Deploty.s.sol)
  • lib:依赖库
  • foundry.toml:配置文件,用于自定义构建、测试、部署等行为。

三、如何跑起来一个代码

配置好vscode插件:
Solidity(由 Juan Blanco 开发):提供 Solidity 语法高亮、自动补全和 linting。
Prettier - Code formatter:自动格式化代码,确保一致性。
Solidity Visual Developer:增强 Solidity 可视化,如函数调用图和审计工具。

本地测试

先用默认的测试代码运行

1
2
forge build
forge test

得到out(合约工件、如ABI)、cache(记录,防重复编译)目录

src/Counter.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter { // 一个很简单的 Counter 合约
uint256 public number; // 维护一个 public 的 uint256 数字

// 设置 number 变量的内容
function setNumber(uint256 newNumber) public {
number = newNumber;
}

// 让 number 变量的内容自增
function increment() public {
number++;
}
}

script/Counter.s.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13; // 许可 和 Solidity版本标识

import "forge-std/Script.sol"; // 引入foundry forge中的Script库
import "../src/Counter.sol"; // 引入要部署的Counter合约

// 部署脚本继承了Script合约
contract CounterScript is Script {
// 可选函数,在每个函数运行之前被调用
function setUp() public {}

// 部署合约时会调用run()函数
function run() public {
vm.startBroadcast(); // 开始记录脚本中合约的调用和创建
new Counter(); // 创建合约
vm.stopBroadcast(); // 结束记录
}
}

运行得到:

1
2
3
4
5
6
7
8
9
 $ forge script script/Counter.s.sol:CounterScript
[⠊] Compiling...
[⠔] Compiling 2 files with Solc 0.8.30
[⠒] Solc 0.8.30 finished in 402.95ms
Compiler run successful!
Script ran successfully.
Gas used: 152949

If you wish to simulate on-chain transactions pass a RPC URL.

表示脚本在 本地EVM 环境编译成功了,此时并没有上链

测试上链

rpc可在https://chainlist.org/?search=sepolia&testnets=true

  1. 使用测试网 Ethereum Sepolia 的链上状态,但是不发送交易(fork测试网)
1
2
forge script script/Counter.s.sol:CounterScript \
--rpc-url https://ethereum-sepolia-rpc.publicnode.com

合约和交易逻辑验证通过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 $ forge script script/Counter.s.sol:CounterScript \
--rpc-url https://ethereum-sepolia-rpc.publicnode.com
[⠊] Compiling...
No files changed, compilation skipped
Script ran successfully.

## Setting up 1 EVM.

==========================

Chain 11155111

Estimated gas price: 60.500178376 gwei

Estimated total gas used for script: 203856

Estimated amount required: 0.012333324363017856 ETH

==========================

SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.

Transactions saved to: /home/vstral/Code/Web3/study/broadcast/Counter.s.sol/11155111/dry-run/run-latest.json

Sensitive values saved to: /home/vstral/Code/Web3/study/cache/Counter.s.sol/11155111/dry-run/run-latest.json

生成了文件

  • 第一个broadcast的:run-latest.json:交易记录
  • 第二个cache的:run-lastest.json:私钥、签名等隐私数据

真正上链

要想上链需要配置自己的私钥,有以下方法:

  1. 可以直接指定私钥
    添加参数 –private-key YOUR_PRIVATE_KEY
  2. 使用foundry.toml配置填入钱包地址和rpc,千万不要写入私钥,私钥需要放在项目根目录.env文件中(注意在.gitignore中屏蔽)
1
2
3
4
5
[profile.default]
sender = "YOUR_ADDRESS"

[rpc_endpoints]
sepolia = "https://ethereum-sepolia-rpc.publicnode.com"

.env文件:

1
2
3
# .env
PRIVATE_KEY=你的私钥
RPC_URL=https://ethereum-sepolia-rpc.publicnode.com

注意需要source .env引入环境变量

添加 –broadcast 和 –private-key:

1
2
3
4
$ forge script script/Counter.s.sol:CounterScript \
--rpc-url sepolia \
--broadcast \
--private-key $PRIVATE_KEY

image.png

(推荐)在合约里读取环境变量,避免私钥出现在命令历史,增大安全风险

1
vm.startBroadcast(vm.envUint("PRIVATE_KEY));

测试合约状态

调用函数:

  1. 读状态(view函数)假设有 function number() view returns (uint256) 这样一个函数:
1
cast call 0xdfD18Fc5A3416Fd677C8b238EAC14c282FdF0428 "number()" --rpc-url sepolia
  1. 调用修改状态的函数
1
2
3
4
cast send 0xdfD18Fc5A3416Fd677C8b238EAC14c282FdF0428 "increment()" \
--private-key $PRIVATE_KEY \
--rpc-url sepolia

cast命令文档:cast - Foundry 中文文档 | 登链社区
s
cast call的本质:调用JSON-RPC的 eth_call:

  • 在某个区块高度的EVM上假装执行
  • 执行完后丢弃结果,不写storage

call send做了什么?
使用eth_sendRawTransaction

  • 构造交易
  • 用私钥签名
  • 通过RPC发送到mempool
  • 被矿工打包
  • 写入区块