diff --git "a/\\" "b/\\" new file mode 100644 index 00000000..dcd4161f --- /dev/null +++ "b/\\" @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +pragma experimental ABIEncoderV2; + +// Cheatcodes are marked as view/pure/none using the following rules: +// 0. A call's observable behaviour includes its return value, logs, reverts and state writes. +// 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), +// 2. Otherwise if you can be influenced by an earlier call, or if reading some state, you're `view`, +// 3. Otherwise you're `pure`. + +interface VmSafe { + struct Log { + bytes32[] topics; + bytes data; + } + + // Loads a storage slot from an address (who, slot) + function load(address, bytes32) external view returns (bytes32); + // Signs data, (privateKey, digest) => (v, r, s) + function sign(uint256, bytes32) external pure returns (uint8, bytes32, bytes32); + // Gets the address for a given private key, (privateKey) => (address) + function addr(uint256) external pure returns (address); + // Gets the nonce of an account + function getNonce(address) external view returns (uint64); + // Performs a foreign function call via the terminal, (stringInputs) => (result) + function ffi(string[] calldata) external returns (bytes memory); + // Sets environment variables, (name, value) + function setEnv(string calldata, string calldata) external; + // Reads environment variables, (name) => (value) + function envBool(string calldata) external view returns (bool); + function envUint(string calldata) external view returns (uint256); + function envInt(string calldata) external view returns (int256); + function envAddress(string calldata) external view returns (address); + function envBytes32(string calldata) external view returns (bytes32); + function envString(string calldata) external view returns (string memory); + function envBytes(string calldata) external view returns (bytes memory); + // Reads environment variables as arrays, (name, delim) => (value[]) + function envBool(string calldata, string calldata) external view returns (bool[] memory); + function envUint(string calldata, string calldata) external view returns (uint256[] memory); + function envInt(string calldata, string calldata) external view returns (int256[] memory); + function envAddress(string calldata, string calldata) external view returns (address[] memory); + function envBytes32(string calldata, string calldata) external view returns (bytes32[] memory); + function envString(string calldata, string calldata) external view returns (string[] memory); + function envBytes(string calldata, string calldata) external view returns (bytes[] memory); + // Records all storage reads and writes + function record() external; + // Gets all accessed reads and write slot from a recording session, for a given address + function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); + // Gets the _creation_ bytecode from an artifact file. Takes in the relative path to the json file + function getCode(string calldata) external view returns (bytes memory); + // Gets the _deployed_ bytecode from an artifact file. Takes in the relative path to the json file + function getDeployedCode(string calldata) external view returns (bytes memory); + // Labels an address in call traces + function label(address, string calldata) external; + // 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 + function broadcast() external; + // 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 + function broadcast(address) external; + // 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 + function broadcast(uint256) external; + // 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 + function startBroadcast() external; + // Has all subsequent calls (at this call depth only) create transactions with the address provided that can later be signed and sent onchain + function startBroadcast(address) external; + // Has all subsequent calls (at this call depth only) create transactions with the private key provided that can later be signed and sent onchain + function startBroadcast(uint256) external; + // Stops collecting onchain transactions + function stopBroadcast() external; + // Reads the entire content of file to string, (path) => (data) + function readFile(string calldata) external view returns (string memory); + // Reads the entire content of file as binary. Path is relative to the project root. (path) => (data) + function readFileBinary(string calldata) external view returns (bytes memory); + // Get the path of the current project root + function projectRoot() external view returns (string memory); + // Reads next line of file to string, (path) => (line) + function readLine(string calldata) external view returns (string memory); + // Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. + // (path, data) => () + function writeFile(string calldata, string calldata) external; + // Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does. + // Path is relative to the project root. (path, data) => () + function writeFileBinary(string calldata, bytes calldata) external; + // Writes line to file, creating a file if it does not exist. + // (path, data) => () + function writeLine(string calldata, string calldata) external; + // Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. + // (path) => () + function closeFile(string calldata) external; + // Removes file. This cheatcode will revert in the following situations, but is not limited to just these cases: + // - Path points to a directory. + // - The file doesn't exist. + // - The user lacks permissions to remove the file. + // (path) => () + function removeFile(string calldata) external; + // Convert values to a string, (value) => (stringified value) + function toString(address) external pure returns (string memory); + function toString(bytes calldata) external pure returns (string memory); + function toString(bytes32) external pure returns (string memory); + function toString(bool) external pure returns (string memory); + function toString(uint256) external pure returns (string memory); + function toString(int256) external pure returns (string memory); + // Convert values from a string, (string) => (parsed value) + function parseBytes(string calldata) external pure returns (bytes memory); + function parseAddress(string calldata) external pure returns (address); + function parseUint(string calldata) external pure returns (uint256); + function parseInt(string calldata) external pure returns (int256); + function parseBytes32(string calldata) external pure returns (bytes32); + function parseBool(string calldata) external pure returns (bool); + // Record all the transaction logs + function recordLogs() external; + // Gets all the recorded logs, () => (logs) + function getRecordedLogs() external returns (Log[] memory); + // Derive a private key from a provided mnenomic string (or mnenomic file path) at the derivation path m/44'/60'/0'/0/{index} + function deriveKey(string calldata, uint32) external pure returns (uint256); + // Derive a private key from a provided mnenomic string (or mnenomic file path) at the derivation path {path}{index} + function deriveKey(string calldata, string calldata, uint32) external pure returns (uint256); + // Adds a private key to the local forge wallet and returns the address + function rememberKey(uint256) external returns (address); + // + // parseJson + // + // ---- + // In case the returned value is a JSON object, it's encoded as a ABI-encoded tuple. As JSON objects + // don't have the notion of ordered, but tuples do, they JSON object is encoded with it's fields ordered in + // ALPHABETICAL ordser. That means that in order to succesfully decode the tuple, we need to define a tuple that + // encodes the fields in the same order, which is alphabetical. In the case of Solidity structs, they are encoded + // as tuples, with the attributes in the order in which they are defined. + // For example: json = { 'a': 1, 'b': 0xa4tb......3xs} + // a: uint256 + // b: address + // To decode that json, we need to define a struct or a tuple as follows: + // struct json = { uint256 a; address b; } + // If we defined a json struct with the opposite order, meaning placing the address b first, it would try to + // decode the tuple in that order, and thus fail. + // ---- + // Given a string of JSON, return it as ABI-encoded, (stringified json, key) => (ABI-encoded data) + function parseJson(string calldata, string calldata) external pure returns (bytes memory); + function parseJson(string calldata) external pure returns (bytes memory); + // + // writeJson + // + // ---- + // Let's assume we want to write the following JSON to a file: + // + // { "boolean": true, "number": 342, "object": { "title": "finally json serialization" } } + // + // ``` + // string memory json1 = "some key"; + // vm.serializeBool(json1, "boolean", true); + // vm.serializeBool(json1, "number", uint256(342)); + // json2 = "some other key"; + // string memory output = vm.serializeString(json2, "title", "finally json serialization"); + // vm.serialize(json1, "object", output); + // vm.writeJson(json1, "./output/example.json"); + // ``` + // The critical insight is that every invocation of serialization will return the stringified version of the JSON + // up to that point. That means we can construct arbitrary JSON objects and then use the return stringified version + // to serialize them as values to another JSON object. + // + // json1 and json2 are simply keys used by the backend to keep track of the objects. So vm.serializeJson(json1,..) + // will find the object in-memory that is keyed by "some key". // writeJson + function serializeBool(string calldata, string calldata, bool) external returns (string memory); + function serializeUint(string calldata, string calldata, uint256) external returns (string memory); + function serializeInt(string calldata, string calldata, int256) external returns (string memory); + function serializeAddress(string calldata, string calldata, address) external returns (string memory); + function serializeBytes32(string calldata, string calldata, bytes32) external returns (string memory); + function serializeString(string calldata, string calldata, string calldata) external returns (string memory); + function serializeBytes(string calldata, string calldata, bytes calldata) external returns (string memory); + + function serializeBool(string calldata, string calldata, bool[] calldata) external returns (string memory); + function serializeUint(string calldata, string calldata, uint256[] calldata) external returns (string memory); + function serializeInt(string calldata, string calldata, int256[] calldata) external returns (string memory); + function serializeAddress(string calldata, string calldata, address[] calldata) external returns (string memory); + function serializeBytes32(string calldata, string calldata, bytes32[] calldata) external returns (string memory); + function serializeString(string calldata, string calldata, string[] calldata) external returns (string memory); + function serializeBytes(string calldata, string calldata, bytes[] calldata) external returns (string memory); + function writeJson(string calldata, string calldata) external; + function writeJson(string calldata, string calldata, string calldata) external; + // Returns the RPC url for the given alias + function rpcUrl(string calldata) external view returns (string memory); + // Returns all rpc urls and their aliases `[alias, url][]` + function rpcUrls() external view returns (string[2][] memory); + // If the condition is false, discard this run's fuzz inputs and generate new ones. + function assume(bool) external pure; +} + +interface Vm is VmSafe { + // Sets block.timestamp (newTimestamp) + function warp(uint256) external; + // Sets block.height (newHeight) + function roll(uint256) external; + // Sets block.basefee (newBasefee) + function fee(uint256) external; + // Sets block.difficulty (newDifficulty) + function difficulty(uint256) external; + // Sets block.chainid + function chainId(uint256) external; + // Stores a value to an address' storage slot, (who, slot, value) + function store(address, bytes32, bytes32) external; + // Sets the nonce of an account; must be higher than the current nonce of the account + function setNonce(address, uint64) external; + // Sets the *next* call's msg.sender to be the input address + function prank(address) external; + // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called + function startPrank(address) external; + // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input + function prank(address, address) external; + // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input + function startPrank(address, address) external; + // Resets subsequent calls' msg.sender to be `address(this)` + function stopPrank() external; + // Sets an address' balance, (who, newBalance) + function deal(address, uint256) external; + // Sets an address' code, (who, newCode) + function etch(address, bytes calldata) external; + // Expects an error on next call + function expectRevert(bytes calldata) external; + function expectRevert(bytes4) external; + function expectRevert() external; + // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). + // Call this function, then emit an event, then call a function. Internally after the call, we check if + // logs were emitted in the expected order with the expected topics and data (as specified by the booleans) + function expectEmit(bool, bool, bool, bool) external; + function expectEmit(bool, bool, bool, bool, address) external; + // Mocks a call to an address, returning specified data. + // Calldata can either be strict or a partial match, e.g. if you only + // pass a Solidity selector to the expected calldata, then the entire Solidity + // function will be mocked. + function mockCall(address, bytes calldata, bytes calldata) external; + // Mocks a call to an address with a specific msg.value, returning specified data. + // Calldata match takes precedence over msg.value in case of ambiguity. + function mockCall(address, uint256, bytes calldata, bytes calldata) external; + // Clears all mocked calls + function clearMockedCalls() external; + // Expects a call to an address with the specified calldata. + // Calldata can either be a strict or a partial match + function expectCall(address, bytes calldata) external; + // Expects a call to an address with the specified msg.value and calldata + function expectCall(address, uint256, bytes calldata) external; + // Sets block.coinbase (who) + function coinbase(address) external; + // Snapshot the current state of the evm. + // Returns the id of the snapshot that was created. + // To revert a snapshot use `revertTo` + function snapshot() external returns (uint256); + // Revert the state of the evm to a previous snapshot + // Takes the snapshot id to revert to. + // This deletes the snapshot and all snapshots taken after the given snapshot id. + function revertTo(uint256) external returns (bool); + // Creates a new fork with the given endpoint and block and returns the identifier of the fork + function createFork(string calldata, uint256) external returns (uint256); + // Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork + function createFork(string calldata) external returns (uint256); + // 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 + function createFork(string calldata, bytes32) external returns (uint256); + // Creates _and_ also selects a new fork with the given endpoint and block and returns the identifier of the fork + function createSelectFork(string calldata, uint256) external returns (uint256); + // 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 + function createSelectFork(string calldata, bytes32) external returns (uint256); + // Creates _and_ also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork + function createSelectFork(string calldata) external returns (uint256); + // Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. + function selectFork(uint256) external; + /// Returns the currently active fork + /// Reverts if no fork is currently active + function activeFork() external view returns (uint256); + // Updates the currently active fork to given block number + // This is similar to `roll` but for the currently active fork + function rollFork(uint256) external; + // Updates the currently active fork to given transaction + // this will `rollFork` with the number of the block the transaction was mined in and replays all transaction mined before it in the block + function rollFork(bytes32) external; + // Updates the given fork to given block number + function rollFork(uint256 forkId, uint256 blockNumber) external; + // Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block + function rollFork(uint256 forkId, bytes32 transaction) external; + // Marks that the account(s) should use persistent storage across fork swaps in a multifork setup + // Meaning, changes made to the state of this account will be kept when switching forks + function makePersistent(address) external; + function makePersistent(address, address) external; + function makePersistent(address, address, address) external; + function makePersistent(address[] calldata) external; + // Revokes persistent status from the address, previously added via `makePersistent` + function revokePersistent(address) external; + function revokePersistent(address[] calldata) external; + // Returns true if the account is marked as persistent + function isPersistent(address) external view returns (bool); + // In forking mode, explicitly grant the given address cheatcode access + function allowCheatcodes(address) external; + // Fetches the given transaction from the active fork and executes it on the current state + function transact(bytes32 txHash) external; + // Fetches the given transaction from the given fork and executes it on the current state + function transact(uint256 forkId, bytes32 txHash) external; +} diff --git a/lib/ds-test b/lib/ds-test index cd98eff2..9310e879 160000 --- a/lib/ds-test +++ b/lib/ds-test @@ -1 +1 @@ -Subproject commit cd98eff28324bfac652e63a239a60632a761790b +Subproject commit 9310e879db8ba3ea6d5c6489a579118fd264a3f5 diff --git a/src/StdJson.sol b/src/StdJson.sol index efac6cfc..4c16e2b9 100644 --- a/src/StdJson.sol +++ b/src/StdJson.sol @@ -5,7 +5,27 @@ pragma experimental ABIEncoderV2; import "./Vm.sol"; -// Helpers for parsing keys into types. +// Helpers for parsing and writing JSON files +// To parse: +// ``` +// using stdJson for string; +// string memory json = vm.readFile("some_peth"); +// json.parseUint(""); +// ``` +// To write: +// ``` +// using stdJson for string; +// string memory json = "deploymentArtifact"; +// Contract contract = new Contract(); +// json.serialize("contractAddress", address(contract)); +// json = json.serialize("deploymentTimes", uint(1)); +// // store the stringified JSON to the 'json' variable we have been using as a key +// // as we won't need it any longer +// string memory json2 = "finalArtifact"; +// string memory final = json2.serialize("depArtifact", json); +// final.write(""); +// ``` + library stdJson { VmSafe private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -68,4 +88,81 @@ library stdJson { function readBytesArray(string memory json, string memory key) internal pure returns (bytes[] memory) { return abi.decode(vm.parseJson(json, key), (bytes[])); } + + function serialize(string memory jsonKey, string memory key, bool value) internal returns (string memory) { + return vm.serializeBool(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, bool[] memory value) + internal + returns (string memory) + { + return vm.serializeBool(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, uint256 value) internal returns (string memory) { + return vm.serializeUint(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, uint256[] memory value) + internal + returns (string memory) + { + return vm.serializeUint(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, int256 value) internal returns (string memory) { + return vm.serializeInt(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, int256[] memory value) + internal + returns (string memory) + { + return vm.serializeInt(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, address value) internal returns (string memory) { + return vm.serializeAddress(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, address[] memory value) + internal + returns (string memory) + { + return vm.serializeAddress(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, bytes32 value) internal returns (string memory) { + return vm.serializeBytes32(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, bytes32[] memory value) + internal + returns (string memory) + { + return vm.serializeBytes32(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, string memory value) + internal + returns (string memory) + { + return vm.serializeString(jsonKey, key, value); + } + + function serialize(string memory jsonKey, string memory key, string[] memory value) + internal + returns (string memory) + { + return vm.serializeString(jsonKey, key, value); + } + + function write(string memory jsonKey, string memory path) internal { + vm.writeJson(jsonKey, path); + } + + function write(string memory jsonKey, string memory path, string memory valueKey) internal { + vm.writeJson(jsonKey, path, valueKey); + } } diff --git a/src/Vm.sol b/src/Vm.sol index b2bdf3b2..6b2343e1 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -118,14 +118,9 @@ interface VmSafe { function deriveKey(string calldata, string calldata, uint32) external pure returns (uint256); // Adds a private key to the local forge wallet and returns the address function rememberKey(uint256) external returns (address); - // Given a string of JSON, return the ABI-encoded value of provided key - // (stringified json, key) => (ABI-encoded data) - // Read the note below! - function parseJson(string calldata, string calldata) external pure returns (bytes memory); - // Given a string of JSON, return it as ABI-encoded, (stringified json, key) => (ABI-encoded data) - // Read the note below! - function parseJson(string calldata) external pure returns (bytes memory); - // Note: + // + // parseJson + // // ---- // In case the returned value is a JSON object, it's encoded as a ABI-encoded tuple. As JSON objects // don't have the notion of ordered, but tuples do, they JSON object is encoded with it's fields ordered in @@ -139,12 +134,63 @@ interface VmSafe { // struct json = { uint256 a; address b; } // If we defined a json struct with the opposite order, meaning placing the address b first, it would try to // decode the tuple in that order, and thus fail. + // ---- + // Given a string of JSON, return it as ABI-encoded, (stringified json, key) => (ABI-encoded data) + function parseJson(string calldata, string calldata) external pure returns (bytes memory); + function parseJson(string calldata) external pure returns (bytes memory); + // + // writeJson + // + // ---- + // Let's assume we want to write the following JSON to a file: + // + // { "boolean": true, "number": 342, "object": { "title": "finally json serialization" } } + // + // ``` + // string memory json1 = "some key"; + // vm.serializeBool(json1, "boolean", true); + // vm.serializeBool(json1, "number", uint256(342)); + // json2 = "some other key"; + // string memory output = vm.serializeString(json2, "title", "finally json serialization"); + // string memory finalJson = vm.serialize(json1, "object", output); + // vm.writeJson(finalJson, "./output/example.json"); + // ``` + // The critical insight is that every invocation of serialization will return the stringified version of the JSON + // up to that point. That means we can construct arbitrary JSON objects and then use the return stringified version + // to serialize them as values to another JSON object. + // + // json1 and json2 are simply keys used by the backend to keep track of the objects. So vm.serializeJson(json1,..) + // will find the object in-memory that is keyed by "some key". // writeJson + // ---- + // Serialize a key and value to a JSON object stored in-memory that can be latery written to a file + // It returns the stringified version of the specific JSON file up to that moment. + // (object_key, value_key, value) => (stringified JSON) + function serializeBool(string calldata, string calldata, bool) external returns (string memory); + function serializeUint(string calldata, string calldata, uint256) external returns (string memory); + function serializeInt(string calldata, string calldata, int256) external returns (string memory); + function serializeAddress(string calldata, string calldata, address) external returns (string memory); + function serializeBytes32(string calldata, string calldata, bytes32) external returns (string memory); + function serializeString(string calldata, string calldata, string calldata) external returns (string memory); + function serializeBytes(string calldata, string calldata, bytes calldata) external returns (string memory); + function serializeBool(string calldata, string calldata, bool[] calldata) external returns (string memory); + function serializeUint(string calldata, string calldata, uint256[] calldata) external returns (string memory); + function serializeInt(string calldata, string calldata, int256[] calldata) external returns (string memory); + function serializeAddress(string calldata, string calldata, address[] calldata) external returns (string memory); + function serializeBytes32(string calldata, string calldata, bytes32[] calldata) external returns (string memory); + function serializeString(string calldata, string calldata, string[] calldata) external returns (string memory); + function serializeBytes(string calldata, string calldata, bytes[] calldata) external returns (string memory); + // Write a serialized JSON object to a file. If the file exists, it will be overwritten. + // (stringified_json, path) + function writeJson(string calldata, string calldata) external; + // Write a serialized JSON object to an **existing** JSON file, replacing a value with key = + // This is useful to replace a specific value of a JSON file, without having to parse the entire thing + // (stringified_json, path, value_key) + function writeJson(string calldata, string calldata, string calldata) external; // Returns the RPC url for the given alias function rpcUrl(string calldata) external view returns (string memory); // Returns all rpc urls and their aliases `[alias, url][]` function rpcUrls() external view returns (string[2][] memory); - // If the condition is false, discard this run's fuzz inputs and generate new ones. function assume(bool) external pure; }