Forge 标准库是一系列有用的合约和库,可与 Forge 和 Foundry 一起使用。它利用 Forge 的作弊代码使编写测试变得更容易、更快,同时改进作弊代码的用户体验。
通过 Foundry Book(Forge-Std 指南)了解如何使用 Forge-Std。
forge install foundry-rs/forge-std
这是一个针对错误和恢复的辅助合约。在 Forge 中,此合约对于expectRevert
作弊代码特别有用,因为它提供了所有编译器内置错误。
有关所有错误代码,请参阅合同本身。
import " forge-std/Test.sol " ;
contract TestContract is Test {
ErrorsTest test;
function setUp () public {
test = new ErrorsTest ();
}
function testExpectArithmetic () public {
vm. expectRevert (stdError.arithmeticError);
test. arithmeticError ( 10 );
}
}
contract ErrorsTest {
function arithmeticError ( uint256 a ) public {
uint256 a = a - 100 ;
}
}
这是一个相当大的合同,因为所有的重载都是为了使用户体验良好。首先,它是record
的包装并accesses
作弊代码。它总是可以在不知道存储布局的情况下找到并写入与特定变量关联的存储槽。对此的一个主要警告是,虽然可以为打包存储变量找到一个槽,但我们无法安全地写入该变量。如果用户尝试写入打包槽,则执行会引发错误,除非它未初始化( bytes32(0)
)。
这是通过在函数调用期间记录所有SLOAD
和SSTORE
来实现的。如果读取或写入单个槽,它会立即返回该槽。否则,我们在幕后迭代并检查每一个(假设用户传递了depth
参数)。如果变量是结构体,则可以传入depth
参数,该参数基本上是景深。
IE:
struct T {
// depth 0
uint256 a;
// depth 1
uint256 b;
}
import " forge-std/Test.sol " ;
contract TestContract is Test {
using stdStorage for StdStorage;
Storage test;
function setUp () public {
test = new Storage ();
}
function testFindExists () public {
// Lets say we want to find the slot for the public
// variable `exists`. We just pass in the function selector
// to the `find` command
uint256 slot = stdstore. target ( address (test)). sig ( " exists() " ). find ();
assertEq (slot, 0 );
}
function testWriteExists () public {
// Lets say we want to write to the slot for the public
// variable `exists`. We just pass in the function selector
// to the `checked_write` command
stdstore. target ( address (test)). sig ( " exists() " ). checked_write ( 100 );
assertEq (test. exists (), 100 );
}
// It supports arbitrary storage layouts, like assembly based storage locations
function testFindHidden () public {
// `hidden` is a random hash of a bytes, iteration through slots would
// not find it. Our mechanism does
// Also, you can use the selector instead of a string
uint256 slot = stdstore. target ( address (test)). sig (test.hidden. selector ). find ();
assertEq (slot, uint256 ( keccak256 ( " my.random.var " )));
}
// If targeting a mapping, you have to pass in the keys necessary to perform the find
// i.e.:
function testFindMapping () public {
uint256 slot = stdstore
. target ( address (test))
. sig (test.map_addr. selector )
. with_key ( address ( this ))
. find ();
// in the `Storage` constructor, we wrote that this address' value was 1 in the map
// so when we load the slot, we expect it to be 1
assertEq ( uint (vm. load ( address (test), bytes32 (slot))), 1 );
}
// If the target is a struct, you can specify the field depth:
function testFindStruct () public {
// NOTE: see the depth parameter - 0 means 0th field, 1 means 1st field, etc.
uint256 slot_for_a_field = stdstore
. target ( address (test))
. sig (test.basicStruct. selector )
. depth ( 0 )
. find ();
uint256 slot_for_b_field = stdstore
. target ( address (test))
. sig (test.basicStruct. selector )
. depth ( 1 )
. find ();
assertEq ( uint (vm. load ( address (test), bytes32 (slot_for_a_field))), 1 );
assertEq ( uint (vm. load ( address (test), bytes32 (slot_for_b_field))), 2 );
}
}
// A complex storage contract
contract Storage {
struct UnpackedStruct {
uint256 a;
uint256 b;
}
constructor () {
map_addr[ msg . sender ] = 1 ;
}
uint256 public exists = 1 ;
mapping ( address => uint256 ) public map_addr;
// mapping(address => Packed) public map_packed;
mapping ( address => UnpackedStruct) public map_struct;
mapping ( address => mapping ( address => uint256 )) public deep_map;
mapping ( address => mapping ( address => UnpackedStruct)) public deep_map_struct;
UnpackedStruct public basicStruct = UnpackedStruct ({
a: 1 ,
b: 2
});
function hidden () public view returns ( bytes32 t ) {
// an extremely hidden storage slot
bytes32 slot = keccak256 ( " my.random.var " );
assembly {
t := sload (slot)
}
}
}
这是一个对各种作弊代码的包装,需要包装器对开发人员更加友好。目前只有prank
相关的功能。一般来说,用户可能希望将 ETH 放入prank
的地址中,但出于安全原因,情况并非如此。明确地说,这个hoax
函数只能用于具有预期余额的地址,因为它会被覆盖。如果一个地址已经有 ETH,你应该使用prank
。如果您想明确更改该余额,只需使用deal
。如果您想两者兼得, hoax
也适合您。
// SPDX-License-Identifier: MIT
pragma solidity ^ 0.8.0 ;
import " forge-std/Test.sol " ;
// Inherit the stdCheats
contract StdCheatsTest is Test {
Bar test;
function setUp () public {
test = new Bar ();
}
function testHoax () public {
// we call `hoax`, which gives the target address
// eth and then calls `prank`
hoax ( address ( 1337 ));
test. bar {value: 100 }( address ( 1337 ));
// overloaded to allow you to specify how much eth to
// initialize the address with
hoax ( address ( 1337 ), 1 );
test. bar {value: 1 }( address ( 1337 ));
}
function testStartHoax () public {
// we call `startHoax`, which gives the target address
// eth and then calls `startPrank`
//
// it is also overloaded so that you can specify an eth amount
startHoax ( address ( 1337 ));
test. bar {value: 100 }( address ( 1337 ));
test. bar {value: 100 }( address ( 1337 ));
vm. stopPrank ();
test. bar ( address ( this ));
}
}
contract Bar {
function bar ( address expectedSender ) public payable {
require ( msg . sender == expectedSender, " !prank " );
}
}
包含各种断言。
console.log
用法遵循与 Hardhat 相同的格式。建议使用console2.sol
,如下所示,因为这将在 Forge 跟踪中显示解码的日志。
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console2.sol " ;
...
console2. log (someValue);
如果您需要与 Hardhat 兼容,则必须使用标准console.sol
。由于console.sol
中的错误,使用uint256
或int256
类型的日志将无法在 Forge 跟踪中正确解码。
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console.sol " ;
...
console. log (someValue);
请参阅我们的贡献指南。
首先,看看你的问题能否在书中找到答案。
如果答案不存在:
如果您想贡献,或者关注贡献者讨论,您可以使用我们的主电报与我们讨论 Foundry 的发展!
Forge 标准库根据 MIT 或 Apache 2.0 许可证提供。