Foundry 备忘录
Foundry GitHub - https://github.com/foundry-rs Foundry Book - https://book.getfoundry.sh/ Installing Foundry - https://github.com/foundry-rs/foundry#installation
Foundry 由三个部分组成:
Forge: 以太坊测试框架(如Truffle,Hardhat和DappTools). Cast: 用于与EVM智能合约交互,发送交易和获取链数据. Anvil: 本地以太坊节点,类似于Ganache或Hardhat网络.
Forge 既包括一个CLI,也包括一个标准库.
有关Foundry的介绍,请查看以下资源:
电报频道here
从一个空文件夹初始化新项目:
forge init
或者定义一个新文件夹,在其中创建项目:
forge init my-app
会创建4个文件夹:
src - 用于存放智能合约
script - 部署脚本
test - 测试用例
lib - 这类似于node_modules
可以重新映射依赖项,使其更易于导入。Forge会自动尝试为您推断一些重新映射:
$ forge remappings
ds-test/=lib/solmate/lib/ds-test/src/
forge-std/=lib/forge-std/src/
如果使用的是 VS Code,则可以通过安装 vscode-solidity 来配置重新映射。
在此处了解有关重新映射工作原理的详细信息
可以运行 forge test
命令来进行测试.
forge test
任何用test
关键字开头的函数都是测试函数.
function testAssertEquality() public {
int some_int = 1;
assertEq(1, some_int);
}
通常, 测试脚本放置在 src/test
文件夹中,并且文件名以 .t.sol
结尾。
Foundry使用 Dappsys Test
(DSTest) 来提供基础的日志和断言功能.
断言的一些例子:
assertTrue
assertEq
assertEqDecimal
assertEq32
assertEq0
assertGt
assertGtDecimal
assertGe
assertGeDecimal
assertLt
assertLtDecimal
assertLe
assertLeDecimal
详细列表这里查看.
从一个基本的计数器合约开始,如下所示 (src/Counter.sol):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Counter {
int private count;
constructor(int _count) {
count = _count;
}
function incrementCounter() public {
count += 1;
}
function decrementCounter() public {
count -= 1;
}
function getCount() public view returns (int) {
return count;
}
}
我们的测试这样写:(test/Counter.t.sol):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import 'src/Counter.sol';
contract ContractTest is Test {
Counter counter;
function setUp() public {
counter = new Counter(10);
}
function testGetCount() public {
int value = counter.getCount();
assertEq(value, 10);
emit log_int(value);
}
function testIncrement() public {
counter.incrementCounter();
counter.incrementCounter();
int value = counter.getCount();
assertEq(value, 12);
emit log_int(value);
}
function testDecrement() public {
counter.decrementCounter();
int value = counter.getCount();
assertEq(value, 9);
emit log_int(value);
}
}
你可以注意到了,我们使用log_int
来打印日志。你也可以使用以下的emit:
emit log(string);
emit log_address(address);
emit log_bytes32(bytes32);
emit log_int(int);
emit log_uint(uint);
emit log_bytes(bytes);
emit log_string(string);
emit log_named_address(string key, address val);
emit log_named_bytes32(string key, bytes32 val);
emit log_named_decimal_int(string key, int val, uint decimals);
emit log_named_decimal_uint(string key, uint val, uint decimals);
emit log_named_int(string key, int val);
emit log_named_uint(string key, uint val);
emit log_named_bytes(string key, bytes val);
emit log_named_string(string key, string val);
默认 forge test
仅显示通过和失败测试的日志,你可以使用-v
来展示更多的日志:
Level 2 (-vv
): 还会显示测试期间发出的日志。这包括来自测试的断言错误,显示诸如预期与实际等信息。
Level 3 (-vvv
): 还会显示失败测试的堆栈跟踪。
Level 4 (-vvvv
): 显示所有测试的堆栈跟踪,并显示失败测试的设置跟踪。
Level 5 (-vvvvv
): 始终显示堆栈跟踪和设置跟踪。
为了查看上面我们写的日志, 我们最少要输入-vv
:
forge test -vv
Cheatcodes 为您提供了额外的断言、更改 EVM 状态、模拟数据等的功能。
这个例子, 你可以使用 prank 和 startPrank来模拟用户:
address bob = address(0x1);
vm.startPrank(bob);
使用setNonce给账户设置nonce。
vm.setNonce(address(100), 1234);
使用roll设置block.number
。
vm.roll(100);
emit log_uint(block.number); // 100
使用warp设置block.timestamp
。
vm.warp(1641070800);
emit log_uint(block.timestamp); // 1641070800
这里查看更多
在上面,我们使用.prank
或 .startPrank
来模拟用户,让我们看看是怎么做到的吧!
假设我们有一个ERC721合同,我们希望确保只有代币的所有者才能转移或销毁该代币。我们的测试可能如下:
// only the owner can transfer
function testTransferToken() public {
// mint the token to bob's address
erc721 = new ERC721();
erc721.mint(bob, 0);
// emulate bob
vm.startPrank(bob);
// transfer to mary
erc721.safeTransferFrom(bob, mary, 0);
// check to make sure mary is the new owner
address owner_of = erc721.ownerOf(0);
assertEq(mary, owner_of);
}
// only the owner can burn
function testBurn() public {
erc721 = new ERC721();
erc721.mint(bob, 0);
vm.startPrank(bob);
erc721.burn(0);
}
模糊测试允许我们定义函数参数类型,框架会在运行时自动测试这些值。
例如,我们可以创建一个测试函数来接收函数参数,并在测试中使用该值,而无需定义它是什么:
合约如下:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
contract HelloWorld {
string private greeting;
uint public version = 0;
constructor (string memory _greeting) {
greeting = _greeting;
}
function greet() public view returns(string memory) {
return greeting;
}
}
测试合约如下:
contract ContractTest is Test {
function testFuzzing(string memory _greeting) public {
HelloWorld hello = new HelloWorld(_greeting);
assertEq(
hello.greet(),
_greeting
);
}
}
您可以轻松打印gas测试报告:
forge test --gas-report
在运行forge build
脚本进行构建或脚本部署之后,ABI 将位于out
目录中。
forge test --help
Foundry 最近发布了Solidity Scripting .
Scripting 让你对如何使用Solidity脚本部署合约进行大量控制, 我相信它旨在取代以前部署合约的forge create
。
From Foundry Book:
Scripts are executed by calling the function named run
, our entrypoint:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import {Counter} from "src/Counter.sol";
contract ContractScript is Script {
function setUp() public {}
function run() public {
vm.startBroadcast();
new Counter(10);
vm.stopBroadcast();
}
}
现在,我们可以使用此脚本将我们的智能合约部署到实时或测试网络. 🚀
startBroadcast
和 startBroadcast(address)
将让所有后续调用(仅在此调用深度)创建可以稍后在链上签名和发送交易。
stopBroadcast
停止收集交易以供以后的链上广播。
启动Anvil运行本地测试网:
anvil
启动后,Anvil 将为您提供一个本地 RPC 和一些您可以使用的私钥和帐户。
现在,我们可以使用本地 RPC 在本地部署:
forge script script/Contract.s.sol:ContractScript --fork-url http://localhost:8545 \
--private-key $PRIVATE_KEY --broadcast
在本地部署合约后,Anvil 将注销合约地址。
接下来,将合约地址设置为环境变量
export CONTRACT_ADDRESS=<contract-address>
然后,我们可以使用cast send
发送事务。
cast send $CONTRACT_ADDRESS "incrementCounter()" \
--private-key $PRIVATE_KEY
我们可以使用cast call
执行读取操作:
cast call $CONTRACT_ADDRESS "getCount()(int)"
现在我们已经在本地部署和测试,我们可以部署到网络
运行以下脚本:
forge script script/Contract.s.sol:ContractScript --rpc-url $RPC_URL \
--private-key $PRIVATE_KEY --broadcast
一旦合约部署到网络,我们可以使用cast send
来发送事务:
cast send $CONTRACT_ADDRESS "incrementCounter()" --rpc-url $RPC_URL \
--private-key $PRIVATE_KEY
我们可以使用cast call
执行读取操作:
cast call $CONTRACT_ADDRESS "getCount()(int)" --rpc-url $RPC_URL
可以通过运行`-- help
来获取命令的完整列表
cast --help
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!