Foundry 之 FORGE和CAST
uwupu 啦啦啦啦啦

Foundry类似于cargo或npm。

参考文献:https://www.hackquest.io/

关键词

  • pass,表示测试通过

Foundry介绍

Foundry 是一个 Solidity 框架,用于构建、测试、模糊、调试和部署Solidity 智能合约。

Foundry 用 Rust 语言编写,包含了一系列的可以与 Ethereum 网络交互的工具。主要有:

  • Forge 用来进行合约的测试。

  • Cast 很方便的与合约进行交互,发交易,查询链上数据。

  • Anvil 可以模拟一个私有节点。

  • Chisel 可以在命令行快速的有效的实时的写合约,测试合约。

OTHER INFO

基于rust。

FORGE

项目管理工具,类似于npm。

forge init PROJECT_NAME

项目初始化。创建一个项目。

项目目录结构

1
2
3
4
5
6
7
8
D:\foundry_test\hello_foundry>tree 
├─.github
│ └─workflows
├─lib # 依赖库目录
├─script # 部署脚本文件
├─src # 智能合约目录
└─test # 测试用例文件夹
├─foundry.toml # 配置文件

forge build

编译合约。默认输出到out文件夹。

forge test

测试合约。测试用例。

用法

  • forge test: 测试test下所有合约;
  • forge test –match-path test/Counter.t.sol:指定要测试的文件夹。
  • forge test –match-contract CounterTest –match-test test_Increment 命令,用 –match-contract 来指定测试合约的名称,其中 –match-test 用来指定调用的测试方法
  • -v/-vv/-vvv/-vvvv:打印日志级别,v越多表示级别越高。
    • vv:打印日志,断言,预期结果,错误原因;
    • vvv:打印测试失败的失败堆栈调用
    • vvvv:打印所有的堆栈调用
    • vvvvv:包括以上所有,另外包含对象的创建等信息。
  • --watch:监听文件更改,文件一旦更改就运行测试。
    • --run-all:与watch一起用,当文件更改就重新运行所有测试。

基本要求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity 0.8.10;

import "forge-std/Test.sol";

contract ContractBTest is Test {
uint256 testNumber;

function setUp() public {
testNumber = 42;
}

function testFail_Subtract43() public {
testNumber -= 43;
}
}
  • setUp为初始化方法。
  • 对于方法名:
    • 测试方法必须以test作为开头,而且有分类:
      • 以test开头:这个方法的要求是要执行成功,也就是没有报错;
      • 以testFail开头:这个方法的要求是要执行失败,也就是有报错;
  • 所有的测试方法必须有externalpublic。声明为internalprivate的方法不会被forge处理。

修改要求的测试结果

1
2
3
4
function test_CannotSubtract43() public {
vm.expectRevert(stdError.arithmeticError);
testNumber -= 43;
}

作为uint的testNumber执行后会变成负数,会报错,这是本来的流程。

这个方法以test开头,但vm.expectRevert(stdError.arithmeticError);修改了pass的条件,含义为“期待出现arithmeticError”,也就是这里“如果出现arithmeticError,则pass,与testFail功能类似。

打印测试日志

console2.log:与printf的使用方法一致。

forge create/合约部署

合约部署和验证。也可以用solidity-scripting来实现,不过这里不介绍。

准备

部署:测试币账号、区块链节点的RPC_URL;

验证:区块链浏览器的ETHERSCAN_API_KEY

准备获取

RPC_URL:可以从Alchemy获取。

ETHERSCAN_API_KEY:在以太坊区块链浏览器中创建 app,会分配对应的 API KEY TOKEN。

EXAMPLE

部署一个ERC20合约为例。合约只有一个构造函数,用来指定Token的名称、标识符、精度及初始发行量信息。

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

import {ERC20} from "solmate/tokens/ERC20.sol";

contract MyToken is ERC20 {
constructor(
string memory name,
string memory symbol,
uint8 decimals,
uint256 initialSupply
) ERC20(name, symbol, decimals) {
_mint(msg.sender, initialSupply);
}
}

需要安装包transmissions11/solmate。forge install transmissions11/solmate

部署

1
2
3
4
5
6
7
forge create \
--rpc-url https://eth-sepolia.g.alchemy.com/v2/xxxxxx \
--constructor-args "MyToken" "MT" 18 1000000000000000 \
--private-key 0xxxxxxx \
--etherscan-api-key xxxx \
--verify src/MyToken.sol:MyToken
--broadcast
  • RPC_URL: 即RPCURL

  • private-key: 钱包私钥

  • verify: 验证合约

    • MyContract:实际部署的合约。
  • constructor-args: 可选,构造函数参数。

  • –broadcast 部署到网络中。

forge verify-contract/验证合约

1
2
3
4
5
6
7
8
9
forge verify-contract \
--chain-id 11155111 \
--num-of-optimizations 1000000 \
--watch \
--constructor-args $(cast abi-encode "constructor(string,string,uint256,uint256)" "ForgeUSD" "FUSD" 18 1000000000000000000000) \
--etherscan-api-key <your_etherscan_api_key> \
--compiler-version v0.8.10+commit.fc410830 \
<the_contract_address> \
src/MyToken.sol:MyToken

基本信息:

-
the_contract_address:合约地址。

  • ::合约源码的路径及合约名称,如果合约文件中包含多个合约,需要使用 :MyContract 这种方式指定具体的合约。
  • etherscan-api-key:区块链浏览器的 API KEY TOKEN,用于验证合约。

以及部署合约时的环境信息:

  • constructor-args:合约构造器的参数,以 ABI-encoded 形式提供,如果没有构造器参数,该属性可忽略。
  • compiler-version:合约编译器版本。如果没有指定则会自动检测。
  • num-of-optimizations:Solidity 编译器在进行优化时迭代运行的次数。请注意,如果在验证时未设置优化次数,则默认为 0,而如果在部署时未设置,则默认为 200,因此,如果保留默认编译设置,需要使用–num-of-optimizations 200 来确保验证通过。
  • chain-id:测试网的ID,sepolia 测试网为 11155111。
  • watch:用于查询验证结果。

solidity-scripting 更方便的部署与验证

forge create输入的参数过多,可以使用脚本更方便的进行部署合约。(注意不是js)

简述:使用solidity写一个Script:从环境变量中取参数,然后部署到区块链中。

  1. 首先创建.env 。(我以为这个.env可以自动输入,直到后面的教程居然来了句source .env我可去*****,这个操作可以的,只是我没想到居然这样)
1
2
3
4
5
6
// 区块链 RPC 节点地址
SEPOLIA_RPC_URL=xxxx
// 钱包私钥
PRIVATE_KEY=xxxx
// 区块链浏览器的 API KEY TOKEN
ETHERSCAN_API_KEY=xxxx
  1. source一下。
  2. 编辑foundry.toml,这里可以使用环境变量
1
2
3
4
5
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"

[etherscan]
sepolia = { key = "${ETHERSCAN_API_KEY}" }
  1. 创建一个脚本(==script==)。在script文件夹下,创建一个MyToken.s.sol文件,输入以下内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {Script} from "forge-std/Script.sol";
import "../src/MyToken.sol";

contract MyTokenScript is Script {

function run() external {
uint256 deployer = vm.envUint("PRIVATE_KEY");//从环境变量拿钱包私钥
vm.startBroadcast(deployer);//开始广播交易,我这里有个错误但方便的理解:相当于连接以太网
MyToken myToken = new MyToken("MyToken", "MT", 18, 1000000000000000000);//部署合约
vm.stopBroadcast();//关闭交易,我这里有个错误但方便的理解:类似于断开以太网
}
}
  1. 运行脚本
1
2
3
4
5
# 加载 .env 文件中的变量
source .env

# 部署并验证合约
forge script script/MyToken.s.sol:MyTokenScript --rpc-url $SEPOLIA_RPC_URL --broadcast --verify -vvvv

接下来会打印部署结果,以及部署的合约地址。

CAST

基本介绍

cast是Foundry用于执行以太坊RPC调用的命令行工具,可以使用Cast进行智能合约的调用、发送交易或检索链数据。

1
cast <subcommand>

EXAMPLE

比如cast获取代币的总供应量.

1
cast call 0x6b175474e89094c44da98b954eedeac495271d0f "totalSupply()(uint256)" --rpc-url <your rpc url> 8603853182003814300330472690

使用cast发送消息.

1
cast send --private-key <Your Private Key> 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc $(cast from-utf8 "hello world") --rpc-url http://127.0.0.1:8545/

获取链上信息

首先要设定环境变量ETH_RPC_URL,然后执行以下命令即可。

  • cast chain-id: 获取当前链的ID
  • cast chain: 获取当前链的名称
  • cast client 获取当前客户端版本
  • cast gas-price 获取当前的gas价格
  • cast block-number 获取当前最新的区块号
  • cast basefee 获取指定区块的基础费用
  • cast block <BLOCK ID>:获取指定区块的详细信息。区块高度,时间戳,交易数等。
  • cast age:获取指定区块的具体时间。

获取账户信息

cast balance <ADDRESS | ENS_NAME.eth>

ADDRESS:地址。

ENS_NAME.eth: ENS名称。

可以通过地址或者ENS名称查询账户余额。

发送交易

发送交易/调用合约函数

1
cast send --private-key <private_key_addr> <contract_addr> "exampleFunc(uint256)" <argument_value_of_the_function>

EXAMPLE

存款函数

1
cast send --private-key PRIVATE_KEY CONTRACT_ADDR "deposit(uint256)" 10

其他

如果出现不存在的函数,则会触发fallback函数: cast send --private-key <private_key_addr> <contract_addr> "dummy()"

可以触发receive函数:cast send --private-key <private_key_addr> <contract_addr> --value 10gwei

获取合约代码

1
cast etherscan-source <contract_address>

可以获取到智能合约的源代码。

 评论