- Notifications
You must be signed in to change notification settings - Fork367
Forge Standard Library is a collection of helpful contracts for use with forge and foundry. It leverages forge's cheatcodes to make writing tests easier and faster, while improving the UX of cheatcodes. For more in-depth usage examples checkout the tests.
License
Apache-2.0, MIT licenses found
Licenses found
foundry-rs/forge-std
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Forge Standard Library is a collection of helpful contracts and libraries for use withForge and Foundry. It leverages Forge's cheatcodes to make writing tests easier and faster, while improving the UX of cheatcodes.
Learn how to use Forge-Std with the📖 Foundry Book (Forge-Std Guide).
forge install foundry-rs/forge-std
This is a helper contract for errors and reverts. In Forge, this contract is particularly helpful for theexpectRevert
cheatcode, as it provides all compiler built-in errors.
See the contract itself for all error codes.
import"forge-std/Test.sol";contractTestContractisTest { ErrorsTest test;function setUp()public { test=newErrorsTest(); }function testExpectArithmetic()public { vm.expectRevert(stdError.arithmeticError); test.arithmeticError(10); }}contractErrorsTest {function arithmeticError(uint256a)public { a= a-100; }}
This is a rather large contract due to all of the overloading to make the UX decent. Primarily, it is a wrapper around therecord
andaccesses
cheatcodes. It canalways find and write the storage slot(s) associated with a particular variable without knowing the storage layout. The onemajor caveat to this is while a slot can be found for packed storage variables, we can't write to that variable safely. If a user tries to write to a packed slot, the execution throws an error, unless it is uninitialized (bytes32(0)
).
This works by recording allSLOAD
s andSSTORE
s during a function call. If there is a single slot read or written to, it immediately returns the slot. Otherwise, behind the scenes, we iterate through and check each one (assuming the user passed in adepth
parameter). If the variable is a struct, you can pass in adepth
parameter which is basically the field depth.
I.e.:
struct T {// depth 0uint256 a;// depth 1uint256 b;}
import"forge-std/Test.sol";contractTestContractisTest {using stdStoragefor StdStorage; Storage test;function setUp()public { test=newStorage(); }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` commanduint256 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 locationsfunction 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 stringuint256 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 1assertEq(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 contractcontractStorage {struct UnpackedStruct {uint256 a;uint256 b; }constructor() { map_addr[msg.sender]=1; }uint256publicexists=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; UnpackedStructpublic basicStruct=UnpackedStruct({ a:1, b:2 });function hidden()publicviewreturns (bytes32t) {// an extremely hidden storage slotbytes32 slot=keccak256("my.random.var");assembly { t:=sload(slot) } }}
This is a wrapper over miscellaneous cheatcodes that need wrappers to be more dev friendly. Currently there are only functions related toprank
. In general, users may expect ETH to be put into an address onprank
, but this is not the case for safety reasons. Explicitly thishoax
function should only be used for addresses that have expected balances as it will get overwritten. If an address already has ETH, you should just useprank
. If you want to change that balance explicitly, just usedeal
. If you want to do both,hoax
is also right for you.
// SPDX-License-Identifier: MITpragma solidity^0.8.0;import"forge-std/Test.sol";// Inherit the stdCheatscontractStdCheatsTestisTest { Bar test;function setUp()public { test=newBar(); }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 withhoax(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 amountstartHoax(address(1337)); test.bar{value:100}(address(1337)); test.bar{value:100}(address(1337)); vm.stopPrank(); test.bar(address(this)); }}contractBar {function bar(addressexpectedSender)publicpayable {require(msg.sender== expectedSender,"!prank"); }}
Contains various assertions.
Usage follows the same format asHardhat.It's recommended to useconsole2.sol
as shown below, as this will show the decoded logs in Forge traces.
// import it indirectly via Test.solimport"forge-std/Test.sol";// or directly import itimport"forge-std/console2.sol";...console2.log(someValue);
If you need compatibility with Hardhat, you must use the standardconsole.sol
instead.Due to a bug inconsole.sol
, logs that useuint256
orint256
types will not be properly decoded in Forge traces.
// import it indirectly via Test.solimport"forge-std/Test.sol";// or directly import itimport"forge-std/console.sol";...console.log(someValue);
See ourcontributing guidelines.
First, see if the answer to your question can be found inbook.
If the answer is not there:
- Join thesupport Telegram to get help, or
- Open adiscussion with your question, or
- Open an issue withthe bug
If you want to contribute, or follow along with contributor discussion, you can use ourmain telegram to chat with us about the development of Foundry!
Forge Standard Library is offered under eitherMIT orApache 2.0 license.
About
Forge Standard Library is a collection of helpful contracts for use with forge and foundry. It leverages forge's cheatcodes to make writing tests easier and faster, while improving the UX of cheatcodes. For more in-depth usage examples checkout the tests.