13 KiB
13 KiB
forge-std API Reference
Complete reference for the Forge Standard Library.
Overview
import {Test, console} from "forge-std/Test.sol";
import {Script} from "forge-std/Script.sol";
Test inherits: StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils
StdAssertions
Boolean
assertTrue(bool condition);
assertTrue(bool condition, string memory err);
assertFalse(bool condition);
assertFalse(bool condition, string memory err);
Equality
// Works with: bool, uint256, int256, address, bytes32, string, bytes
assertEq(T left, T right);
assertEq(T left, T right, string memory err);
assertNotEq(T left, T right);
assertNotEq(T left, T right, string memory err);
// Arrays
assertEq(T[] memory left, T[] memory right);
Comparison
// Works with: uint256, int256
assertLt(T left, T right); // <
assertLt(T left, T right, string memory err);
assertGt(T left, T right); // >
assertGt(T left, T right, string memory err);
assertLe(T left, T right); // <=
assertLe(T left, T right, string memory err);
assertGe(T left, T right); // >=
assertGe(T left, T right, string memory err);
Decimal Formatting
// Shows values with decimal places in error messages
assertEqDecimal(uint256 left, uint256 right, uint256 decimals);
assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals);
assertLtDecimal(uint256 left, uint256 right, uint256 decimals);
assertGtDecimal(uint256 left, uint256 right, uint256 decimals);
assertLeDecimal(uint256 left, uint256 right, uint256 decimals);
assertGeDecimal(uint256 left, uint256 right, uint256 decimals);
// Example
assertEqDecimal(1e18, 1e18, 18); // Shows "1.0" not "1000000000000000000"
Approximation
// Absolute difference
assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta);
assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string memory err);
// Relative difference (maxPercentDelta: 1e18 = 100%)
assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta);
assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string memory err);
// Examples
assertApproxEqAbs(1000, 1005, 10); // Pass: |1000-1005| <= 10
assertApproxEqRel(100, 101, 0.02e18); // Pass: 1% diff <= 2%
Call Comparison
assertEqCall(address target, bytes memory callDataA, bytes memory callDataB);
assertEqCall(address targetA, bytes memory callDataA, address targetB, bytes memory callDataB);
Failure
fail();
fail(string memory err);
bool hasFailed = failed();
StdCheats
Address Creation
// Create labeled address
address alice = makeAddr("alice");
// Create address with private key
(address bob, uint256 bobKey) = makeAddrAndKey("bob");
// Create account struct
Account memory account = makeAccount("charlie");
// account.addr, account.key
Account Setup
// ETH
deal(address to, uint256 amount);
// ERC20
deal(address token, address to, uint256 amount);
deal(address token, address to, uint256 amount, bool adjustTotalSupply);
// ERC721
dealERC721(address token, address to, uint256 tokenId);
// ERC1155
dealERC1155(address token, address to, uint256 id, uint256 amount);
dealERC1155(address token, address to, uint256 id, uint256 amount, bool adjustTotalSupply);
Time Manipulation
skip(uint256 seconds); // Move forward
rewind(uint256 seconds); // Move backward
// Examples
skip(1 days);
skip(1 hours);
rewind(30 minutes);
Prank with ETH (hoax)
// Single call as sender with ETH
hoax(address sender);
hoax(address sender, uint256 give);
hoax(address sender, address origin);
hoax(address sender, address origin, uint256 give);
// Multiple calls
startHoax(address sender);
startHoax(address sender, uint256 give);
// ... calls ...
vm.stopPrank();
// Example
hoax(alice, 10 ether);
vault.deposit{value: 1 ether}();
Code Deployment
// Deploy from artifacts
address deployed = deployCode("ContractName.sol");
address deployed = deployCode("ContractName.sol:ContractName");
address deployed = deployCode("ContractName.sol", constructorArgs);
address deployed = deployCode("ContractName.sol", constructorArgs, value);
// Deploy to specific address
deployCodeTo("ContractName.sol", targetAddress);
deployCodeTo("ContractName.sol", constructorArgs, targetAddress);
Assumptions
// Address type checks
assumeNotZeroAddress(address addr);
assumeNotPrecompile(address addr);
assumeNotPrecompile(address addr, uint256 chainId);
assumeNotForgeAddress(address addr);
assumePayable(address addr);
assumeNotPayable(address addr);
// Token blacklists
assumeNotBlacklisted(address token, address addr);
// Combined checks
assumeAddressIsNot(address addr, AddressType t);
assumeAddressIsNot(address addr, AddressType t1, AddressType t2);
// AddressType enum: ZeroAddress, Precompile, ForgeAddress
Fork Detection
bool forking = isFork();
// Modifiers
function testOnlyLocal() public skipWhenForking { }
function testOnlyForked() public skipWhenNotForking { }
Gas Metering
// Disable gas metering for expensive setup
modifier noGasMetering;
function testExpensiveSetup() public noGasMetering {
// Gas not counted
}
Account Destruction
destroyAccount(address target, address beneficiary);
StdStorage
Dynamic storage slot finding and manipulation.
Setup
using stdStorage for StdStorage;
Finding Slots
// Simple variable
uint256 slot = stdstore
.target(address(contract))
.sig("variableName()")
.find();
// Mapping
uint256 slot = stdstore
.target(address(contract))
.sig("balances(address)")
.with_key(user)
.find();
// Nested mapping
uint256 slot = stdstore
.target(address(contract))
.sig("allowances(address,address)")
.with_key(owner)
.with_key(spender)
.find();
// Struct field
uint256 slot = stdstore
.target(address(contract))
.sig("structVar()")
.depth(0) // Field index
.find();
Writing Values
stdstore
.target(address(contract))
.sig("balances(address)")
.with_key(user)
.checked_write(1000e18);
// For int256
stdstore
.target(address(contract))
.sig("delta()")
.checked_write_int(-100);
Example
function testSetBalance() public {
// Set alice's balance to 1000 tokens
stdstore
.target(address(token))
.sig("balanceOf(address)")
.with_key(alice)
.checked_write(1000e18);
assertEq(token.balanceOf(alice), 1000e18);
}
StdUtils
Bounded Randomness
// Constrain fuzz input to range
uint256 bounded = bound(uint256 x, uint256 min, uint256 max);
int256 bounded = bound(int256 x, int256 min, int256 max);
// Constrain to valid private key range
uint256 key = boundPrivateKey(uint256 pk);
// Example
function testFuzz(uint256 amount) public {
amount = bound(amount, 1, 1000);
// amount is now in [1, 1000]
}
Address Computation
// CREATE address
address addr = computeCreateAddress(address deployer, uint256 nonce);
// CREATE2 address
address addr = computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer);
address addr = computeCreate2Address(bytes32 salt, bytes32 initCodeHash); // Uses CREATE2_FACTORY
// Init code hash
bytes32 hash = hashInitCode(bytes memory creationCode);
bytes32 hash = hashInitCode(bytes memory creationCode, bytes memory args);
// Example
bytes32 initHash = hashInitCode(type(MyContract).creationCode);
address predicted = computeCreate2Address(salt, initHash, factory);
Token Utilities
// Batch balance query (uses Multicall3)
uint256[] memory balances = getTokenBalances(address token, address[] memory addresses);
Byte Conversion
uint256 value = bytesToUint(bytes memory b);
StdJson
Reading
using stdJson for string;
string memory json = vm.readFile("data.json");
// Single values
uint256 amount = json.readUint(".amount");
int256 balance = json.readInt(".balance");
address addr = json.readAddress(".recipient");
bytes32 hash = json.readBytes32(".hash");
string memory name = json.readString(".name");
bytes memory data = json.readBytes(".data");
bool flag = json.readBool(".enabled");
// Arrays
uint256[] memory amounts = json.readUintArray(".amounts");
address[] memory addrs = json.readAddressArray(".addresses");
string[] memory names = json.readStringArray(".names");
// With defaults
uint256 amount = json.readUintOr(".amount", 100);
address addr = json.readAddressOr(".recipient", address(0));
// Check existence
bool exists = json.keyExists(".key");
// Raw bytes
bytes memory raw = json.parseRaw(".data");
Writing
using stdJson for string;
string memory json = "obj";
json = json.serialize("amount", uint256(100));
json = json.serialize("recipient", address(0x123));
json = json.serialize("enabled", true);
json = json.serialize("amounts", amounts);
json.write("output.json");
json.write("output.json", ".config");
StdToml
Identical API to StdJson:
using stdToml for string;
string memory toml = vm.readFile("config.toml");
uint256 runs = toml.readUint(".profile.default.fuzz_runs");
StdChains
Access chain configuration:
Chain memory chain = getChain("mainnet");
// chain.name, chain.chainId, chain.rpcUrl
Chain memory chain = getChain(1); // By chain ID
// Set custom RPC
setChain("custom", ChainData({
name: "Custom Chain",
chainId: 12345,
rpcUrl: "https://..."
}));
StdInvariant
For invariant testing targets:
// Target contracts for fuzzing
targetContract(address);
targetContracts(); // Returns address[]
// Exclude from fuzzing
excludeContract(address);
excludeContracts(); // Returns address[]
// Target senders
targetSender(address);
targetSenders(); // Returns address[]
// Exclude senders
excludeSender(address);
excludeSenders(); // Returns address[]
// Target specific selectors
targetSelector(FuzzSelector memory);
targetSelectors(); // Returns FuzzSelector[]
// Target artifacts (deploy and fuzz)
targetArtifact(string memory);
targetArtifacts(); // Returns string[]
// Target artifact selectors
targetArtifactSelector(FuzzArtifactSelector memory);
targetArtifactSelectors(); // Returns FuzzArtifactSelector[]
StdError
Common error selectors:
import {stdError} from "forge-std/StdError.sol";
vm.expectRevert(stdError.arithmeticError); // Overflow/underflow
vm.expectRevert(stdError.assertionError); // assert() failed
vm.expectRevert(stdError.divisionError); // Division by zero
vm.expectRevert(stdError.encodeStorageError); // Storage encoding
vm.expectRevert(stdError.enumConversionError); // Invalid enum
vm.expectRevert(stdError.indexOOBError); // Array index out of bounds
vm.expectRevert(stdError.memOverflowError); // Memory overflow
vm.expectRevert(stdError.popEmptyArrayError); // Pop empty array
vm.expectRevert(stdError.zeroVarError); // Zero-initialized function pointer
StdMath
import {stdMath} from "forge-std/StdMath.sol";
uint256 absolute = stdMath.abs(int256 x);
uint256 delta = stdMath.delta(uint256 a, uint256 b);
uint256 delta = stdMath.delta(int256 a, int256 b);
uint256 percent = stdMath.percentDelta(uint256 a, uint256 b);
Console Logging
import {console} from "forge-std/console.sol";
// or
import {console2} from "forge-std/console2.sol"; // Smaller bytecode
console.log("message");
console.log("value:", value);
console.log("a:", a, "b:", b);
// Type-specific
console.log(uint256 x);
console.log(int256 x);
console.log(address x);
console.log(bool x);
console.log(string memory x);
console.log(bytes memory x);
console.log(bytes32 x);
// Formatted
console.logBytes(bytes memory);
console.logBytes1(bytes1);
// ... up to logBytes32
Script.sol
Base for deployment scripts:
import {Script, console} from "forge-std/Script.sol";
contract DeployScript is Script {
function setUp() public {}
function run() public {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerKey);
MyContract c = new MyContract();
c.initialize();
vm.stopBroadcast();
console.log("Deployed:", address(c));
}
}
Script vs Test
| Feature | Test | Script |
|---|---|---|
| Base | Test |
Script |
| Cheats | Full StdCheats |
StdCheatsSafe |
| Purpose | Testing | Deployment |
| Broadcast | No | Yes |
| State changes | Local | On-chain |
Constants
// From CommonBase
address constant VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
address constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67;
address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
address constant DEFAULT_SENDER = 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38;
address constant DEFAULT_TEST_CONTRACT = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f;
address constant MULTICALL3_ADDRESS = 0xcA11bde05977b3631167028862bE2a173976CA11;