From 5b2aa6c9c8b528315a665a1784c55886b7865a17 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 20 Dec 2023 22:16:14 +0100 Subject: [PATCH 01/13] feat: automatically generate Vm.sol from cheatcodes.json --- .gitattributes | 1 + scripts/vm.py | 620 +++++++++++++++++++++++++ src/Vm.sol | 1200 ++++++++++++++++++++++++++++-------------------- 3 files changed, 1328 insertions(+), 493 deletions(-) create mode 100644 .gitattributes create mode 100755 scripts/vm.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..27042d45 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +src/Vm.sol linguist-generated diff --git a/scripts/vm.py b/scripts/vm.py new file mode 100755 index 00000000..3e34e612 --- /dev/null +++ b/scripts/vm.py @@ -0,0 +1,620 @@ +#!/usr/bin/env python3 + +import json +import subprocess +from enum import Enum as PyEnum +from typing import Callable +from urllib import request + +VoidFn = Callable[[], None] + +CHEATCODES_JSON_URL = "https://raw.githubusercontent.com/foundry-rs/foundry/master/crates/cheatcodes/assets/cheatcodes.json" +OUT_PATH = "src/Vm.sol" + +VM_SAFE_DOC = """\ +/// The `VmSafe` interface does not allow manipulation of the EVM state or other actions that may +/// result in Script simulations differing from on-chain execution. It is recommended to only use +/// these cheats in scripts. +""" + +VM_DOC = """\ +/// The `Vm` interface does allow manipulation of the EVM state. These are all intended to be used +/// in tests, but it is not recommended to use these cheats in scripts. +""" + + +def main(): + json_str = request.urlopen(CHEATCODES_JSON_URL).read().decode("utf-8") + contract = Cheatcodes.from_json(json_str) + contract.cheatcodes.sort(key=lambda cc: cc.func.id) + + safe = list(filter(lambda cc: cc.safety == Safety.SAFE, contract.cheatcodes)) + safe.sort(key=CmpCheatcode) + unsafe = list(filter(lambda cc: cc.safety == Safety.UNSAFE, contract.cheatcodes)) + unsafe.sort(key=CmpCheatcode) + assert len(safe) + len(unsafe) == len(contract.cheatcodes) + + out = "" + + out += "// Automatically @generated by scripts/vm.py. Do not modify manually.\n\n" + + pp = CheatcodesPrinter( + spdx_identifier="MIT OR Apache-2.0", + solidity_requirement=">=0.6.2 <0.9.0", + abicoder_pragma=True, + ) + pp.p_prelude() + pp.prelude = False + out += pp.finish() + + out += "\n\n" + out += VM_SAFE_DOC + vm_safe = Cheatcodes( + # TODO: Custom errors were introduced in 0.8.4 + errors=[], # contract.errors + events=contract.events, + enums=contract.enums, + structs=contract.structs, + cheatcodes=safe, + ) + pp.p_contract(vm_safe, "VmSafe") + out += pp.finish() + + out += "\n\n" + out += VM_DOC + vm_unsafe = Cheatcodes( + errors=[], + events=[], + enums=[], + structs=[], + cheatcodes=unsafe, + ) + pp.p_contract(vm_unsafe, "Vm", "VmSafe") + out += pp.finish() + + with open(OUT_PATH, "w") as f: + f.write(out) + + subprocess.run(["forge", "fmt", OUT_PATH]) + + print(f"Wrote to {OUT_PATH}") + + +class CmpCheatcode: + cheatcode: "Cheatcode" + + def __init__(self, cheatcode: "Cheatcode"): + self.cheatcode = cheatcode + + def __lt__(self, other: "CmpCheatcode") -> bool: + return cmp_cheatcode(self.cheatcode, other.cheatcode) < 0 + + def __eq__(self, other: "CmpCheatcode") -> bool: + return cmp_cheatcode(self.cheatcode, other.cheatcode) == 0 + + def __gt__(self, other: "CmpCheatcode") -> bool: + return cmp_cheatcode(self.cheatcode, other.cheatcode) > 0 + + +def cmp_cheatcode(a: "Cheatcode", b: "Cheatcode") -> int: + if a.group != b.group: + return -1 if a.group < b.group else 1 + if a.status != b.status: + return -1 if a.status < b.status else 1 + if a.safety != b.safety: + return -1 if a.safety < b.safety else 1 + if a.func.id != b.func.id: + return -1 if a.func.id < b.func.id else 1 + return 0 + + +class Status(PyEnum): + STABLE: str = "stable" + EXPERIMENTAL: str = "experimental" + DEPRECATED: str = "deprecated" + REMOVED: str = "removed" + + def __lt__(self, other: "Group") -> bool: + return self.value < other.value + + +class Group(PyEnum): + EVM: str = "evm" + TESTING: str = "testing" + SCRIPTING: str = "scripting" + FILESYSTEM: str = "filesystem" + ENVIRONMENT: str = "environment" + STRING: str = "string" + JSON: str = "json" + UTILITIES: str = "utilities" + + def __lt__(self, other: "Group") -> bool: + return self.value < other.value + + +class Safety(PyEnum): + UNSAFE: str = "unsafe" + SAFE: str = "safe" + + def __lt__(self, other: "Group") -> bool: + return self.value < other.value + + +class Visibility(PyEnum): + EXTERNAL: str = "external" + PUBLIC: str = "public" + INTERNAL: str = "internal" + PRIVATE: str = "private" + + def __str__(self): + return self.value + + +class Mutability(PyEnum): + PURE: str = "pure" + VIEW: str = "view" + NONE: str = "" + + def __str__(self): + return self.value + + +class Function: + id: str + description: str + declaration: str + visibility: Visibility + mutability: Mutability + signature: str + selector: str + selector_bytes: bytes + + def __init__( + self, + id: str, + description: str, + declaration: str, + visibility: Visibility, + mutability: Mutability, + signature: str, + selector: str, + selector_bytes: bytes, + ): + self.id = id + self.description = description + self.declaration = declaration + self.visibility = visibility + self.mutability = mutability + self.signature = signature + self.selector = selector + self.selector_bytes = selector_bytes + + @staticmethod + def from_dict(d: dict) -> "Function": + return Function( + d["id"], + d["description"], + d["declaration"], + Visibility(d["visibility"]), + Mutability(d["mutability"]), + d["signature"], + d["selector"], + bytes(d["selectorBytes"]), + ) + + +class Cheatcode: + func: Function + group: Group + status: Status + safety: Safety + + def __init__(self, func: Function, group: Group, status: Status, safety: Safety): + self.func = func + self.group = group + self.status = status + self.safety = safety + + @staticmethod + def from_dict(d: dict) -> "Cheatcode": + return Cheatcode( + Function.from_dict(d["func"]), + Group(d["group"]), + Status(d["status"]), + Safety(d["safety"]), + ) + + +class Error: + name: str + description: str + declaration: str + + def __init__(self, name: str, description: str, declaration: str): + self.name = name + self.description = description + self.declaration = declaration + + @staticmethod + def from_dict(d: dict) -> "Error": + return Error(**d) + + +class Event: + name: str + description: str + declaration: str + + def __init__(self, name: str, description: str, declaration: str): + self.name = name + self.description = description + self.declaration = declaration + + @staticmethod + def from_dict(d: dict) -> "Event": + return Event(**d) + + +class EnumVariant: + name: str + description: str + + def __init__(self, name: str, description: str): + self.name = name + self.description = description + + +class Enum: + name: str + description: str + variants: list[EnumVariant] + + def __init__(self, name: str, description: str, variants: list[EnumVariant]): + self.name = name + self.description = description + self.variants = variants + + @staticmethod + def from_dict(d: dict) -> "Enum": + return Enum( + d["name"], + d["description"], + list(map(lambda v: EnumVariant(**v), d["variants"])), + ) + + +class StructField: + name: str + ty: str + description: str + + def __init__(self, name: str, ty: str, description: str): + self.name = name + self.ty = ty + self.description = description + + +class Struct: + name: str + description: str + fields: list[StructField] + + def __init__(self, name: str, description: str, fields: list[StructField]): + self.name = name + self.description = description + self.fields = fields + + @staticmethod + def from_dict(d: dict) -> "Struct": + return Struct( + d["name"], + d["description"], + list(map(lambda f: StructField(**f), d["fields"])), + ) + + +class Cheatcodes: + errors: list[Error] + events: list[Event] + enums: list[Enum] + structs: list[Struct] + cheatcodes: list[Cheatcode] + + def __init__( + self, + errors: list[Error], + events: list[Event], + enums: list[Enum], + structs: list[Struct], + cheatcodes: list[Cheatcode], + ): + self.errors = errors + self.events = events + self.enums = enums + self.structs = structs + self.cheatcodes = cheatcodes + + @staticmethod + def from_dict(d: dict) -> "Cheatcodes": + return Cheatcodes( + errors=[Error.from_dict(e) for e in d["errors"]], + events=[Event.from_dict(e) for e in d["events"]], + enums=[Enum.from_dict(e) for e in d["enums"]], + structs=[Struct.from_dict(e) for e in d["structs"]], + cheatcodes=[Cheatcode.from_dict(e) for e in d["cheatcodes"]], + ) + + @staticmethod + def from_json(s) -> "Cheatcodes": + return Cheatcodes.from_dict(json.loads(s)) + + @staticmethod + def from_json_file(file_path: str) -> "Cheatcodes": + with open(file_path, "r") as f: + return Cheatcodes.from_dict(json.load(f)) + + +class Item(PyEnum): + ERROR: str = "error" + EVENT: str = "event" + ENUM: str = "enum" + STRUCT: str = "struct" + FUNCTION: str = "function" + + +class ItemOrder: + _list: list[Item] + + def __init__(self, list: list[Item]) -> None: + assert len(list) <= len(Item), "list must not contain more items than Item" + assert len(list) == len(set(list)), "list must not contain duplicates" + self._list = list + pass + + def get_list(self) -> list[Item]: + return self._list + + @staticmethod + def default() -> "ItemOrder": + return ItemOrder( + [ + Item.ERROR, + Item.EVENT, + Item.ENUM, + Item.STRUCT, + Item.FUNCTION, + ] + ) + + +class CheatcodesPrinter: + buffer: str + + prelude: bool + spdx_identifier: str + solidity_requirement: str + abicoder_v2: bool + + block_doc_style: bool + + indent_level: int + _indent_str: str + + nl_str: str + + items_order: ItemOrder + + def __init__( + self, + buffer: str = "", + prelude: bool = True, + spdx_identifier: str = "UNLICENSED", + solidity_requirement: str = "", + abicoder_pragma: bool = False, + block_doc_style: bool = False, + indent_level: int = 0, + indent_with: int | str = 4, + nl_str: str = "\n", + items_order: ItemOrder = ItemOrder.default(), + ): + self.prelude = prelude + self.spdx_identifier = spdx_identifier + self.solidity_requirement = solidity_requirement + self.abicoder_v2 = abicoder_pragma + self.block_doc_style = block_doc_style + self.buffer = buffer + self.indent_level = indent_level + self.nl_str = nl_str + + if isinstance(indent_with, int): + assert indent_with >= 0 + self._indent_str = " " * indent_with + elif isinstance(indent_with, str): + self._indent_str = indent_with + else: + assert False, "indent_with must be int or str" + + self.items_order = items_order + + def finish(self) -> str: + ret = self.buffer.rstrip() + self.buffer = "" + return ret + + def p_contract(self, contract: Cheatcodes, name: str, inherits: str = ""): + if self.prelude: + self.p_prelude(contract) + + self._p_str("interface ") + name = name.strip() + if name != "": + self._p_str(name) + self._p_str(" ") + if inherits != "": + self._p_str("is ") + self._p_str(inherits) + self._p_str(" ") + self._p_str("{") + self._p_nl() + self._with_indent(lambda: self._p_items(contract)) + self._p_str("}") + self._p_nl() + + def _p_items(self, contract: Cheatcodes): + for item in self.items_order.get_list(): + if item == Item.ERROR: + self.p_errors(contract.errors) + elif item == Item.EVENT: + self.p_events(contract.events) + elif item == Item.ENUM: + self.p_enums(contract.enums) + elif item == Item.STRUCT: + self.p_structs(contract.structs) + elif item == Item.FUNCTION: + self.p_functions(contract.cheatcodes) + else: + assert False, f"unknown item {item}" + + def p_prelude(self, contract: Cheatcodes | None = None): + self._p_str(f"// SPDX-License-Identifier: {self.spdx_identifier}") + self._p_nl() + + if self.solidity_requirement != "": + req = self.solidity_requirement + elif contract and len(contract.errors) > 0: + req = ">=0.8.4 <0.9.0" + else: + req = ">=0.6.0 <0.9.0" + self._p_str(f"pragma solidity {req};") + self._p_nl() + + if self.abicoder_v2: + self._p_str("pragma experimental ABIEncoderV2;") + self._p_nl() + + self._p_nl() + + def p_errors(self, errors: list[Error]): + for error in errors: + self._p_line(lambda: self.p_error(error)) + + def p_error(self, error: Error): + self._p_doc(error.description) + self._p_line(lambda: self._p_str(error.declaration)) + + def p_events(self, events: list[Event]): + for event in events: + self._p_line(lambda: self.p_event(event)) + + def p_event(self, event: Event): + self._p_doc(event.description) + self._p_line(lambda: self._p_str(event.declaration)) + + def p_enums(self, enums: list[Enum]): + for enum in enums: + self._p_line(lambda: self.p_enum(enum)) + + def p_enum(self, enum: Enum): + self._p_doc(enum.description) + self._p_line(lambda: self._p_str(f"enum {enum.name} {{")) + self._with_indent(lambda: self.p_enum_variants(enum.variants)) + self._p_line(lambda: self._p_str("}")) + + def p_enum_variants(self, variants: list[EnumVariant]): + for i, variant in enumerate(variants): + self._p_indent() + self._p_doc(variant.description) + + self._p_indent() + self._p_str(variant.name) + if i < len(variants) - 1: + self._p_str(",") + self._p_nl() + + def p_structs(self, structs: list[Struct]): + for struct in structs: + self._p_line(lambda: self.p_struct(struct)) + + def p_struct(self, struct: Struct): + self._p_doc(struct.description) + self._p_line(lambda: self._p_str(f"struct {struct.name} {{")) + self._with_indent(lambda: self.p_struct_fields(struct.fields)) + self._p_line(lambda: self._p_str("}")) + + def p_struct_fields(self, fields: list[StructField]): + for field in fields: + self._p_line(lambda: self.p_struct_field(field)) + + def p_struct_field(self, field: StructField): + self._p_doc(field.description) + self._p_indented(lambda: self._p_str(f"{field.ty} {field.name};")) + + def p_functions(self, cheatcodes: list[Cheatcode]): + for cheatcode in cheatcodes: + self._p_line(lambda: self.p_function(cheatcode.func)) + + def p_function(self, func: Function): + self._p_doc(func.description) + self._p_line(lambda: self._p_str(func.declaration)) + + def _p_doc(self, doc: str): + doc = doc.strip() + if doc == "": + return + + doc = map(lambda line: line.lstrip(), doc.split("\n")) + if self.block_doc_style: + self._p_str("/**") + self._p_nl() + for line in doc: + self._p_indent() + self._p_str(" * ") + self._p_str(line) + self._p_nl() + self._p_indent() + self._p_str(" */") + self._p_nl() + else: + first_line = True + for line in doc: + if not first_line: + self._p_indent() + first_line = False + + self._p_str("/// ") + self._p_str(line) + self._p_nl() + + def _with_indent(self, f: VoidFn): + self._inc_indent() + f() + self._dec_indent() + + def _p_line(self, f: VoidFn): + self._p_indent() + f() + self._p_nl() + + def _p_indented(self, f: VoidFn): + self._p_indent() + f() + + def _p_indent(self): + for _ in range(self.indent_level): + self._p_str(self._indent_str) + + def _p_nl(self): + self._p_str(self.nl_str) + + def _p_str(self, txt: str): + self.buffer += txt + + def _inc_indent(self): + self.indent_level += 1 + + def _dec_indent(self): + self.indent_level -= 1 + + +if __name__ == "__main__": + main() diff --git a/src/Vm.sol b/src/Vm.sol index 7b651d19..8148eec6 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -1,848 +1,1062 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; +// Automatically @generated by scripts/vm.py. Do not modify manually. +// SPDX-License-Identifier: MIT OR Apache-2.0 +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`. - -// The `VmSafe` interface does not allow manipulation of the EVM state or other actions that may -// result in Script simulations differing from on-chain execution. It is recommended to only use -// these cheats in scripts. +/// The `VmSafe` interface does not allow manipulation of the EVM state or other actions that may +/// result in Script simulations differing from on-chain execution. It is recommended to only use +/// these cheats in scripts. interface VmSafe { - // ======== Types ======== - enum CallerMode { + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode + /// No caller modification is currently active. + { None, + /// A one time broadcast triggered by a `vm.broadcast()` call is currently active. Broadcast, + /// A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active. RecurrentBroadcast, + /// A one time prank triggered by a `vm.prank()` call is currently active. Prank, + /// A recurrent prank triggered by a `vm.startPrank()` call is currently active. RecurrentPrank } - enum AccountAccessKind { + /// The kind of account access that occurred. + enum AccountAccessKind + /// The account was called. + { Call, + /// The account was called via delegatecall. DelegateCall, + /// The account was called via callcode. CallCode, + /// The account was called via staticcall. StaticCall, + /// The account was created. Create, + /// The account was selfdestructed. SelfDestruct, + /// Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess). Resume, + /// The account's balance was read. Balance, + /// The account's codesize was read. Extcodesize, - Extcodehash, - Extcodecopy + /// The account's code was copied. + Extcodecopy, + /// The account's codehash was read. + Extcodehash } + /// An Ethereum log. Returned by `getRecordedLogs`. struct Log { + /// The topics of the log, including the signature, if any. bytes32[] topics; + /// The raw data of the log. bytes data; + /// The address of the log's emitter. address emitter; } + /// An RPC URL and its alias. Returned by `rpcUrlStructs`. struct Rpc { + /// The alias of the RPC URL. string key; + /// The RPC URL. string url; } + /// An RPC log object. Returned by `eth_getLogs`. struct EthGetLogs { + /// The address of the log's emitter. address emitter; + /// The topics of the log, including the signature, if any. bytes32[] topics; + /// The raw data of the log. bytes data; + /// The block hash. bytes32 blockHash; + /// The block number. uint64 blockNumber; + /// The transaction hash. bytes32 transactionHash; + /// The transaction index in the block. uint64 transactionIndex; + /// The log index. uint256 logIndex; + /// Whether the log was removed. bool removed; } + /// A single entry in a directory listing. Returned by `readDir`. struct DirEntry { + /// The error message, if any. string errorMessage; + /// The path of the entry. string path; + /// The depth of the entry. uint64 depth; + /// Whether the entry is a directory. bool isDir; + /// Whether the entry is a symlink. bool isSymlink; } + /// Metadata information about a file. + /// This structure is returned from the `fsMetadata` function and represents known + /// metadata about a file such as its permissions, size, modification + /// times, etc. struct FsMetadata { + /// True if this metadata is for a directory. bool isDir; + /// True if this metadata is for a symlink. bool isSymlink; + /// The size of the file, in bytes, this metadata is for. uint256 length; + /// True if this metadata is for a readonly (unwritable) file. bool readOnly; + /// The last modification time listed in this metadata. uint256 modified; + /// The last access time of this metadata. uint256 accessed; + /// The creation time listed in this metadata. uint256 created; } + /// A wallet with a public and private key. struct Wallet { + /// The wallet's address. address addr; + /// The wallet's public key `X`. uint256 publicKeyX; + /// The wallet's public key `Y`. uint256 publicKeyY; + /// The wallet's private key. uint256 privateKey; } + /// The result of a `tryFfi` call. struct FfiResult { + /// The exit code of the call. int32 exitCode; + /// The optionally hex-decoded `stdout` data. bytes stdout; + /// The `stderr` data. bytes stderr; } + /// Information on the chain and fork. struct ChainInfo { + /// The fork identifier. Set to zero if no fork is active. uint256 forkId; + /// The chain ID of the current fork. uint256 chainId; } + /// The result of a `stopAndReturnStateDiff` call. struct AccountAccess { + /// The chain and fork the access occurred. ChainInfo chainInfo; + /// The kind of account access that determines what the account is. + /// If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee. + /// If kind is Create, then the account is the newly created account. + /// If kind is SelfDestruct, then the account is the selfdestruct recipient. + /// If kind is a Resume, then account represents a account context that has resumed. AccountAccessKind kind; + /// The account that was accessed. + /// It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT. address account; + /// What accessed the account. address accessor; + /// If the account was initialized or empty prior to the access. + /// An account is considered initialized if it has code, a + /// non-zero nonce, or a non-zero balance. bool initialized; + /// The previous balance of the accessed account. uint256 oldBalance; + /// The potential new balance of the accessed account. + /// That is, all balance changes are recorded here, even if reverts occurred. uint256 newBalance; + /// Code of the account deployed by CREATE. bytes deployedCode; + /// Value passed along with the account access uint256 value; + /// Input data provided to the CREATE or CALL bytes data; + /// If this access reverted in either the current or parent context. bool reverted; + /// An ordered list of storage accesses made during an account access operation. StorageAccess[] storageAccesses; } + /// The storage accessed during an `AccountAccess`. struct StorageAccess { + /// The account whose storage was accessed. address account; + /// The slot that was accessed. bytes32 slot; + /// If the access was a write. bool isWrite; + /// The previous value of the slot. bytes32 previousValue; + /// The new value of the slot. bytes32 newValue; + /// If the access was reverted. bool reverted; } - // ======== EVM ======== + /// Gets the environment variable `name` and parses it as `address`. + /// Reverts if the variable was not found or could not be parsed. + function envAddress(string calldata name) external view returns (address value); - // Gets the address for a given private key - function addr(uint256 privateKey) external pure returns (address keyAddr); + /// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); - // Gets the nonce of an account. - // See `getNonce(Wallet memory wallet)` for an alternative way to manage users and get their nonces. - function getNonce(address account) external view returns (uint64 nonce); + /// Gets the environment variable `name` and parses it as `bool`. + /// Reverts if the variable was not found or could not be parsed. + function envBool(string calldata name) external view returns (bool value); - // Loads a storage slot from an address - function load(address target, bytes32 slot) external view returns (bytes32 data); + /// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value); - // Signs data - function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + /// Gets the environment variable `name` and parses it as `bytes32`. + /// Reverts if the variable was not found or could not be parsed. + function envBytes32(string calldata name) external view returns (bytes32 value); - // -------- Record Storage -------- - // Records all storage reads and writes - function record() external; + /// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value); - // Gets all accessed reads and write slot from a `vm.record` session, for a given address - function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); + /// Gets the environment variable `name` and parses it as `bytes`. + /// Reverts if the variable was not found or could not be parsed. + function envBytes(string calldata name) external view returns (bytes memory value); - // Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order, - // along with the context of the calls. - function startStateDiffRecording() external; + /// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value); - // Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session. - function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); + /// Gets the environment variable `name` and parses it as `int256`. + /// Reverts if the variable was not found or could not be parsed. + function envInt(string calldata name) external view returns (int256 value); - // -------- Recording Map Writes -------- + /// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value); - // Starts recording all map SSTOREs for later retrieval. - function startMappingRecording() external; + /// Gets the environment variable `name` and parses it as `bool`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, bool defaultValue) external returns (bool value); - // Stops recording all map SSTOREs for later retrieval and clears the recorded data. - function stopMappingRecording() external; + /// Gets the environment variable `name` and parses it as `uint256`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, uint256 defaultValue) external returns (uint256 value); - // Gets the number of elements in the mapping at the given slot, for a given address. - function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); + /// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) + external + returns (address[] memory value); - // Gets the elements at index idx of the mapping at the given slot, for a given address. The - // index must be less than the length of the mapping (i.e. the number of keys in the mapping). - function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value); + /// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) + external + returns (bytes32[] memory value); - // Gets the map key and parent of a mapping at a given slot, for a given address. - function getMappingKeyAndParentOf(address target, bytes32 elementSlot) + /// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external - returns (bool found, bytes32 key, bytes32 parent); + returns (string[] memory value); - // -------- Record Logs -------- - // Record all the transaction logs - function recordLogs() external; + /// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) + external + returns (bytes[] memory value); - // Gets all the recorded logs - function getRecordedLogs() external returns (Log[] memory logs); + /// Gets the environment variable `name` and parses it as `int256`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, int256 defaultValue) external returns (int256 value); - // -------- Gas Metering -------- - // It's recommend to use the `noGasMetering` modifier included with forge-std, instead of - // using these functions directly. + /// Gets the environment variable `name` and parses it as `address`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, address defaultValue) external returns (address value); - // Pauses gas metering (i.e. gas usage is not counted). Noop if already paused. - function pauseGasMetering() external; + /// Gets the environment variable `name` and parses it as `bytes32`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, bytes32 defaultValue) external returns (bytes32 value); - // Resumes gas metering (i.e. gas usage is counted again). Noop if already on. - function resumeGasMetering() external; + /// Gets the environment variable `name` and parses it as `string`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata defaultValue) external returns (string memory value); - // -------- RPC Methods -------- + /// Gets the environment variable `name` and parses it as `bytes`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, bytes calldata defaultValue) external returns (bytes memory value); - /// Gets all the logs according to specified filter. - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) + /// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external - returns (EthGetLogs[] memory logs); + returns (bool[] memory value); - // Performs an Ethereum JSON-RPC request to the current fork URL. - function rpc(string calldata method, string calldata params) external returns (bytes memory data); + /// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) + external + returns (uint256[] memory value); - // ======== Test Configuration ======== + /// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) + external + returns (int256[] memory value); - // If the condition is false, discard this run's fuzz inputs and generate new ones. - function assume(bool condition) external pure; + /// Gets the environment variable `name` and parses it as `string`. + /// Reverts if the variable was not found or could not be parsed. + function envString(string calldata name) external view returns (string memory value); - // Writes a breakpoint to jump to in the debugger - function breakpoint(string calldata char) external; + /// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + function envString(string calldata name, string calldata delim) external view returns (string[] memory value); - // Writes a conditional breakpoint to jump to in the debugger - function breakpoint(string calldata char, bool value) external; + /// Gets the environment variable `name` and parses it as `uint256`. + /// Reverts if the variable was not found or could not be parsed. + function envUint(string calldata name) external view returns (uint256 value); - // Returns the RPC url for the given alias - function rpcUrl(string calldata rpcAlias) external view returns (string memory json); + /// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); - // Returns all rpc urls and their aliases `[alias, url][]` - function rpcUrls() external view returns (string[2][] memory urls); + /// Sets environment variables. + function setEnv(string calldata name, string calldata value) external; - // Returns all rpc urls and their aliases as structs. - function rpcUrlStructs() external view returns (Rpc[] memory urls); + /// Gets all accessed reads and write slot from a `vm.record` session, for a given address. + function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); - // Suspends execution of the main thread for `duration` milliseconds - function sleep(uint256 duration) external; + /// Gets the address for a given private key. + function addr(uint256 privateKey) external pure returns (address keyAddr); - // ======== OS and Filesystem ======== + /// Gets all the logs according to specified filter. + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address addr, bytes32[] memory topics) + external + returns (EthGetLogs[] memory logs); - // -------- Metadata -------- + /// Gets the map key and parent of a mapping at a given slot, for a given address. + function getMappingKeyAndParentOf(address target, bytes32 elementSlot) + external + returns (bool found, bytes32 key, bytes32 parent); - // Returns true if the given path points to an existing entity, else returns false - function exists(string calldata path) external returns (bool result); + /// Gets the number of elements in the mapping at the given slot, for a given address. + function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); - // Given a path, query the file system to get information about a file, directory, etc. - function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); + /// Gets the elements at index idx of the mapping at the given slot, for a given address. The + /// index must be less than the length of the mapping (i.e. the number of keys in the mapping). + function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value); - // Returns true if the path exists on disk and is pointing at a directory, else returns false - function isDir(string calldata path) external returns (bool result); + /// Gets the nonce of an account. + function getNonce(address account) external view returns (uint64 nonce); - // Returns true if the path exists on disk and is pointing at a regular file, else returns false - function isFile(string calldata path) external returns (bool result); + /// Gets all the recorded logs. + function getRecordedLogs() external returns (Log[] memory logs); - // Get the path of the current project root. - function projectRoot() external view returns (string memory path); + /// Loads a storage slot from an address. + function load(address target, bytes32 slot) external view returns (bytes32 data); - // Returns the time since unix epoch in milliseconds - function unixTime() external returns (uint256 milliseconds); + /// Pauses gas metering (i.e. gas usage is not counted). Noop if already paused. + function pauseGasMetering() external; + + /// Records all storage reads and writes. + function record() external; + + /// Record all the transaction logs. + function recordLogs() external; + + /// Resumes gas metering (i.e. gas usage is counted again). Noop if already on. + function resumeGasMetering() external; + + /// Performs an Ethereum JSON-RPC request to the current fork URL. + function rpc(string calldata method, string calldata params) external returns (bytes memory data); + + /// Signs data. + function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// Starts recording all map SSTOREs for later retrieval. + function startMappingRecording() external; + + /// Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order, + /// along with the context of the calls + function startStateDiffRecording() external; - // -------- Reading and writing -------- + /// Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session. + function stopAndReturnStateDiff() external returns (AccountAccess[] memory accesses); - // Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. - // `path` is relative to the project root. + /// Stops recording all map SSTOREs for later retrieval and clears the recorded data. + function stopMappingRecording() external; + + /// Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. + /// `path` is relative to the project root. function closeFile(string calldata path) external; - // Copies the contents of one file to another. This function will **overwrite** the contents of `to`. - // On success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`. - // Both `from` and `to` are relative to the project root. + /// Copies the contents of one file to another. This function will **overwrite** the contents of `to`. + /// On success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`. + /// Both `from` and `to` are relative to the project root. function copyFile(string calldata from, string calldata to) external returns (uint64 copied); - // Creates a new, empty directory at the provided path. - // This cheatcode will revert in the following situations, but is not limited to just these cases: - // - User lacks permissions to modify `path`. - // - A parent of the given path doesn't exist and `recursive` is false. - // - `path` already exists and `recursive` is false. - // `path` is relative to the project root. + /// Creates a new, empty directory at the provided path. + /// This cheatcode will revert in the following situations, but is not limited to just these cases: + /// - User lacks permissions to modify `path`. + /// - A parent of the given path doesn't exist and `recursive` is false. + /// - `path` already exists and `recursive` is false. + /// `path` is relative to the project root. function createDir(string calldata path, bool recursive) external; - // Reads the directory at the given path recursively, up to `max_depth`. - // `max_depth` defaults to 1, meaning only the direct children of the given directory will be returned. - // Follows symbolic links if `follow_links` is true. + /// Returns true if the given path points to an existing entity, else returns false. + function exists(string calldata path) external returns (bool result); + + /// Performs a foreign function call via the terminal. + function ffi(string[] calldata commandInput) external returns (bytes memory result); + + /// Given a path, query the file system to get information about a file, directory, etc. + function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); + + /// Gets the creation bytecode from an artifact file. Takes in the relative path to the json file. + function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); + + /// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file. + function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); + + /// Returns true if the path exists on disk and is pointing at a directory, else returns false. + function isDir(string calldata path) external returns (bool result); + + /// Returns true if the path exists on disk and is pointing at a regular file, else returns false. + function isFile(string calldata path) external returns (bool result); + + /// Get the path of the current project root. + function projectRoot() external view returns (string memory path); + + /// Reads the directory at the given path recursively, up to `maxDepth`. + /// `maxDepth` defaults to 1, meaning only the direct children of the given directory will be returned. + /// Follows symbolic links if `followLinks` is true. function readDir(string calldata path) external view returns (DirEntry[] memory entries); + + /// See `readDir(string)`. function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries); + + /// See `readDir(string)`. function readDir(string calldata path, uint64 maxDepth, bool followLinks) external view returns (DirEntry[] memory entries); - // Reads the entire content of file to string. `path` is relative to the project root. + /// Reads the entire content of file to string. `path` is relative to the project root. function readFile(string calldata path) external view returns (string memory data); - // Reads the entire content of file as binary. `path` is relative to the project root. + /// Reads the entire content of file as binary. `path` is relative to the project root. function readFileBinary(string calldata path) external view returns (bytes memory data); - // Reads next line of file to string. + /// Reads next line of file to string. function readLine(string calldata path) external view returns (string memory line); - // Reads a symbolic link, returning the path that the link points to. - // This cheatcode will revert in the following situations, but is not limited to just these cases: - // - `path` is not a symbolic link. - // - `path` does not exist. + /// Reads a symbolic link, returning the path that the link points to. + /// This cheatcode will revert in the following situations, but is not limited to just these cases: + /// - `path` is not a symbolic link. + /// - `path` does not exist. function readLink(string calldata linkPath) external view returns (string memory targetPath); - // Removes a directory at the provided path. - // This cheatcode will revert in the following situations, but is not limited to just these cases: - // - `path` doesn't exist. - // - `path` isn't a directory. - // - User lacks permissions to modify `path`. - // - The directory is not empty and `recursive` is false. - // `path` is relative to the project root. + /// Removes a directory at the provided path. + /// This cheatcode will revert in the following situations, but is not limited to just these cases: + /// - `path` doesn't exist. + /// - `path` isn't a directory. + /// - User lacks permissions to modify `path`. + /// - The directory is not empty and `recursive` is false. + /// `path` is relative to the project root. function removeDir(string calldata path, bool recursive) external; - // Removes a file from the filesystem. - // 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` is relative to the project root. + /// Removes a file from the filesystem. + /// 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` is relative to the project root. function removeFile(string calldata path) external; - // Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. - // `path` is relative to the project root. + /// Performs a foreign function call via terminal and returns the exit code, stdout, and stderr. + function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); + + /// Returns the time since unix epoch in milliseconds. + function unixTime() external returns (uint256 milliseconds); + + /// Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. + /// `path` is relative to the project root. function writeFile(string calldata path, string calldata data) 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. + /// 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. function writeFileBinary(string calldata path, bytes calldata data) external; - // Writes line to file, creating a file if it does not exist. - // `path` is relative to the project root. + /// Writes line to file, creating a file if it does not exist. + /// `path` is relative to the project root. function writeLine(string calldata path, string calldata data) external; - // -------- Foreign Function Interface -------- + /// Checks if `key` exists in a JSON object. + function keyExists(string calldata json, string calldata key) external view returns (bool); - // Performs a foreign function call via the terminal - function ffi(string[] calldata commandInput) external returns (bytes memory result); + /// Parses a string of JSON data at `key` and coerces it to `address`. + function parseJsonAddress(string calldata json, string calldata key) external pure returns (address); - // Performs a foreign function call via terminal and returns the exit code, stdout, and stderr - function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); + /// Parses a string of JSON data at `key` and coerces it to `address[]`. + function parseJsonAddressArray(string calldata json, string calldata key) + external + pure + returns (address[] memory); - // ======== Environment Variables ======== + /// Parses a string of JSON data at `key` and coerces it to `bool`. + function parseJsonBool(string calldata json, string calldata key) external pure returns (bool); - // Sets environment variables - function setEnv(string calldata name, string calldata value) external; + /// Parses a string of JSON data at `key` and coerces it to `bool[]`. + function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory); - // Reads environment variables, (name) => (value) - function envBool(string calldata name) external view returns (bool value); - function envUint(string calldata name) external view returns (uint256 value); - function envInt(string calldata name) external view returns (int256 value); - function envAddress(string calldata name) external view returns (address value); - function envBytes32(string calldata name) external view returns (bytes32 value); - function envString(string calldata name) external view returns (string memory value); - function envBytes(string calldata name) external view returns (bytes memory value); + /// Parses a string of JSON data at `key` and coerces it to `bytes`. + function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory); - // Reads environment variables as arrays - function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value); - function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); - function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value); - function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); - function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value); - function envString(string calldata name, string calldata delim) external view returns (string[] memory value); - function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value); + /// Parses a string of JSON data at `key` and coerces it to `bytes32`. + function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32); - // Read environment variables with default value - function envOr(string calldata name, bool defaultValue) external returns (bool value); - function envOr(string calldata name, uint256 defaultValue) external returns (uint256 value); - function envOr(string calldata name, int256 defaultValue) external returns (int256 value); - function envOr(string calldata name, address defaultValue) external returns (address value); - function envOr(string calldata name, bytes32 defaultValue) external returns (bytes32 value); - function envOr(string calldata name, string calldata defaultValue) external returns (string memory value); - function envOr(string calldata name, bytes calldata defaultValue) external returns (bytes memory value); + /// Parses a string of JSON data at `key` and coerces it to `bytes32[]`. + function parseJsonBytes32Array(string calldata json, string calldata key) + external + pure + returns (bytes32[] memory); - // Read environment variables as arrays with default value - function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) + /// Parses a string of JSON data at `key` and coerces it to `bytes[]`. + function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory); + + /// Parses a string of JSON data at `key` and coerces it to `int256`. + function parseJsonInt(string calldata json, string calldata key) external pure returns (int256); + + /// Parses a string of JSON data at `key` and coerces it to `int256[]`. + function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory); + + /// Returns an array of all the keys in a JSON object. + function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys); + + /// Parses a string of JSON data at `key` and coerces it to `string`. + function parseJsonString(string calldata json, string calldata key) external pure returns (string memory); + + /// Parses a string of JSON data at `key` and coerces it to `string[]`. + function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory); + + /// Parses a string of JSON data at `key` and coerces it to `uint256`. + function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256); + + /// Parses a string of JSON data at `key` and coerces it to `uint256[]`. + function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory); + + /// ABI-encodes a JSON object. + function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); + + /// ABI-encodes a JSON object at `key`. + function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData); + + /// See `serializeJson`. + function serializeAddress(string calldata objectKey, string calldata valueKey, address value) external - returns (bool[] memory value); - function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) + returns (string memory json); + + /// See `serializeJson`. + function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) external - returns (uint256[] memory value); - function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) + returns (string memory json); + + /// See `serializeJson`. + function serializeBool(string calldata objectKey, string calldata valueKey, bool value) external - returns (int256[] memory value); - function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) + returns (string memory json); + + /// See `serializeJson`. + function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values) external - returns (address[] memory value); - function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) + returns (string memory json); + + /// See `serializeJson`. + function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) external - returns (bytes32[] memory value); - function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) + returns (string memory json); + + /// See `serializeJson`. + function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values) external - returns (string[] memory value); - function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) + returns (string memory json); + + /// See `serializeJson`. + function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value) external - returns (bytes[] memory value); + returns (string memory json); - // ======== User Management ======== + /// See `serializeJson`. + function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values) + external + returns (string memory json); - // Derives a private key from the name, labels the account with that name, and returns the wallet - function createWallet(string calldata walletLabel) external returns (Wallet memory wallet); + /// See `serializeJson`. + function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) + external + returns (string memory json); - // Generates a wallet from the private key and returns the wallet - function createWallet(uint256 privateKey) external returns (Wallet memory wallet); + /// See `serializeJson`. + function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) + external + returns (string memory json); - // Generates a wallet from the private key, labels the account with that name, and returns the wallet - function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); + /// Serializes a key and value to a JSON object stored in-memory that can be later written to a file. + /// Returns the stringified version of the specific JSON file up to that moment. + function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); - // Gets the label for the specified address - function getLabel(address account) external returns (string memory currentLabel); + /// See `serializeJson`. + function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) + external + returns (string memory json); - // Get nonce for a Wallet. - // See `getNonce(address account)` for an alternative way to get a nonce. - function getNonce(Wallet calldata wallet) external returns (uint64 nonce); + /// See `serializeJson`. + function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) + external + returns (string memory json); - // Labels an address in call traces - function label(address account, string calldata newLabel) external; + /// See `serializeJson`. + function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) + external + returns (string memory json); - // Signs data, (Wallet, digest) => (v, r, s) - function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + /// See `serializeJson`. + function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) + external + returns (string memory json); - // ======== Scripts ======== + /// Write a serialized JSON object to a file. If the file exists, it will be overwritten. + function writeJson(string calldata json, string calldata path) external; - // -------- Broadcasting Transactions -------- + /// 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. + function writeJson(string calldata json, string calldata path, string calldata valueKey) 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 + /// 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 + /// 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 signer) 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 + /// 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 privateKey) 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 + /// 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 + /// 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 signer) 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 + /// 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 privateKey) external; - // Stops collecting onchain transactions + /// Stops collecting onchain transactions. function stopBroadcast() external; - // -------- Key Management -------- + /// Parses the given `string` into an `address`. + function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue); - // 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 mnemonic, uint32 index) external pure returns (uint256 privateKey); + /// Parses the given `string` into a `bool`. + function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); - // Derive a private key from a provided mnenomic string (or mnenomic file path) at {derivationPath}{index} - function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) - external - pure - returns (uint256 privateKey); + /// Parses the given `string` into `bytes`. + function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue); - // Adds a private key to the local forge wallet and returns the address - function rememberKey(uint256 privateKey) external returns (address keyAddr); + /// Parses the given `string` into a `bytes32`. + function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue); - // ======== Utilities ======== + /// Parses the given `string` into a `int256`. + function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue); + + /// Parses the given `string` into a `uint256`. + function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue); - // Convert values to a string + /// Converts the given value to a `string`. function toString(address value) external pure returns (string memory stringifiedValue); + + /// Converts the given value to a `string`. function toString(bytes calldata value) external pure returns (string memory stringifiedValue); + + /// Converts the given value to a `string`. function toString(bytes32 value) external pure returns (string memory stringifiedValue); + + /// Converts the given value to a `string`. function toString(bool value) external pure returns (string memory stringifiedValue); + + /// Converts the given value to a `string`. function toString(uint256 value) external pure returns (string memory stringifiedValue); + + /// Converts the given value to a `string`. function toString(int256 value) external pure returns (string memory stringifiedValue); - // Convert values from a string - function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue); - function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue); - function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue); - function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue); - function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue); - function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); + /// If the condition is false, discard this run's fuzz inputs and generate new ones. + function assume(bool condition) external pure; - // Gets the creation bytecode from an artifact file. Takes in the relative path to the json file - function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); + /// Writes a breakpoint to jump to in the debugger. + function breakpoint(string calldata char) external; - // Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file - function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); + /// Writes a conditional breakpoint to jump to in the debugger. + function breakpoint(string calldata char, bool value) external; - // Compute the address a contract will be deployed at for a given deployer address and nonce. - function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address); + /// Returns the RPC url for the given alias. + function rpcUrl(string calldata rpcAlias) external view returns (string memory json); + + /// Returns all rpc urls and their aliases as structs. + function rpcUrlStructs() external view returns (Rpc[] memory urls); + + /// Returns all rpc urls and their aliases `[alias, url][]`. + function rpcUrls() external view returns (string[2][] memory urls); + + /// Suspends execution of the main thread for `duration` milliseconds. + function sleep(uint256 duration) external; - // Compute the address of a contract created with CREATE2 using the given CREATE2 deployer. + /// Compute the address of a contract created with CREATE2 using the given CREATE2 deployer. function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address); - // Compute the address of a contract created with CREATE2 using foundry's default CREATE2 - // deployer: 0x4e59b44847b379578588920cA78FbF26c0B4956C, https://github.com/Arachnid/deterministic-deployment-proxy + /// Compute the address of a contract created with CREATE2 using the default CREATE2 deployer. function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address); - // ======== JSON Parsing and Manipulation ======== + /// Compute the address a contract will be deployed at for a given deployer address and nonce. + function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address); - // -------- Reading -------- + /// Derives a private key from the name, labels the account with that name, and returns the wallet. + function createWallet(string calldata walletLabel) external returns (Wallet memory wallet); - // NOTE: Please read https://book.getfoundry.sh/cheatcodes/parse-json to understand the - // limitations and caveats of the JSON parsing cheats. + /// Generates a wallet from the private key and returns the wallet. + function createWallet(uint256 privateKey) external returns (Wallet memory wallet); - // Checks if a key exists in a JSON object. - function keyExists(string calldata json, string calldata key) external view returns (bool); + /// Generates a wallet from the private key, labels the account with that name, and returns the wallet. + function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); - // Given a string of JSON, return it as ABI-encoded - function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData); - function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); + /// 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 mnemonic, uint32 index) external pure returns (uint256 privateKey); - // The following parseJson cheatcodes will do type coercion, for the type that they indicate. - // For example, parseJsonUint will coerce all values to a uint256. That includes stringified numbers '12' - // and hex numbers '0xEF'. - // Type coercion works ONLY for discrete values or arrays. That means that the key must return a value or array, not - // a JSON object. - function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256); - function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory); - function parseJsonInt(string calldata json, string calldata key) external pure returns (int256); - function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory); - function parseJsonBool(string calldata json, string calldata key) external pure returns (bool); - function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory); - function parseJsonAddress(string calldata json, string calldata key) external pure returns (address); - function parseJsonAddressArray(string calldata json, string calldata key) + /// Derive a private key from a provided mnenomic string (or mnenomic file path) + /// at `{derivationPath}{index}`. + function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure - returns (address[] memory); - function parseJsonString(string calldata json, string calldata key) external pure returns (string memory); - function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory); - function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory); - function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory); - function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32); - function parseJsonBytes32Array(string calldata json, string calldata key) + returns (uint256 privateKey); + + /// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language + /// at the derivation path `m/44'/60'/0'/0/{index}`. + function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure - returns (bytes32[] memory); - - // Returns array of keys for a JSON object - function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys); - - // -------- Writing -------- - - // NOTE: Please read https://book.getfoundry.sh/cheatcodes/serialize-json to understand how - // to use the serialization cheats. + returns (uint256 privateKey); - // Serialize a key and value to a JSON object stored in-memory that can be later written to a file - // It returns the stringified version of the specific JSON file up to that moment. - function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); - function serializeBool(string calldata objectKey, string calldata valueKey, bool value) - external - returns (string memory json); - function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) - external - returns (string memory json); - function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) + /// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language + /// at `{derivationPath}{index}`. + function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external - returns (string memory json); - function serializeAddress(string calldata objectKey, string calldata valueKey, address value) - external - returns (string memory json); - function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) - external - returns (string memory json); - function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) - external - returns (string memory json); - function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value) - external - returns (string memory json); + pure + returns (uint256 privateKey); - function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values) - external - returns (string memory json); - function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) - external - returns (string memory json); - function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) - external - returns (string memory json); - function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) - external - returns (string memory json); - function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values) - external - returns (string memory json); - function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) - external - returns (string memory json); - function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values) - external - returns (string memory json); + /// Gets the label for the specified address. + function getLabel(address account) external returns (string memory currentLabel); - // NOTE: Please read https://book.getfoundry.sh/cheatcodes/write-json to understand how - // to use the JSON writing cheats. + /// Get a `Wallet`'s nonce. + function getNonce(Wallet calldata wallet) external returns (uint64 nonce); - // Write a serialized JSON object to a file. If the file exists, it will be overwritten. - function writeJson(string calldata json, string calldata path) external; + /// Labels an address in call traces. + function label(address account, string calldata newLabel) 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 - function writeJson(string calldata json, string calldata path, string calldata valueKey) external; + /// Adds a private key to the local forge wallet and returns the address. + function rememberKey(uint256 privateKey) external returns (address keyAddr); + + /// Signs data with a `Wallet`. + function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); } -// The `Vm` interface does allow manipulation of the EVM state. These are all intended to be used -// in tests, but it is not recommended to use these cheats in scripts. +/// The `Vm` interface does allow manipulation of the EVM state. These are all intended to be used +/// in tests, but it is not recommended to use these cheats in scripts. interface Vm is VmSafe { - // ======== EVM ======== + /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. + function activeFork() external view returns (uint256 forkId); - // -------- Block and Transaction Properties -------- + /// In forking mode, explicitly grant the given address cheatcode access. + function allowCheatcodes(address account) external; - // Sets block.chainid + /// Sets `block.chainid`. function chainId(uint256 newChainId) external; - // Sets block.coinbase + /// Clears all mocked calls. + function clearMockedCalls() external; + + /// Sets `block.coinbase`. function coinbase(address newCoinbase) external; - // Sets block.difficulty - // Not available on EVM versions from Paris onwards. Use `prevrandao` instead. - // If used on unsupported EVM versions it will revert. - function difficulty(uint256 newDifficulty) external; + /// Marks the slots of an account and the account address as cold. + function cool(address target) external; - // Sets block.basefee - function fee(uint256 newBasefee) external; + /// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork. + function createFork(string calldata urlOrAlias) external returns (uint256 forkId); - // Sets block.prevrandao - // Not available on EVM versions before Paris. Use `difficulty` instead. - // If used on unsupported EVM versions it will revert. - function prevrandao(bytes32 newPrevrandao) external; + /// Creates a new fork with the given endpoint and block and returns the identifier of the fork. + function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); - // Sets block.height - function roll(uint256 newHeight) external; + /// Creates a new fork with the given endpoint and at the block the given transaction was mined in, + /// replays all transaction mined in the block before the transaction, and returns the identifier of the fork. + function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); - // Sets tx.gasprice - function txGasPrice(uint256 newGasPrice) external; + /// 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 urlOrAlias) external returns (uint256 forkId); - // Sets block.timestamp - function warp(uint256 newTimestamp) external; + /// Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork. + function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); - // -------- Account State -------- + /// Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in, + /// replays all transaction mined in the block before the transaction, returns the identifier of the fork. + function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); - // Sets an address' balance + /// Sets an address' balance. function deal(address account, uint256 newBalance) external; - // Sets an address' code - function etch(address target, bytes calldata newRuntimeBytecode) external; + /// Removes the snapshot with the given ID created by `snapshot`. + /// Takes the snapshot ID to delete. + /// Returns `true` if the snapshot was successfully deleted. + /// Returns `false` if the snapshot does not exist. + function deleteSnapshot(uint256 snapshotId) external returns (bool success); - // Load a genesis JSON file's `allocs` into the in-memory state. - function loadAllocs(string calldata pathToAllocsJson) external; + /// Removes _all_ snapshots previously created by `snapshot`. + function deleteSnapshots() external; - // Resets the nonce of an account to 0 for EOAs and 1 for contract accounts - function resetNonce(address account) external; + /// Sets `block.difficulty`. + /// Not available on EVM versions from Paris onwards. Use `prevrandao` instead. + /// Reverts if used on unsupported EVM versions. + function difficulty(uint256 newDifficulty) external; - // Sets the nonce of an account; must be higher than the current nonce of the account - function setNonce(address account, uint64 newNonce) external; + /// Sets an address' code. + function etch(address target, bytes calldata newRuntimeBytecode) external; - // Sets the nonce of an account to an arbitrary value - function setNonceUnsafe(address account, uint64 newNonce) external; + /// Sets `block.basefee`. + function fee(uint256 newBasefee) external; - // Stores a value to an address' storage slot. - function store(address target, bytes32 slot, bytes32 value) external; + /// Returns true if the account is marked as persistent. + function isPersistent(address account) external view returns (bool persistent); - // -------- Call Manipulation -------- - // --- Mocks --- + /// Load a genesis JSON file's `allocs` into the in-memory revm state. + function loadAllocs(string calldata pathToAllocsJson) external; - // Clears all mocked calls - function clearMockedCalls() 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 account) 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 callee, bytes calldata data, bytes calldata returnData) external; + /// See `makePersistent(address)`. + function makePersistent(address account0, address account1) 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 callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external; + /// See `makePersistent(address)`. + function makePersistent(address account0, address account1, address account2) external; + + /// See `makePersistent(address)`. + function makePersistent(address[] calldata accounts) external; - // Reverts a call to an address with specified revert data. + /// Reverts a call to an address with specified revert data. function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external; - // Reverts a call to an address with a specific msg.value, with specified revert data. + /// Reverts a call to an address with a specific `msg.value`, with specified revert data. function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external; - // --- Impersonation (pranks) --- + /// 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 callee, bytes calldata data, bytes calldata returnData) external; - // Sets the *next* call's msg.sender to be the input address - function prank(address msgSender) 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 callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external; - // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called - function startPrank(address msgSender) external; + /// Sets the *next* call's `msg.sender` to be the input address. + function prank(address msgSender) external; - // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input + /// Sets the *next* call's `msg.sender` to be the input address, and the `tx.origin` to be the second input. function prank(address msgSender, address txOrigin) 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 msgSender, address txOrigin) external; - - // Resets subsequent calls' msg.sender to be `address(this)` - function stopPrank() external; + /// Sets `block.prevrandao`. + /// Not available on EVM versions before Paris. Use `difficulty` instead. + /// If used on unsupported EVM versions it will revert. + function prevrandao(bytes32 newPrevrandao) external; - // Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification + /// Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification. function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); - // -------- State Snapshots -------- - - // 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 snapshotId); + /// Resets the nonce of an account to 0 for EOAs and 1 for contract accounts. + function resetNonce(address account) external; - // Revert the state of the EVM to a previous snapshot - // Takes the snapshot id to revert to. - // Returns true if the revert succeeded, false otherwise. - // - // This does not automatically delete the snapshot. To delete the snapshot use `deleteSnapshot` or `revertToAndDelete` + /// Revert the state of the EVM to a previous snapshot + /// Takes the snapshot ID to revert to. + /// Returns `true` if the snapshot was successfully reverted. + /// Returns `false` if the snapshot does not exist. + /// **Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteSnapshot`. function revertTo(uint256 snapshotId) external returns (bool success); - // Deletes the snapshot. - // Returns true if the snapshot existed, false otherwise. - // - // This does not revert to the state of the snapshot, only deletes it. - function deleteSnapshot(uint256 snapshotId) external returns (bool success); + /// Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots + /// Takes the snapshot ID to revert to. + /// Returns `true` if the snapshot was successfully reverted and deleted. + /// Returns `false` if the snapshot does not exist. + function revertToAndDelete(uint256 snapshotId) external returns (bool success); - // Deletes all snapshots. - function deleteSnapshots() external; + /// Revokes persistent status from the address, previously added via `makePersistent`. + function revokePersistent(address account) external; - // Revert the state of the EVM to a previous snapshot - // Takes the snapshot id to revert to. - // - // This also deletes the snapshot after reverting to its state. - function revertToAndDelete(uint256 snapshotId) external returns (bool success); + /// See `revokePersistent(address)`. + function revokePersistent(address[] calldata accounts) external; - // -------- Forking -------- - // --- Creation and Selection --- + /// Sets `block.height`. + function roll(uint256 newHeight) external; - // Returns the identifier of the currently active fork. Reverts if no fork is currently active. - function activeFork() external view returns (uint256 forkId); + /// Updates the currently active fork to given block number + /// This is similar to `roll` but for the currently active fork. + function rollFork(uint256 blockNumber) external; - // Creates a new fork with the given endpoint and block and returns the identifier of the fork - function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); + /// 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 txHash) external; - // Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork - function createFork(string calldata urlOrAlias) external returns (uint256 forkId); + /// Updates the given fork to given block number. + function rollFork(uint256 forkId, uint256 blockNumber) external; - // Creates a new fork with the given endpoint and at the block the given transaction was mined in, replays all transaction mined in the block before the transaction, - // and returns the identifier of the fork - function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); + /// 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 txHash) external; - // Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork - function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); + /// Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. + function selectFork(uint256 forkId) external; - // Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in, replays all transaction mined in the block before - // the transaction, returns the identifier of the fork - function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); + /// Sets the nonce of an account. Must be higher than the current nonce of the account. + function setNonce(address account, uint64 newNonce) external; - // 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 urlOrAlias) external returns (uint256 forkId); + /// Sets the nonce of an account to an arbitrary value. + function setNonceUnsafe(address account, uint64 newNonce) external; - // Updates the currently active fork to given block number - // This is similar to `roll` but for the currently active fork - function rollFork(uint256 blockNumber) 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 snapshotId); - // 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 txHash) external; + /// Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called. + function startPrank(address msgSender) external; - // Updates the given fork to given block number - function rollFork(uint256 forkId, uint256 blockNumber) 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 msgSender, address txOrigin) 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 txHash) external; + /// Resets subsequent calls' `msg.sender` to be `address(this)`. + function stopPrank() external; - // Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. - function selectFork(uint256 forkId) external; + /// Stores a value to an address' storage slot. + function store(address target, bytes32 slot, bytes32 value) external; - // Fetches the given transaction from the active fork and executes it on the current state + /// 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 + /// Fetches the given transaction from the given fork and executes it on the current state. function transact(uint256 forkId, bytes32 txHash) external; - // --- Behavior --- - - // In forking mode, explicitly grant the given address cheatcode access - function allowCheatcodes(address account) 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 account) external; - function makePersistent(address account0, address account1) external; - function makePersistent(address account0, address account1, address account2) external; - function makePersistent(address[] calldata accounts) external; + /// Sets `tx.gasprice`. + function txGasPrice(uint256 newGasPrice) external; - // Revokes persistent status from the address, previously added via `makePersistent` - function revokePersistent(address account) external; - function revokePersistent(address[] calldata accounts) external; + /// Sets `block.timestamp`. + function warp(uint256 newTimestamp) external; - // Returns true if the account is marked as persistent - function isPersistent(address account) external view returns (bool persistent); + /// Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas. + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; - // ======== Test Assertions and Utilities ======== + /// Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas. + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) + external; - // Expects a call to an address with the specified calldata. - // Calldata can either be a strict or a partial match + /// Expects a call to an address with the specified calldata. + /// Calldata can either be a strict or a partial match. function expectCall(address callee, bytes calldata data) external; - // Expects given number of calls to an address with the specified calldata. + /// Expects given number of calls to an address with the specified calldata. function expectCall(address callee, bytes calldata data, uint64 count) external; - // Expects a call to an address with the specified msg.value and calldata + /// Expects a call to an address with the specified `msg.value` and calldata. function expectCall(address callee, uint256 msgValue, bytes calldata data) external; - // Expects given number of calls to an address with the specified msg.value and calldata + /// Expects given number of calls to an address with the specified `msg.value` and calldata. function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external; - // Expect a call to an address with the specified msg.value, gas, and calldata. + /// Expect a call to an address with the specified `msg.value`, gas, and calldata. function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external; - // Expects given number of calls to an address with the specified msg.value, gas, and calldata. + /// Expects given number of calls to an address with the specified `msg.value`, gas, and calldata. function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external; - // Expect a call to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; - - // Expect given number of calls to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) - 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). + /// 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 checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; - // Same as the previous method, but also checks supplied address against emitting contract. + /// Same as the previous method, but also checks supplied address against emitting contract. function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external; - // Prepare an expected log with all topic and data checks enabled. - // 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. + /// Prepare an expected log with all topic and data checks enabled. + /// 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. function expectEmit() external; - // Same as the previous method, but also checks supplied address against emitting contract. + /// Same as the previous method, but also checks supplied address against emitting contract. function expectEmit(address emitter) external; - // Expects an error on next call that exactly matches the revert data. - function expectRevert(bytes calldata revertData) external; + /// Expects an error on next call with any revert data. + function expectRevert() external; - // Expects an error on next call that starts with the revert data. + /// Expects an error on next call that starts with the revert data. function expectRevert(bytes4 revertData) external; - // Expects an error on next call with any revert data. - function expectRevert() external; + /// Expects an error on next call that exactly matches the revert data. + function expectRevert(bytes calldata revertData) external; - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other - // memory is written to, the test will fail. Can be called multiple times to add more ranges to the set. + /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other + /// memory is written to, the test will fail. Can be called multiple times to add more ranges to the set. function expectSafeMemory(uint64 min, uint64 max) external; - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. - // If any other memory is written to, the test will fail. Can be called multiple times to add more ranges - // to the set. + /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. + /// If any other memory is written to, the test will fail. Can be called multiple times to add more ranges + /// to the set. function expectSafeMemoryCall(uint64 min, uint64 max) external; - // Marks a test as skipped. Must be called at the top of the test. + /// Marks a test as skipped. Must be called at the top of the test. function skip(bool skipTest) external; } From 88b4013787632f6d2ef85ad992b82eea8c8ab3d0 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 20 Dec 2023 22:44:17 +0100 Subject: [PATCH 02/13] fix: patch for older versions --- scripts/vm.py | 53 +++++++++++------ src/Vm.sol | 160 +++++++++++++++++++++++++------------------------- 2 files changed, 116 insertions(+), 97 deletions(-) diff --git a/scripts/vm.py b/scripts/vm.py index 3e34e612..f715c36b 100755 --- a/scripts/vm.py +++ b/scripts/vm.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import json +import re import subprocess from enum import Enum as PyEnum from typing import Callable @@ -72,10 +73,21 @@ def main(): pp.p_contract(vm_unsafe, "Vm", "VmSafe") out += pp.finish() + # Compatibility with <0.8.0 + def memory_to_calldata(m: re.Match) -> str: + return " calldata " + m.group(1) + + out = re.sub(r" memory (.*returns)", memory_to_calldata, out) + out = out.replace("address addr", "address addr_") + out = out.replace( + "AccountAccess[] memory accesses", "AccountAccess[] memory accesses_" + ) + with open(OUT_PATH, "w") as f: f.write(out) - subprocess.run(["forge", "fmt", OUT_PATH]) + res = subprocess.run(["forge", "fmt", OUT_PATH]) + assert res.returncode == 0 print(f"Wrote to {OUT_PATH}") @@ -499,7 +511,7 @@ def p_errors(self, errors: list[Error]): self._p_line(lambda: self.p_error(error)) def p_error(self, error: Error): - self._p_doc(error.description) + self._p_comment(error.description, doc=True) self._p_line(lambda: self._p_str(error.declaration)) def p_events(self, events: list[Event]): @@ -507,7 +519,7 @@ def p_events(self, events: list[Event]): self._p_line(lambda: self.p_event(event)) def p_event(self, event: Event): - self._p_doc(event.description) + self._p_comment(event.description, doc=True) self._p_line(lambda: self._p_str(event.declaration)) def p_enums(self, enums: list[Enum]): @@ -515,7 +527,7 @@ def p_enums(self, enums: list[Enum]): self._p_line(lambda: self.p_enum(enum)) def p_enum(self, enum: Enum): - self._p_doc(enum.description) + self._p_comment(enum.description, doc=True) self._p_line(lambda: self._p_str(f"enum {enum.name} {{")) self._with_indent(lambda: self.p_enum_variants(enum.variants)) self._p_line(lambda: self._p_str("}")) @@ -523,7 +535,7 @@ def p_enum(self, enum: Enum): def p_enum_variants(self, variants: list[EnumVariant]): for i, variant in enumerate(variants): self._p_indent() - self._p_doc(variant.description) + self._p_comment(variant.description) self._p_indent() self._p_str(variant.name) @@ -536,7 +548,7 @@ def p_structs(self, structs: list[Struct]): self._p_line(lambda: self.p_struct(struct)) def p_struct(self, struct: Struct): - self._p_doc(struct.description) + self._p_comment(struct.description, doc=True) self._p_line(lambda: self._p_str(f"struct {struct.name} {{")) self._with_indent(lambda: self.p_struct_fields(struct.fields)) self._p_line(lambda: self._p_str("}")) @@ -546,7 +558,7 @@ def p_struct_fields(self, fields: list[StructField]): self._p_line(lambda: self.p_struct_field(field)) def p_struct_field(self, field: StructField): - self._p_doc(field.description) + self._p_comment(field.description) self._p_indented(lambda: self._p_str(f"{field.ty} {field.name};")) def p_functions(self, cheatcodes: list[Cheatcode]): @@ -554,21 +566,25 @@ def p_functions(self, cheatcodes: list[Cheatcode]): self._p_line(lambda: self.p_function(cheatcode.func)) def p_function(self, func: Function): - self._p_doc(func.description) + self._p_comment(func.description, doc=True) self._p_line(lambda: self._p_str(func.declaration)) - def _p_doc(self, doc: str): - doc = doc.strip() - if doc == "": + def _p_comment(self, s: str, doc: bool = False): + s = s.strip() + if s == "": return - doc = map(lambda line: line.lstrip(), doc.split("\n")) + s = map(lambda line: line.lstrip(), s.split("\n")) if self.block_doc_style: - self._p_str("/**") + self._p_str("/*") + if doc: + self._p_str("*") self._p_nl() - for line in doc: + for line in s: self._p_indent() - self._p_str(" * ") + self._p_str(" ") + if doc: + self._p_str("* ") self._p_str(line) self._p_nl() self._p_indent() @@ -576,12 +592,15 @@ def _p_doc(self, doc: str): self._p_nl() else: first_line = True - for line in doc: + for line in s: if not first_line: self._p_indent() first_line = False - self._p_str("/// ") + if doc: + self._p_str("/// ") + else: + self._p_str("// ") self._p_str(line) self._p_nl() diff --git a/src/Vm.sol b/src/Vm.sol index 8148eec6..8a174eac 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -10,97 +10,97 @@ pragma experimental ABIEncoderV2; interface VmSafe { /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. enum CallerMode - /// No caller modification is currently active. + // No caller modification is currently active. { None, - /// A one time broadcast triggered by a `vm.broadcast()` call is currently active. + // A one time broadcast triggered by a `vm.broadcast()` call is currently active. Broadcast, - /// A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active. + // A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active. RecurrentBroadcast, - /// A one time prank triggered by a `vm.prank()` call is currently active. + // A one time prank triggered by a `vm.prank()` call is currently active. Prank, - /// A recurrent prank triggered by a `vm.startPrank()` call is currently active. + // A recurrent prank triggered by a `vm.startPrank()` call is currently active. RecurrentPrank } /// The kind of account access that occurred. enum AccountAccessKind - /// The account was called. + // The account was called. { Call, - /// The account was called via delegatecall. + // The account was called via delegatecall. DelegateCall, - /// The account was called via callcode. + // The account was called via callcode. CallCode, - /// The account was called via staticcall. + // The account was called via staticcall. StaticCall, - /// The account was created. + // The account was created. Create, - /// The account was selfdestructed. + // The account was selfdestructed. SelfDestruct, - /// Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess). + // Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess). Resume, - /// The account's balance was read. + // The account's balance was read. Balance, - /// The account's codesize was read. + // The account's codesize was read. Extcodesize, - /// The account's code was copied. + // The account's code was copied. Extcodecopy, - /// The account's codehash was read. + // The account's codehash was read. Extcodehash } /// An Ethereum log. Returned by `getRecordedLogs`. struct Log { - /// The topics of the log, including the signature, if any. + // The topics of the log, including the signature, if any. bytes32[] topics; - /// The raw data of the log. + // The raw data of the log. bytes data; - /// The address of the log's emitter. + // The address of the log's emitter. address emitter; } /// An RPC URL and its alias. Returned by `rpcUrlStructs`. struct Rpc { - /// The alias of the RPC URL. + // The alias of the RPC URL. string key; - /// The RPC URL. + // The RPC URL. string url; } /// An RPC log object. Returned by `eth_getLogs`. struct EthGetLogs { - /// The address of the log's emitter. + // The address of the log's emitter. address emitter; - /// The topics of the log, including the signature, if any. + // The topics of the log, including the signature, if any. bytes32[] topics; - /// The raw data of the log. + // The raw data of the log. bytes data; - /// The block hash. + // The block hash. bytes32 blockHash; - /// The block number. + // The block number. uint64 blockNumber; - /// The transaction hash. + // The transaction hash. bytes32 transactionHash; - /// The transaction index in the block. + // The transaction index in the block. uint64 transactionIndex; - /// The log index. + // The log index. uint256 logIndex; - /// Whether the log was removed. + // Whether the log was removed. bool removed; } /// A single entry in a directory listing. Returned by `readDir`. struct DirEntry { - /// The error message, if any. + // The error message, if any. string errorMessage; - /// The path of the entry. + // The path of the entry. string path; - /// The depth of the entry. + // The depth of the entry. uint64 depth; - /// Whether the entry is a directory. + // Whether the entry is a directory. bool isDir; - /// Whether the entry is a symlink. + // Whether the entry is a symlink. bool isSymlink; } @@ -109,101 +109,101 @@ interface VmSafe { /// metadata about a file such as its permissions, size, modification /// times, etc. struct FsMetadata { - /// True if this metadata is for a directory. + // True if this metadata is for a directory. bool isDir; - /// True if this metadata is for a symlink. + // True if this metadata is for a symlink. bool isSymlink; - /// The size of the file, in bytes, this metadata is for. + // The size of the file, in bytes, this metadata is for. uint256 length; - /// True if this metadata is for a readonly (unwritable) file. + // True if this metadata is for a readonly (unwritable) file. bool readOnly; - /// The last modification time listed in this metadata. + // The last modification time listed in this metadata. uint256 modified; - /// The last access time of this metadata. + // The last access time of this metadata. uint256 accessed; - /// The creation time listed in this metadata. + // The creation time listed in this metadata. uint256 created; } /// A wallet with a public and private key. struct Wallet { - /// The wallet's address. - address addr; - /// The wallet's public key `X`. + // The wallet's address. + address addr_; + // The wallet's public key `X`. uint256 publicKeyX; - /// The wallet's public key `Y`. + // The wallet's public key `Y`. uint256 publicKeyY; - /// The wallet's private key. + // The wallet's private key. uint256 privateKey; } /// The result of a `tryFfi` call. struct FfiResult { - /// The exit code of the call. + // The exit code of the call. int32 exitCode; - /// The optionally hex-decoded `stdout` data. + // The optionally hex-decoded `stdout` data. bytes stdout; - /// The `stderr` data. + // The `stderr` data. bytes stderr; } /// Information on the chain and fork. struct ChainInfo { - /// The fork identifier. Set to zero if no fork is active. + // The fork identifier. Set to zero if no fork is active. uint256 forkId; - /// The chain ID of the current fork. + // The chain ID of the current fork. uint256 chainId; } /// The result of a `stopAndReturnStateDiff` call. struct AccountAccess { - /// The chain and fork the access occurred. + // The chain and fork the access occurred. ChainInfo chainInfo; - /// The kind of account access that determines what the account is. - /// If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee. - /// If kind is Create, then the account is the newly created account. - /// If kind is SelfDestruct, then the account is the selfdestruct recipient. - /// If kind is a Resume, then account represents a account context that has resumed. + // The kind of account access that determines what the account is. + // If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee. + // If kind is Create, then the account is the newly created account. + // If kind is SelfDestruct, then the account is the selfdestruct recipient. + // If kind is a Resume, then account represents a account context that has resumed. AccountAccessKind kind; - /// The account that was accessed. - /// It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT. + // The account that was accessed. + // It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT. address account; - /// What accessed the account. + // What accessed the account. address accessor; - /// If the account was initialized or empty prior to the access. - /// An account is considered initialized if it has code, a - /// non-zero nonce, or a non-zero balance. + // If the account was initialized or empty prior to the access. + // An account is considered initialized if it has code, a + // non-zero nonce, or a non-zero balance. bool initialized; - /// The previous balance of the accessed account. + // The previous balance of the accessed account. uint256 oldBalance; - /// The potential new balance of the accessed account. - /// That is, all balance changes are recorded here, even if reverts occurred. + // The potential new balance of the accessed account. + // That is, all balance changes are recorded here, even if reverts occurred. uint256 newBalance; - /// Code of the account deployed by CREATE. + // Code of the account deployed by CREATE. bytes deployedCode; - /// Value passed along with the account access + // Value passed along with the account access uint256 value; - /// Input data provided to the CREATE or CALL + // Input data provided to the CREATE or CALL bytes data; - /// If this access reverted in either the current or parent context. + // If this access reverted in either the current or parent context. bool reverted; - /// An ordered list of storage accesses made during an account access operation. + // An ordered list of storage accesses made during an account access operation. StorageAccess[] storageAccesses; } /// The storage accessed during an `AccountAccess`. struct StorageAccess { - /// The account whose storage was accessed. + // The account whose storage was accessed. address account; - /// The slot that was accessed. + // The slot that was accessed. bytes32 slot; - /// If the access was a write. + // If the access was a write. bool isWrite; - /// The previous value of the slot. + // The previous value of the slot. bytes32 previousValue; - /// The new value of the slot. + // The new value of the slot. bytes32 newValue; - /// If the access was reverted. + // If the access was reverted. bool reverted; } @@ -357,7 +357,7 @@ interface VmSafe { function addr(uint256 privateKey) external pure returns (address keyAddr); /// Gets all the logs according to specified filter. - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address addr, bytes32[] memory topics) + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address addr_, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs); @@ -408,7 +408,7 @@ interface VmSafe { function startStateDiffRecording() external; /// Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session. - function stopAndReturnStateDiff() external returns (AccountAccess[] memory accesses); + function stopAndReturnStateDiff() external returns (AccountAccess[] memory accesses_); /// Stops recording all map SSTOREs for later retrieval and clears the recorded data. function stopMappingRecording() external; From 1255513778c91234f3385565d91243fda9739a8d Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 20 Dec 2023 22:47:25 +0100 Subject: [PATCH 03/13] fix: stricter replace --- scripts/vm.py | 4 ++-- src/Vm.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/vm.py b/scripts/vm.py index f715c36b..643b1088 100755 --- a/scripts/vm.py +++ b/scripts/vm.py @@ -78,9 +78,9 @@ def memory_to_calldata(m: re.Match) -> str: return " calldata " + m.group(1) out = re.sub(r" memory (.*returns)", memory_to_calldata, out) - out = out.replace("address addr", "address addr_") + out = out.replace("address addr,", "address addr_,") out = out.replace( - "AccountAccess[] memory accesses", "AccountAccess[] memory accesses_" + "AccountAccess[] memory accesses)", "AccountAccess[] memory accesses_)" ) with open(OUT_PATH, "w") as f: diff --git a/src/Vm.sol b/src/Vm.sol index 8a174eac..cde9c3d0 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -128,7 +128,7 @@ interface VmSafe { /// A wallet with a public and private key. struct Wallet { // The wallet's address. - address addr_; + address addr; // The wallet's public key `X`. uint256 publicKeyX; // The wallet's public key `Y`. From 4081900932daf25164a0146648215189560153fa Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 20 Dec 2023 23:11:53 +0100 Subject: [PATCH 04/13] chore: update --- scripts/vm.py | 4 ---- src/Vm.sol | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/vm.py b/scripts/vm.py index 643b1088..9f76312a 100755 --- a/scripts/vm.py +++ b/scripts/vm.py @@ -78,10 +78,6 @@ def memory_to_calldata(m: re.Match) -> str: return " calldata " + m.group(1) out = re.sub(r" memory (.*returns)", memory_to_calldata, out) - out = out.replace("address addr,", "address addr_,") - out = out.replace( - "AccountAccess[] memory accesses)", "AccountAccess[] memory accesses_)" - ) with open(OUT_PATH, "w") as f: f.write(out) diff --git a/src/Vm.sol b/src/Vm.sol index cde9c3d0..b18fe62d 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -357,7 +357,7 @@ interface VmSafe { function addr(uint256 privateKey) external pure returns (address keyAddr); /// Gets all the logs according to specified filter. - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address addr_, bytes32[] calldata topics) + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs); @@ -408,7 +408,7 @@ interface VmSafe { function startStateDiffRecording() external; /// Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session. - function stopAndReturnStateDiff() external returns (AccountAccess[] memory accesses_); + function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); /// Stops recording all map SSTOREs for later retrieval and clears the recorded data. function stopMappingRecording() external; From 8606215a788fb2105fbd77ede56ebac3c2604a02 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 20 Dec 2023 23:26:38 +0100 Subject: [PATCH 05/13] test: update interface ID test --- test/Vm.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Vm.t.sol b/test/Vm.t.sol index 4e97f1f1..7622ca82 100644 --- a/test/Vm.t.sol +++ b/test/Vm.t.sol @@ -9,7 +9,7 @@ contract VmTest is Test { // inadvertently moved between Vm and VmSafe. This test must be updated each time a function is // added to or removed from Vm or VmSafe. function test_interfaceId() public { - assertEq(type(VmSafe).interfaceId, bytes4(0x5e4a68ae), "VmSafe"); - assertEq(type(Vm).interfaceId, bytes4(0xd6a02054), "Vm"); + assertEq(type(VmSafe).interfaceId, bytes4(0x45a144dc), "VmSafe"); + assertEq(type(Vm).interfaceId, bytes4(0x965fbf75), "Vm"); } } From bdd99152f2756449f42425dbc0a7d6358b097833 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 20 Dec 2023 23:52:22 +0100 Subject: [PATCH 06/13] chore: update --- scripts/vm.py | 11 +++++++---- src/Vm.sol | 10 ++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/vm.py b/scripts/vm.py index 9f76312a..c9056553 100755 --- a/scripts/vm.py +++ b/scripts/vm.py @@ -27,13 +27,16 @@ def main(): json_str = request.urlopen(CHEATCODES_JSON_URL).read().decode("utf-8") contract = Cheatcodes.from_json(json_str) - contract.cheatcodes.sort(key=lambda cc: cc.func.id) - safe = list(filter(lambda cc: cc.safety == Safety.SAFE, contract.cheatcodes)) + ccs = contract.cheatcodes + ccs = list(filter(lambda cc: cc.status != Status.EXPERIMENTAL, ccs)) + ccs.sort(key=lambda cc: cc.func.id) + + safe = list(filter(lambda cc: cc.safety == Safety.SAFE, ccs)) safe.sort(key=CmpCheatcode) - unsafe = list(filter(lambda cc: cc.safety == Safety.UNSAFE, contract.cheatcodes)) + unsafe = list(filter(lambda cc: cc.safety == Safety.UNSAFE, ccs)) unsafe.sort(key=CmpCheatcode) - assert len(safe) + len(unsafe) == len(contract.cheatcodes) + assert len(safe) + len(unsafe) == len(ccs) out = "" diff --git a/src/Vm.sol b/src/Vm.sol index b18fe62d..2d41a011 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -9,9 +9,8 @@ pragma experimental ABIEncoderV2; /// these cheats in scripts. interface VmSafe { /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. - enum CallerMode - // No caller modification is currently active. - { + enum CallerMode { + // No caller modification is currently active. None, // A one time broadcast triggered by a `vm.broadcast()` call is currently active. Broadcast, @@ -24,9 +23,8 @@ interface VmSafe { } /// The kind of account access that occurred. - enum AccountAccessKind - // The account was called. - { + enum AccountAccessKind { + // The account was called. Call, // The account was called via delegatecall. DelegateCall, From ea0b9458583a881fda90f07efc311f9ee9001359 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 21 Dec 2023 00:14:38 +0100 Subject: [PATCH 07/13] feat: header comments --- scripts/vm.py | 27 ++++++++++++++++++++++++++- src/Vm.sol | 20 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/scripts/vm.py b/scripts/vm.py index c9056553..7658f5b7 100755 --- a/scripts/vm.py +++ b/scripts/vm.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import copy import json import re import subprocess @@ -34,9 +35,10 @@ def main(): safe = list(filter(lambda cc: cc.safety == Safety.SAFE, ccs)) safe.sort(key=CmpCheatcode) + prefix_with_group_comment(safe) unsafe = list(filter(lambda cc: cc.safety == Safety.UNSAFE, ccs)) unsafe.sort(key=CmpCheatcode) - assert len(safe) + len(unsafe) == len(ccs) + prefix_with_group_comment(unsafe) out = "" @@ -119,6 +121,22 @@ def cmp_cheatcode(a: "Cheatcode", b: "Cheatcode") -> int: return 0 +# HACK: A way to add group header comments without having to modify printer code +def prefix_with_group_comment(cheats: list["Cheatcode"]): + s = set() + for i, cheat in enumerate(cheats): + if cheat.group in s: + continue + + s.add(cheat.group) + + c = copy.deepcopy(cheat) + c.func.description = "" + c.func.declaration = f"// ======== {c.group} ========" + cheats.insert(i, c) + return cheats + + class Status(PyEnum): STABLE: str = "stable" EXPERIMENTAL: str = "experimental" @@ -139,6 +157,13 @@ class Group(PyEnum): JSON: str = "json" UTILITIES: str = "utilities" + def __str__(self): + if self == Group.EVM: + return "EVM" + if self == Group.JSON: + return "JSON" + return self.value[0].upper() + self.value[1:] + def __lt__(self, other: "Group") -> bool: return self.value < other.value diff --git a/src/Vm.sol b/src/Vm.sol index 2d41a011..06ed1e6e 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -205,6 +205,8 @@ interface VmSafe { bool reverted; } + // ======== Environment ======== + /// Gets the environment variable `name` and parses it as `address`. /// Reverts if the variable was not found or could not be parsed. function envAddress(string calldata name) external view returns (address value); @@ -348,6 +350,8 @@ interface VmSafe { /// Sets environment variables. function setEnv(string calldata name, string calldata value) external; + // ======== EVM ======== + /// Gets all accessed reads and write slot from a `vm.record` session, for a given address. function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); @@ -411,6 +415,8 @@ interface VmSafe { /// Stops recording all map SSTOREs for later retrieval and clears the recorded data. function stopMappingRecording() external; + // ======== Filesystem ======== + /// Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. /// `path` is relative to the project root. function closeFile(string calldata path) external; @@ -516,6 +522,8 @@ interface VmSafe { /// `path` is relative to the project root. function writeLine(string calldata path, string calldata data) external; + // ======== JSON ======== + /// Checks if `key` exists in a JSON object. function keyExists(string calldata json, string calldata key) external view returns (bool); @@ -657,6 +665,8 @@ interface VmSafe { /// This is useful to replace a specific value of a JSON file, without having to parse the entire thing. function writeJson(string calldata json, string calldata path, string calldata valueKey) external; + // ======== Scripting ======== + /// 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; @@ -684,6 +694,8 @@ interface VmSafe { /// Stops collecting onchain transactions. function stopBroadcast() external; + // ======== String ======== + /// Parses the given `string` into an `address`. function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue); @@ -720,6 +732,8 @@ interface VmSafe { /// Converts the given value to a `string`. function toString(int256 value) external pure returns (string memory stringifiedValue); + // ======== Testing ======== + /// If the condition is false, discard this run's fuzz inputs and generate new ones. function assume(bool condition) external pure; @@ -741,6 +755,8 @@ interface VmSafe { /// Suspends execution of the main thread for `duration` milliseconds. function sleep(uint256 duration) external; + // ======== Utilities ======== + /// Compute the address of a contract created with CREATE2 using the given CREATE2 deployer. function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external @@ -806,6 +822,8 @@ interface VmSafe { /// The `Vm` interface does allow manipulation of the EVM state. These are all intended to be used /// in tests, but it is not recommended to use these cheats in scripts. interface Vm is VmSafe { + // ======== EVM ======== + /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. function activeFork() external view returns (uint256 forkId); @@ -994,6 +1012,8 @@ interface Vm is VmSafe { /// Sets `block.timestamp`. function warp(uint256 newTimestamp) external; + // ======== Testing ======== + /// Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas. function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; From ada2961515679360971500d43c6eaac842cfcae3 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 21 Dec 2023 00:23:11 +0100 Subject: [PATCH 08/13] chore: update function name --- scripts/vm.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/vm.py b/scripts/vm.py index 7658f5b7..55f4f1cb 100755 --- a/scripts/vm.py +++ b/scripts/vm.py @@ -35,10 +35,10 @@ def main(): safe = list(filter(lambda cc: cc.safety == Safety.SAFE, ccs)) safe.sort(key=CmpCheatcode) - prefix_with_group_comment(safe) + prefix_with_group_headers(safe) unsafe = list(filter(lambda cc: cc.safety == Safety.UNSAFE, ccs)) unsafe.sort(key=CmpCheatcode) - prefix_with_group_comment(unsafe) + prefix_with_group_headers(unsafe) out = "" @@ -87,8 +87,9 @@ def memory_to_calldata(m: re.Match) -> str: with open(OUT_PATH, "w") as f: f.write(out) - res = subprocess.run(["forge", "fmt", OUT_PATH]) - assert res.returncode == 0 + forge_fmt = ["forge", "fmt", OUT_PATH] + res = subprocess.run(forge_fmt) + assert res.returncode == 0, f"command failed: {forge_fmt}" print(f"Wrote to {OUT_PATH}") @@ -122,7 +123,7 @@ def cmp_cheatcode(a: "Cheatcode", b: "Cheatcode") -> int: # HACK: A way to add group header comments without having to modify printer code -def prefix_with_group_comment(cheats: list["Cheatcode"]): +def prefix_with_group_headers(cheats: list["Cheatcode"]): s = set() for i, cheat in enumerate(cheats): if cheat.group in s: From 112db58f4b46b5b24ee2ab2ea30f98bd1875d747 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 21 Dec 2023 00:39:43 +0100 Subject: [PATCH 09/13] chore: update --- src/Vm.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Vm.sol b/src/Vm.sol index 06ed1e6e..4ef18891 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -42,10 +42,10 @@ interface VmSafe { Balance, // The account's codesize was read. Extcodesize, - // The account's code was copied. - Extcodecopy, // The account's codehash was read. - Extcodehash + Extcodehash, + // The account's code was copied. + Extcodecopy } /// An Ethereum log. Returned by `getRecordedLogs`. @@ -839,9 +839,6 @@ interface Vm is VmSafe { /// Sets `block.coinbase`. function coinbase(address newCoinbase) external; - /// Marks the slots of an account and the account address as cold. - function cool(address target) external; - /// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork. function createFork(string calldata urlOrAlias) external returns (uint256 forkId); From 8f6dbc3d397d1f7ca6fb1b51f249e734c542c362 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:48:20 +0100 Subject: [PATCH 10/13] fmt --- test/StdError.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/StdError.t.sol b/test/StdError.t.sol index a6da60b7..a306eaa7 100644 --- a/test/StdError.t.sol +++ b/test/StdError.t.sol @@ -63,7 +63,9 @@ contract StdErrorsTest is Test { } contract ErrorsTest { - enum T {T1} + enum T { + T1 + } uint256[] public someArr; bytes someBytes; From 13edbd94e69a77063bca9f9bd2e46e5f88826f98 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:55:53 +0100 Subject: [PATCH 11/13] test: update interfaceId (removed cool) --- test/Vm.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Vm.t.sol b/test/Vm.t.sol index 7622ca82..6b086769 100644 --- a/test/Vm.t.sol +++ b/test/Vm.t.sol @@ -10,6 +10,6 @@ contract VmTest is Test { // added to or removed from Vm or VmSafe. function test_interfaceId() public { assertEq(type(VmSafe).interfaceId, bytes4(0x45a144dc), "VmSafe"); - assertEq(type(Vm).interfaceId, bytes4(0x965fbf75), "Vm"); + assertEq(type(Vm).interfaceId, bytes4(0xd6a02054), "Vm"); } } From a2ad76ce1e2687df6efad641dcd07ba6b4b08e9c Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 21 Dec 2023 10:10:20 +0100 Subject: [PATCH 12/13] chore: re-add assertion --- scripts/vm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/vm.py b/scripts/vm.py index 55f4f1cb..eefb4b7e 100755 --- a/scripts/vm.py +++ b/scripts/vm.py @@ -35,9 +35,11 @@ def main(): safe = list(filter(lambda cc: cc.safety == Safety.SAFE, ccs)) safe.sort(key=CmpCheatcode) - prefix_with_group_headers(safe) unsafe = list(filter(lambda cc: cc.safety == Safety.UNSAFE, ccs)) unsafe.sort(key=CmpCheatcode) + assert len(safe) + len(unsafe) == len(ccs) + + prefix_with_group_headers(safe) prefix_with_group_headers(unsafe) out = "" From bfdaa9c518038c6d736d6a562c0dede03d80d396 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 21 Dec 2023 10:11:34 +0100 Subject: [PATCH 13/13] chore: update with getBlockNumber, getBlockTime --- src/Vm.sol | 12 ++++++++++++ test/Vm.t.sol | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Vm.sol b/src/Vm.sol index 4ef18891..2f6113be 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -363,6 +363,18 @@ interface VmSafe { external returns (EthGetLogs[] memory logs); + /// Gets the current `block.number`. + /// You should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + function getBlockNumber() external view returns (uint256 height); + + /// Gets the current `block.timestamp`. + /// You should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + function getBlockTimestamp() external view returns (uint256 timestamp); + /// Gets the map key and parent of a mapping at a given slot, for a given address. function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external diff --git a/test/Vm.t.sol b/test/Vm.t.sol index 6b086769..dcea7673 100644 --- a/test/Vm.t.sol +++ b/test/Vm.t.sol @@ -9,7 +9,7 @@ contract VmTest is Test { // inadvertently moved between Vm and VmSafe. This test must be updated each time a function is // added to or removed from Vm or VmSafe. function test_interfaceId() public { - assertEq(type(VmSafe).interfaceId, bytes4(0x45a144dc), "VmSafe"); + assertEq(type(VmSafe).interfaceId, bytes4(0x7e017c39), "VmSafe"); assertEq(type(Vm).interfaceId, bytes4(0xd6a02054), "Vm"); } }