Forge Standard Library is a collection of helpful contracts and libraries for use with Forge 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-stdThis is a helper contract for errors and reverts. In Forge, this contract is particularly helpful for the expectRevert 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 the record and accesses cheatcodes. It can always find and write the storage slot(s) associated with a particular variable without knowing the storage layout. By default, writing to packed storage variables is not supported and will throw an error. However, you can enable packed slot support by calling enable_packed_slots() before using find() or checked_write().
This works by recording all SLOADs and SSTOREs 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 a depth parameter). If the variable is a struct, you can pass in a depth 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 {
// Let's 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 {
// Let's 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 bytes; iterating 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;
}
uint256public 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() publicviewreturns (bytes32t) {
// an extremely hidden storage slotbytes32 slot =keccak256("my.random.var");
assembly {
t :=sload(slot)
}
}
}This is a wrapper around miscellaneous cheatcodes that need wrappers to be more dev-friendly. It includes functions for pranking, dealing with ETH and tokens, deploying contracts, creating test addresses, time manipulation, and fuzzing helpers. In general, users may expect ETH to be put into an address with prank, but this is not the case for safety reasons. Explicitly, this hoax 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 use prank. If you want to change that balance explicitly, just use deal. If you want to do both, hoax is also right for you.
// SPDX-License-Identifier: MIT OR Apache-2.0pragma 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");
}
}Provides comprehensive assertion functions for testing, including equality checks (assertEq, assertNotEq), comparisons (assertLt, assertGt, assertLe, assertGe), approximate equality (assertApproxEqAbs, assertApproxEqRel), and boolean assertions (assertTrue, assertFalse). All assertions support multiple data types and optional custom error messages.
This is a contract that parses a TOML configuration file and loads its variables into storage, automatically casting them on deployment. It assumes a TOML structure where top-level keys represent chain IDs or aliases. Under each chain key, variables are organized by type in separate sub-tables like [<chain>.<type>], where type must be: bool, address, bytes32, uint, int, string, or bytes.
// SPDX-License-Identifier: MIT OR Apache-2.0pragma solidity^0.8.13;
import"forge-std/Script.sol";
import"forge-std/StdConfig.sol";
contractMyScriptisScript {
StdConfig config;
function run() public {
// Load config (set writeToFile=true only in scripts to persist changes)
config =newStdConfig("config.toml", false);
// Get values for the current chainuint256 myNumber = config.get("important_number").toUint256();
address weth = config.get("weth").toAddress();
address[] memory admins = config.get("whitelisted_admins").toAddressArray();
// Get values for a specific chainbool isLive = config.get(1, "is_live").toBool();
// Check if a key existsif (config.exists("optional_param")) {
// ...
}
// Get RPC URL for current or specific chainstringmemory rpc = config.getRpcUrl();
stringmemory mainnetRpc = config.getRpcUrl(1);
// Get all configured chain IDsuint256[] memory chainIds = config.getChainIds();
}
}See the contract itself for supported TOML format and all available methods.
Usage follows the same format as Hardhat.
It's recommended to use console2.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 standard console.sol instead.
Due to a bug in console.sol, logs that use uint256 or int256 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 our contributing guidelines.
First, see if the answer to your question can be found in book.
If the answer is not there:
- Join the support Telegram to get help, or
- Open a discussion with your question, or
- Open an issue with the bug
If you want to contribute, or follow along with contributor discussion, you can use our main telegram to chat with us about the development of Foundry!
Forge Standard Library is offered under either MIT or Apache 2.0 license.