568 lines
13 KiB
Markdown
568 lines
13 KiB
Markdown
# forge-std API Reference
|
|
|
|
Complete reference for the Forge Standard Library.
|
|
|
|
## Overview
|
|
|
|
```solidity
|
|
import {Test, console} from "forge-std/Test.sol";
|
|
import {Script} from "forge-std/Script.sol";
|
|
```
|
|
|
|
`Test` inherits: `StdAssertions`, `StdChains`, `StdCheats`, `StdInvariant`, `StdUtils`
|
|
|
|
## StdAssertions
|
|
|
|
### Boolean
|
|
|
|
```solidity
|
|
assertTrue(bool condition);
|
|
assertTrue(bool condition, string memory err);
|
|
|
|
assertFalse(bool condition);
|
|
assertFalse(bool condition, string memory err);
|
|
```
|
|
|
|
### Equality
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
assertEqCall(address target, bytes memory callDataA, bytes memory callDataB);
|
|
assertEqCall(address targetA, bytes memory callDataA, address targetB, bytes memory callDataB);
|
|
```
|
|
|
|
### Failure
|
|
|
|
```solidity
|
|
fail();
|
|
fail(string memory err);
|
|
bool hasFailed = failed();
|
|
```
|
|
|
|
## StdCheats
|
|
|
|
### Address Creation
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
skip(uint256 seconds); // Move forward
|
|
rewind(uint256 seconds); // Move backward
|
|
|
|
// Examples
|
|
skip(1 days);
|
|
skip(1 hours);
|
|
rewind(30 minutes);
|
|
```
|
|
|
|
### Prank with ETH (hoax)
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
bool forking = isFork();
|
|
|
|
// Modifiers
|
|
function testOnlyLocal() public skipWhenForking { }
|
|
function testOnlyForked() public skipWhenNotForking { }
|
|
```
|
|
|
|
### Gas Metering
|
|
|
|
```solidity
|
|
// Disable gas metering for expensive setup
|
|
modifier noGasMetering;
|
|
|
|
function testExpensiveSetup() public noGasMetering {
|
|
// Gas not counted
|
|
}
|
|
```
|
|
|
|
### Account Destruction
|
|
|
|
```solidity
|
|
destroyAccount(address target, address beneficiary);
|
|
```
|
|
|
|
## StdStorage
|
|
|
|
Dynamic storage slot finding and manipulation.
|
|
|
|
### Setup
|
|
|
|
```solidity
|
|
using stdStorage for StdStorage;
|
|
```
|
|
|
|
### Finding Slots
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
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
|
|
|
|
```solidity
|
|
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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// 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
|
|
|
|
```solidity
|
|
// Batch balance query (uses Multicall3)
|
|
uint256[] memory balances = getTokenBalances(address token, address[] memory addresses);
|
|
```
|
|
|
|
### Byte Conversion
|
|
|
|
```solidity
|
|
uint256 value = bytesToUint(bytes memory b);
|
|
```
|
|
|
|
## StdJson
|
|
|
|
### Reading
|
|
|
|
```solidity
|
|
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
|
|
|
|
```solidity
|
|
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:
|
|
|
|
```solidity
|
|
using stdToml for string;
|
|
|
|
string memory toml = vm.readFile("config.toml");
|
|
uint256 runs = toml.readUint(".profile.default.fuzz_runs");
|
|
```
|
|
|
|
## StdChains
|
|
|
|
Access chain configuration:
|
|
|
|
```solidity
|
|
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:
|
|
|
|
```solidity
|
|
// 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:
|
|
|
|
```solidity
|
|
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
|
|
|
|
```solidity
|
|
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
|
|
|
|
```solidity
|
|
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:
|
|
|
|
```solidity
|
|
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
|
|
|
|
```solidity
|
|
// 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;
|
|
```
|