| 1 | # Forge Standard Library • [](https://github.com/foundry-rs/forge-std/actions/workflows/ci.yml) |
| 2 | |
| 3 | Forge Standard Library is a collection of helpful contracts and libraries for use with [Forge and Foundry](https://github.com/foundry-rs/foundry). It leverages Forge's cheatcodes to make writing tests easier and faster, while improving the UX of cheatcodes. |
| 4 | |
| 5 | **Learn how to use Forge-Std with the [📖 Foundry Book (Forge-Std Guide)](https://getfoundry.sh/reference/forge-std/overview/).** |
| 6 | |
| 7 | ## Install |
| 8 | |
| 9 | ```bash |
| 10 | forge install foundry-rs/forge-std |
| 11 | ``` |
| 12 | |
| 13 | ## Contracts |
| 14 | ### stdError |
| 15 | |
| 16 | This 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. |
| 17 | |
| 18 | See the contract itself for all error codes. |
| 19 | |
| 20 | #### Example usage |
| 21 | |
| 22 | ```solidity |
| 23 | |
| 24 | import "forge-std/Test.sol"; |
| 25 | |
| 26 | contract TestContract is Test { |
| 27 | ErrorsTest test; |
| 28 | |
| 29 | function setUp() public { |
| 30 | test = new ErrorsTest(); |
| 31 | } |
| 32 | |
| 33 | function testExpectArithmetic() public { |
| 34 | vm.expectRevert(stdError.arithmeticError); |
| 35 | test.arithmeticError(10); |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | contract ErrorsTest { |
| 40 | function arithmeticError(uint256 a) public { |
| 41 | a = a - 100; |
| 42 | } |
| 43 | } |
| 44 | ``` |
| 45 | |
| 46 | ### stdStorage |
| 47 | |
| 48 | 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. The one _major_ 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)`). |
| 49 | |
| 50 | This works by recording all `SLOAD`s and `SSTORE`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 a `depth` parameter). If the variable is a struct, you can pass in a `depth` parameter which is basically the field depth. |
| 51 | |
| 52 | I.e.: |
| 53 | ```solidity |
| 54 | struct T { |
| 55 | // depth 0 |
| 56 | uint256 a; |
| 57 | // depth 1 |
| 58 | uint256 b; |
| 59 | } |
| 60 | ``` |
| 61 | |
| 62 | #### Example usage |
| 63 | |
| 64 | ```solidity |
| 65 | import "forge-std/Test.sol"; |
| 66 | |
| 67 | contract TestContract is Test { |
| 68 | using stdStorage for StdStorage; |
| 69 | |
| 70 | Storage test; |
| 71 | |
| 72 | function setUp() public { |
| 73 | test = new Storage(); |
| 74 | } |
| 75 | |
| 76 | function testFindExists() public { |
| 77 | // Lets say we want to find the slot for the public |
| 78 | // variable `exists`. We just pass in the function selector |
| 79 | // to the `find` command |
| 80 | uint256 slot = stdstore.target(address(test)).sig("exists()").find(); |
| 81 | assertEq(slot, 0); |
| 82 | } |
| 83 | |
| 84 | function testWriteExists() public { |
| 85 | // Lets say we want to write to the slot for the public |
| 86 | // variable `exists`. We just pass in the function selector |
| 87 | // to the `checked_write` command |
| 88 | stdstore.target(address(test)).sig("exists()").checked_write(100); |
| 89 | assertEq(test.exists(), 100); |
| 90 | } |
| 91 | |
| 92 | // It supports arbitrary storage layouts, like assembly based storage locations |
| 93 | function testFindHidden() public { |
| 94 | // `hidden` is a random hash of a bytes, iteration through slots would |
| 95 | // not find it. Our mechanism does |
| 96 | // Also, you can use the selector instead of a string |
| 97 | uint256 slot = stdstore.target(address(test)).sig(test.hidden.selector).find(); |
| 98 | assertEq(slot, uint256(keccak256("my.random.var"))); |
| 99 | } |
| 100 | |
| 101 | // If targeting a mapping, you have to pass in the keys necessary to perform the find |
| 102 | // i.e.: |
| 103 | function testFindMapping() public { |
| 104 | uint256 slot = stdstore |
| 105 | .target(address(test)) |
| 106 | .sig(test.map_addr.selector) |
| 107 | .with_key(address(this)) |
| 108 | .find(); |
| 109 | // in the `Storage` constructor, we wrote that this address' value was 1 in the map |
| 110 | // so when we load the slot, we expect it to be 1 |
| 111 | assertEq(uint(vm.load(address(test), bytes32(slot))), 1); |
| 112 | } |
| 113 | |
| 114 | // If the target is a struct, you can specify the field depth: |
| 115 | function testFindStruct() public { |
| 116 | // NOTE: see the depth parameter - 0 means 0th field, 1 means 1st field, etc. |
| 117 | uint256 slot_for_a_field = stdstore |
| 118 | .target(address(test)) |
| 119 | .sig(test.basicStruct.selector) |
| 120 | .depth(0) |
| 121 | .find(); |
| 122 | |
| 123 | uint256 slot_for_b_field = stdstore |
| 124 | .target(address(test)) |
| 125 | .sig(test.basicStruct.selector) |
| 126 | .depth(1) |
| 127 | .find(); |
| 128 | |
| 129 | assertEq(uint(vm.load(address(test), bytes32(slot_for_a_field))), 1); |
| 130 | assertEq(uint(vm.load(address(test), bytes32(slot_for_b_field))), 2); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | // A complex storage contract |
| 135 | contract Storage { |
| 136 | struct UnpackedStruct { |
| 137 | uint256 a; |
| 138 | uint256 b; |
| 139 | } |
| 140 | |
| 141 | constructor() { |
| 142 | map_addr[msg.sender] = 1; |
| 143 | } |
| 144 | |
| 145 | uint256 public exists = 1; |
| 146 | mapping(address => uint256) public map_addr; |
| 147 | // mapping(address => Packed) public map_packed; |
| 148 | mapping(address => UnpackedStruct) public map_struct; |
| 149 | mapping(address => mapping(address => uint256)) public deep_map; |
| 150 | mapping(address => mapping(address => UnpackedStruct)) public deep_map_struct; |
| 151 | UnpackedStruct public basicStruct = UnpackedStruct({ |
| 152 | a: 1, |
| 153 | b: 2 |
| 154 | }); |
| 155 | |
| 156 | function hidden() public view returns (bytes32 t) { |
| 157 | // an extremely hidden storage slot |
| 158 | bytes32 slot = keccak256("my.random.var"); |
| 159 | assembly { |
| 160 | t := sload(slot) |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | ``` |
| 165 | |
| 166 | ### stdCheats |
| 167 | |
| 168 | This is a wrapper over miscellaneous cheatcodes that need wrappers to be more dev friendly. Currently there are only functions related to `prank`. In general, users may expect ETH to be put into an address on `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. |
| 169 | |
| 170 | |
| 171 | #### Example usage: |
| 172 | ```solidity |
| 173 | |
| 174 | // SPDX-License-Identifier: MIT |
| 175 | pragma solidity ^0.8.0; |
| 176 | |
| 177 | import "forge-std/Test.sol"; |
| 178 | |
| 179 | // Inherit the stdCheats |
| 180 | contract StdCheatsTest is Test { |
| 181 | Bar test; |
| 182 | function setUp() public { |
| 183 | test = new Bar(); |
| 184 | } |
| 185 | |
| 186 | function testHoax() public { |
| 187 | // we call `hoax`, which gives the target address |
| 188 | // eth and then calls `prank` |
| 189 | hoax(address(1337)); |
| 190 | test.bar{value: 100}(address(1337)); |
| 191 | |
| 192 | // overloaded to allow you to specify how much eth to |
| 193 | // initialize the address with |
| 194 | hoax(address(1337), 1); |
| 195 | test.bar{value: 1}(address(1337)); |
| 196 | } |
| 197 | |
| 198 | function testStartHoax() public { |
| 199 | // we call `startHoax`, which gives the target address |
| 200 | // eth and then calls `startPrank` |
| 201 | // |
| 202 | // it is also overloaded so that you can specify an eth amount |
| 203 | startHoax(address(1337)); |
| 204 | test.bar{value: 100}(address(1337)); |
| 205 | test.bar{value: 100}(address(1337)); |
| 206 | vm.stopPrank(); |
| 207 | test.bar(address(this)); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | contract Bar { |
| 212 | function bar(address expectedSender) public payable { |
| 213 | require(msg.sender == expectedSender, "!prank"); |
| 214 | } |
| 215 | } |
| 216 | ``` |
| 217 | |
| 218 | ### Std Assertions |
| 219 | |
| 220 | Contains various assertions. |
| 221 | |
| 222 | ### `console.log` |
| 223 | |
| 224 | Usage follows the same format as [Hardhat](https://hardhat.org/hardhat-network/reference/#console-log). |
| 225 | It's recommended to use `console2.sol` as shown below, as this will show the decoded logs in Forge traces. |
| 226 | |
| 227 | ```solidity |
| 228 | // import it indirectly via Test.sol |
| 229 | import "forge-std/Test.sol"; |
| 230 | // or directly import it |
| 231 | import "forge-std/console2.sol"; |
| 232 | ... |
| 233 | console2.log(someValue); |
| 234 | ``` |
| 235 | |
| 236 | If you need compatibility with Hardhat, you must use the standard `console.sol` instead. |
| 237 | Due to a bug in `console.sol`, logs that use `uint256` or `int256` types will not be properly decoded in Forge traces. |
| 238 | |
| 239 | ```solidity |
| 240 | // import it indirectly via Test.sol |
| 241 | import "forge-std/Test.sol"; |
| 242 | // or directly import it |
| 243 | import "forge-std/console.sol"; |
| 244 | ... |
| 245 | console.log(someValue); |
| 246 | ``` |
| 247 | |
| 248 | ## Contributing |
| 249 | |
| 250 | See our [contributing guidelines](./CONTRIBUTING.md). |
| 251 | |
| 252 | ## Getting Help |
| 253 | |
| 254 | First, see if the answer to your question can be found in [book](https://book.getfoundry.sh). |
| 255 | |
| 256 | If the answer is not there: |
| 257 | |
| 258 | - Join the [support Telegram](https://t.me/foundry_support) to get help, or |
| 259 | - Open a [discussion](https://github.com/foundry-rs/foundry/discussions/new/choose) with your question, or |
| 260 | - Open an issue with [the bug](https://github.com/foundry-rs/foundry/issues/new/choose) |
| 261 | |
| 262 | If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/foundry_rs) to chat with us about the development of Foundry! |
| 263 | |
| 264 | ## License |
| 265 | |
| 266 | Forge Standard Library is offered under either [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) license. |