Skip to content

Commit 35b2bc0

Browse files
authored
Feat: add serialize (vm.writeJson) to stdjson (#213)
* chore: rebase * fix: grammar nit| * chore: forge fmt * fix: add cheatcode + comments to VM interface * fix: rebase conflicts * fix: rebase conflicts * fix: backward compat * chore: forge fmt * fix: update comments * chore: add comments to stdJson * fix: change comments to reflect improvmenets
1 parent 068f042 commit 35b2bc0

File tree

4 files changed

+449
-11
lines changed

4 files changed

+449
-11
lines changed

\

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.6.2 <0.9.0;
3+
4+
pragma experimental ABIEncoderV2;
5+
6+
// Cheatcodes are marked as view/pure/none using the following rules:
7+
// 0. A call's observable behaviour includes its return value, logs, reverts and state writes.
8+
// 1. If you can influence a later call's observable behaviour, you're neither `view` nor `pure` (you are modifying some state be it the EVM, interpreter, filesystem, etc),
9+
// 2. Otherwise if you can be influenced by an earlier call, or if reading some state, you're `view`,
10+
// 3. Otherwise you're `pure`.
11+
12+
interface VmSafe {
13+
struct Log {
14+
bytes32[] topics;
15+
bytes data;
16+
}
17+
18+
// Loads a storage slot from an address (who, slot)
19+
function load(address, bytes32) external view returns (bytes32);
20+
// Signs data, (privateKey, digest) => (v, r, s)
21+
function sign(uint256, bytes32) external pure returns (uint8, bytes32, bytes32);
22+
// Gets the address for a given private key, (privateKey) => (address)
23+
function addr(uint256) external pure returns (address);
24+
// Gets the nonce of an account
25+
function getNonce(address) external view returns (uint64);
26+
// Performs a foreign function call via the terminal, (stringInputs) => (result)
27+
function ffi(string[] calldata) external returns (bytes memory);
28+
// Sets environment variables, (name, value)
29+
function setEnv(string calldata, string calldata) external;
30+
// Reads environment variables, (name) => (value)
31+
function envBool(string calldata) external view returns (bool);
32+
function envUint(string calldata) external view returns (uint256);
33+
function envInt(string calldata) external view returns (int256);
34+
function envAddress(string calldata) external view returns (address);
35+
function envBytes32(string calldata) external view returns (bytes32);
36+
function envString(string calldata) external view returns (string memory);
37+
function envBytes(string calldata) external view returns (bytes memory);
38+
// Reads environment variables as arrays, (name, delim) => (value[])
39+
function envBool(string calldata, string calldata) external view returns (bool[] memory);
40+
function envUint(string calldata, string calldata) external view returns (uint256[] memory);
41+
function envInt(string calldata, string calldata) external view returns (int256[] memory);
42+
function envAddress(string calldata, string calldata) external view returns (address[] memory);
43+
function envBytes32(string calldata, string calldata) external view returns (bytes32[] memory);
44+
function envString(string calldata, string calldata) external view returns (string[] memory);
45+
function envBytes(string calldata, string calldata) external view returns (bytes[] memory);
46+
// Records all storage reads and writes
47+
function record() external;
48+
// Gets all accessed reads and write slot from a recording session, for a given address
49+
function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes);
50+
// Gets the _creation_ bytecode from an artifact file. Takes in the relative path to the json file
51+
function getCode(string calldata) external view returns (bytes memory);
52+
// Gets the _deployed_ bytecode from an artifact file. Takes in the relative path to the json file
53+
function getDeployedCode(string calldata) external view returns (bytes memory);
54+
// Labels an address in call traces
55+
function label(address, string calldata) external;
56+
// Using the address that calls the test contract, has the next call (at this call depth only) create a transaction that can later be signed and sent onchain
57+
function broadcast() external;
58+
// Has the next call (at this call depth only) create a transaction with the address provided as the sender that can later be signed and sent onchain
59+
function broadcast(address) external;
60+
// Has the next call (at this call depth only) create a transaction with the private key provided as the sender that can later be signed and sent onchain
61+
function broadcast(uint256) external;
62+
// Using the address that calls the test contract, has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain
63+
function startBroadcast() external;
64+
// Has all subsequent calls (at this call depth only) create transactions with the address provided that can later be signed and sent onchain
65+
function startBroadcast(address) external;
66+
// Has all subsequent calls (at this call depth only) create transactions with the private key provided that can later be signed and sent onchain
67+
function startBroadcast(uint256) external;
68+
// Stops collecting onchain transactions
69+
function stopBroadcast() external;
70+
// Reads the entire content of file to string, (path) => (data)
71+
function readFile(string calldata) external view returns (string memory);
72+
// Reads the entire content of file as binary. Path is relative to the project root. (path) => (data)
73+
function readFileBinary(string calldata) external view returns (bytes memory);
74+
// Get the path of the current project root
75+
function projectRoot() external view returns (string memory);
76+
// Reads next line of file to string, (path) => (line)
77+
function readLine(string calldata) external view returns (string memory);
78+
// Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does.
79+
// (path, data) => ()
80+
function writeFile(string calldata, string calldata) external;
81+
// Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does.
82+
// Path is relative to the project root. (path, data) => ()
83+
function writeFileBinary(string calldata, bytes calldata) external;
84+
// Writes line to file, creating a file if it does not exist.
85+
// (path, data) => ()
86+
function writeLine(string calldata, string calldata) external;
87+
// Closes file for reading, resetting the offset and allowing to read it from beginning with readLine.
88+
// (path) => ()
89+
function closeFile(string calldata) external;
90+
// Removes file. This cheatcode will revert in the following situations, but is not limited to just these cases:
91+
// - Path points to a directory.
92+
// - The file doesn't exist.
93+
// - The user lacks permissions to remove the file.
94+
// (path) => ()
95+
function removeFile(string calldata) external;
96+
// Convert values to a string, (value) => (stringified value)
97+
function toString(address) external pure returns (string memory);
98+
function toString(bytes calldata) external pure returns (string memory);
99+
function toString(bytes32) external pure returns (string memory);
100+
function toString(bool) external pure returns (string memory);
101+
function toString(uint256) external pure returns (string memory);
102+
function toString(int256) external pure returns (string memory);
103+
// Convert values from a string, (string) => (parsed value)
104+
function parseBytes(string calldata) external pure returns (bytes memory);
105+
function parseAddress(string calldata) external pure returns (address);
106+
function parseUint(string calldata) external pure returns (uint256);
107+
function parseInt(string calldata) external pure returns (int256);
108+
function parseBytes32(string calldata) external pure returns (bytes32);
109+
function parseBool(string calldata) external pure returns (bool);
110+
// Record all the transaction logs
111+
function recordLogs() external;
112+
// Gets all the recorded logs, () => (logs)
113+
function getRecordedLogs() external returns (Log[] memory);
114+
// Derive a private key from a provided mnenomic string (or mnenomic file path) at the derivation path m/44'/60'/0'/0/{index}
115+
function deriveKey(string calldata, uint32) external pure returns (uint256);
116+
// Derive a private key from a provided mnenomic string (or mnenomic file path) at the derivation path {path}{index}
117+
function deriveKey(string calldata, string calldata, uint32) external pure returns (uint256);
118+
// Adds a private key to the local forge wallet and returns the address
119+
function rememberKey(uint256) external returns (address);
120+
//
121+
// parseJson
122+
//
123+
// ----
124+
// In case the returned value is a JSON object, it's encoded as a ABI-encoded tuple. As JSON objects
125+
// don't have the notion of ordered, but tuples do, they JSON object is encoded with it's fields ordered in
126+
// ALPHABETICAL ordser. That means that in order to succesfully decode the tuple, we need to define a tuple that
127+
// encodes the fields in the same order, which is alphabetical. In the case of Solidity structs, they are encoded
128+
// as tuples, with the attributes in the order in which they are defined.
129+
// For example: json = { 'a': 1, 'b': 0xa4tb......3xs}
130+
// a: uint256
131+
// b: address
132+
// To decode that json, we need to define a struct or a tuple as follows:
133+
// struct json = { uint256 a; address b; }
134+
// If we defined a json struct with the opposite order, meaning placing the address b first, it would try to
135+
// decode the tuple in that order, and thus fail.
136+
// ----
137+
// Given a string of JSON, return it as ABI-encoded, (stringified json, key) => (ABI-encoded data)
138+
function parseJson(string calldata, string calldata) external pure returns (bytes memory);
139+
function parseJson(string calldata) external pure returns (bytes memory);
140+
//
141+
// writeJson
142+
//
143+
// ----
144+
// Let's assume we want to write the following JSON to a file:
145+
//
146+
// { "boolean": true, "number": 342, "object": { "title": "finally json serialization" } }
147+
//
148+
// ```
149+
// string memory json1 = "some key";
150+
// vm.serializeBool(json1, "boolean", true);
151+
// vm.serializeBool(json1, "number", uint256(342));
152+
// json2 = "some other key";
153+
// string memory output = vm.serializeString(json2, "title", "finally json serialization");
154+
// vm.serialize(json1, "object", output);
155+
// vm.writeJson(json1, "./output/example.json");
156+
// ```
157+
// The critical insight is that every invocation of serialization will return the stringified version of the JSON
158+
// up to that point. That means we can construct arbitrary JSON objects and then use the return stringified version
159+
// to serialize them as values to another JSON object.
160+
//
161+
// json1 and json2 are simply keys used by the backend to keep track of the objects. So vm.serializeJson(json1,..)
162+
// will find the object in-memory that is keyed by "some key". // writeJson
163+
function serializeBool(string calldata, string calldata, bool) external returns (string memory);
164+
function serializeUint(string calldata, string calldata, uint256) external returns (string memory);
165+
function serializeInt(string calldata, string calldata, int256) external returns (string memory);
166+
function serializeAddress(string calldata, string calldata, address) external returns (string memory);
167+
function serializeBytes32(string calldata, string calldata, bytes32) external returns (string memory);
168+
function serializeString(string calldata, string calldata, string calldata) external returns (string memory);
169+
function serializeBytes(string calldata, string calldata, bytes calldata) external returns (string memory);
170+
171+
function serializeBool(string calldata, string calldata, bool[] calldata) external returns (string memory);
172+
function serializeUint(string calldata, string calldata, uint256[] calldata) external returns (string memory);
173+
function serializeInt(string calldata, string calldata, int256[] calldata) external returns (string memory);
174+
function serializeAddress(string calldata, string calldata, address[] calldata) external returns (string memory);
175+
function serializeBytes32(string calldata, string calldata, bytes32[] calldata) external returns (string memory);
176+
function serializeString(string calldata, string calldata, string[] calldata) external returns (string memory);
177+
function serializeBytes(string calldata, string calldata, bytes[] calldata) external returns (string memory);
178+
function writeJson(string calldata, string calldata) external;
179+
function writeJson(string calldata, string calldata, string calldata) external;
180+
// Returns the RPC url for the given alias
181+
function rpcUrl(string calldata) external view returns (string memory);
182+
// Returns all rpc urls and their aliases `[alias, url][]`
183+
function rpcUrls() external view returns (string[2][] memory);
184+
// If the condition is false, discard this run's fuzz inputs and generate new ones.
185+
function assume(bool) external pure;
186+
}
187+
188+
interface Vm is VmSafe {
189+
// Sets block.timestamp (newTimestamp)
190+
function warp(uint256) external;
191+
// Sets block.height (newHeight)
192+
function roll(uint256) external;
193+
// Sets block.basefee (newBasefee)
194+
function fee(uint256) external;
195+
// Sets block.difficulty (newDifficulty)
196+
function difficulty(uint256) external;
197+
// Sets block.chainid
198+
function chainId(uint256) external;
199+
// Stores a value to an address' storage slot, (who, slot, value)
200+
function store(address, bytes32, bytes32) external;
201+
// Sets the nonce of an account; must be higher than the current nonce of the account
202+
function setNonce(address, uint64) external;
203+
// Sets the *next* call's msg.sender to be the input address
204+
function prank(address) external;
205+
// Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called
206+
function startPrank(address) external;
207+
// Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input
208+
function prank(address, address) external;
209+
// Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input
210+
function startPrank(address, address) external;
211+
// Resets subsequent calls' msg.sender to be `address(this)`
212+
function stopPrank() external;
213+
// Sets an address' balance, (who, newBalance)
214+
function deal(address, uint256) external;
215+
// Sets an address' code, (who, newCode)
216+
function etch(address, bytes calldata) external;
217+
// Expects an error on next call
218+
function expectRevert(bytes calldata) external;
219+
function expectRevert(bytes4) external;
220+
function expectRevert() external;
221+
// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData).
222+
// Call this function, then emit an event, then call a function. Internally after the call, we check if
223+
// logs were emitted in the expected order with the expected topics and data (as specified by the booleans)
224+
function expectEmit(bool, bool, bool, bool) external;
225+
function expectEmit(bool, bool, bool, bool, address) external;
226+
// Mocks a call to an address, returning specified data.
227+
// Calldata can either be strict or a partial match, e.g. if you only
228+
// pass a Solidity selector to the expected calldata, then the entire Solidity
229+
// function will be mocked.
230+
function mockCall(address, bytes calldata, bytes calldata) external;
231+
// Mocks a call to an address with a specific msg.value, returning specified data.
232+
// Calldata match takes precedence over msg.value in case of ambiguity.
233+
function mockCall(address, uint256, bytes calldata, bytes calldata) external;
234+
// Clears all mocked calls
235+
function clearMockedCalls() external;
236+
// Expects a call to an address with the specified calldata.
237+
// Calldata can either be a strict or a partial match
238+
function expectCall(address, bytes calldata) external;
239+
// Expects a call to an address with the specified msg.value and calldata
240+
function expectCall(address, uint256, bytes calldata) external;
241+
// Sets block.coinbase (who)
242+
function coinbase(address) external;
243+
// Snapshot the current state of the evm.
244+
// Returns the id of the snapshot that was created.
245+
// To revert a snapshot use `revertTo`
246+
function snapshot() external returns (uint256);
247+
// Revert the state of the evm to a previous snapshot
248+
// Takes the snapshot id to revert to.
249+
// This deletes the snapshot and all snapshots taken after the given snapshot id.
250+
function revertTo(uint256) external returns (bool);
251+
// Creates a new fork with the given endpoint and block and returns the identifier of the fork
252+
function createFork(string calldata, uint256) external returns (uint256);
253+
// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork
254+
function createFork(string calldata) external returns (uint256);
255+
// Creates a new fork with the given endpoint and at the block the given transaction was mined in, and replays all transaction mined in the block before the transaction
256+
function createFork(string calldata, bytes32) external returns (uint256);
257+
// Creates _and_ also selects a new fork with the given endpoint and block and returns the identifier of the fork
258+
function createSelectFork(string calldata, uint256) external returns (uint256);
259+
// Creates _and_ also selects new fork with the given endpoint and at the block the given transaction was mined in, and replays all transaction mined in the block before the transaction
260+
function createSelectFork(string calldata, bytes32) external returns (uint256);
261+
// Creates _and_ also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork
262+
function createSelectFork(string calldata) external returns (uint256);
263+
// Takes a fork identifier created by `createFork` and sets the corresponding forked state as active.
264+
function selectFork(uint256) external;
265+
/// Returns the currently active fork
266+
/// Reverts if no fork is currently active
267+
function activeFork() external view returns (uint256);
268+
// Updates the currently active fork to given block number
269+
// This is similar to `roll` but for the currently active fork
270+
function rollFork(uint256) external;
271+
// Updates the currently active fork to given transaction
272+
// this will `rollFork` with the number of the block the transaction was mined in and replays all transaction mined before it in the block
273+
function rollFork(bytes32) external;
274+
// Updates the given fork to given block number
275+
function rollFork(uint256 forkId, uint256 blockNumber) external;
276+
// Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block
277+
function rollFork(uint256 forkId, bytes32 transaction) external;
278+
// Marks that the account(s) should use persistent storage across fork swaps in a multifork setup
279+
// Meaning, changes made to the state of this account will be kept when switching forks
280+
function makePersistent(address) external;
281+
function makePersistent(address, address) external;
282+
function makePersistent(address, address, address) external;
283+
function makePersistent(address[] calldata) external;
284+
// Revokes persistent status from the address, previously added via `makePersistent`
285+
function revokePersistent(address) external;
286+
function revokePersistent(address[] calldata) external;
287+
// Returns true if the account is marked as persistent
288+
function isPersistent(address) external view returns (bool);
289+
// In forking mode, explicitly grant the given address cheatcode access
290+
function allowCheatcodes(address) external;
291+
// Fetches the given transaction from the active fork and executes it on the current state
292+
function transact(bytes32 txHash) external;
293+
// Fetches the given transaction from the given fork and executes it on the current state
294+
function transact(uint256 forkId, bytes32 txHash) external;
295+
}

lib/ds-test

Submodule ds-test updated 1 file

0 commit comments

Comments
 (0)