contracts/lib/forge-std/README.md 8.9 K raw
1
# Forge Standard Library • [![CI status](https://github.com/foundry-rs/forge-std/actions/workflows/ci.yml/badge.svg)](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.