From 1982a2ffacdd72addc9918e1c685ac4771b850a3 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Fri, 22 Aug 2025 17:17:42 +0200 Subject: [PATCH 01/54] feat: config helper contract --- src/Config.sol | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 src/Config.sol diff --git a/src/Config.sol b/src/Config.sol new file mode 100644 index 00000000..4a3e8fd7 --- /dev/null +++ b/src/Config.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {VmSafe} from "./Vm.sol"; + +/** + * @title Config + * @notice An abstract contract to parse a TOML configuration file and load its + * variables into structured storage on deployment. + * @dev This contract assumes a flat TOML structure where top-level keys + * represent chain IDs or profiles, and the keys under them are the + * configuration variables. Nested tables are ignored by this implementation. + * + * Supported TOML Format: + * ``` + * [mainnet] + * endpoint_url = "https://eth.llamarpc.com" + * + * [mainnet.vars] + * is_live = true + * weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + * whitelisted_admins = [ + * "0x0000000000000000000000000000000000000001", + * "0x0000000000000000000000000000000000000002" + * ] + * + * [optimism] + * endpoint_url = "https://mainnet.optimism.io" + * + * [optimism.vars] + * is_live = false + * weth = "0x4200000000000000000000000000000000000006" + * ``` + */ +abstract contract Config { + VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + + // --- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ + + // Storage for the configured RPC url. + mapping(uint256 chainId => string url) private rpcOf; + + // Storage for variable types. + mapping(uint256 chainId => mapping(string key => bool value)) private boolsOf; + mapping(uint256 chainId => mapping(string key => uint256 value)) private uintsOf; + mapping(uint256 chainId => mapping(string key => address value)) private addressesOf; + mapping(uint256 chainId => mapping(string key => bytes32 value)) private bytes32sOf; + mapping(uint256 chainId => mapping(string key => string value)) private stringsOf; + mapping(uint256 chainId => mapping(string key => bytes value)) private bytesOf; + + // Storage for array variable types. + mapping(uint256 chainId => mapping(string key => bool[] value)) private boolArraysOf; + mapping(uint256 chainId => mapping(string key => uint256[] value)) private uintArraysOf; + mapping(uint256 chainId => mapping(string key => address[] value)) private addressArraysOf; + mapping(uint256 chainId => mapping(string key => bytes32[] value)) private bytes32ArraysOf; + mapping(uint256 chainId => mapping(string key => string[] value)) private stringArraysOf; + mapping(uint256 chainId => mapping(string key => bytes[] value)) private bytesArraysOf; + + // --- CONSTRUCTOR --------------------------------------------------------- + + /// @notice Reads the TOML file and iterates through each top-level key, which is + /// assumed to be a chain name or ID. For each chain, it caches its RPC + /// endpoint and all variables defined in its `vars` sub-table. + /// + /// The constructor uses a series of try-catch blocks to determine the type + /// of each variable. It attempts to parse array types (e.g., `address[]`) + /// before their singular counterparts (`address`) to ensure correct type + /// inference. If a variable cannot be parsed as any of the supported types, + /// the constructor will revert with an error. + /// + /// @param configFilePath: The local path to the TOML configuration file. + constructor(string memory configFilePath) { + string memory content = vm.readFile(configFilePath); + string[] memory chain_keys = vm.parseTomlKeys(content, "$"); + + // Cache the entire configuration to storage + for (uint i = 0; i < chain_keys.length; i++) { + string memory chain_key = chain_keys[i]; + uint256 chain_id = resolveChainId(chain_key); + + // Cache the configure rpc endpoint for that chain. + // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured. + try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { + rpcOf[chain_id] = url; + } catch { + rpcOf[chain_id] = vm.rpcUrl(chain_key); + } + + string memory path_to_chain = string.concat("$.", chain_key, ".vars"); + string[] memory var_keys = vm.parseTomlKeys(content, path_to_chain); + + // Parse and cache variables + for (uint j = 0; j < var_keys.length; j++) { + string memory var_key = var_keys[j]; + string memory path_to_var = string.concat(path_to_chain, ".", var_key); + + // Attempt to parse as a boolean array + try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { + boolArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a boolean + try vm.parseTomlBool(content, path_to_var) returns (bool val) { + boolsOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as an address array + try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { + addressArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as an address + try vm.parseTomlAddress(content, path_to_var) returns (address val) { + addressesOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a uint256 array + try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { + uintArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a uint256 + try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { + uintsOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a bytes32 array + try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { + bytes32ArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a bytes32 + try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { + bytes32sOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a bytes array + try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { + bytesArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as bytes + try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { + bytesOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a string array + try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { + stringArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a string (last, as it can be a fallback) + try vm.parseTomlString(content, path_to_var) returns (string memory val) { + stringsOf[chain_id][var_key] = val; + continue; + } catch {} + + revert(string.concat("unable to parse variable: '", var_key, "' from '[", chain_key, "']")); + } + } + } + + // --- HELPER FUNCTIONS ---------------------------------------------------- + + function resolveChainId(string memory aliasOrId) private view returns (uint256) { + try vm.parseUint(aliasOrId) returns (uint256 chainId) { + return chainId; + } catch { + try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) { + return chainInfo.chainId; + } catch { + revert(string.concat("chain key: '", aliasOrId, "' is not a valid alias nor a number.")); + } + } + } + + // --- GETTER FUNCTIONS ---------------------------------------------------- + + function readBool(uint256 chain_id, string memory key) public view returns (bool) { + return boolsOf[chain_id][key]; + } + + function readUint(uint256 chain_id, string memory key) public view returns (uint256) { + return uintsOf[chain_id][key]; + } + + function readAddress(uint256 chain_id, string memory key) public view returns (address) { + return addressesOf[chain_id][key]; + } + + function readBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { + return bytes32sOf[chain_id][key]; + } + + function readString(uint256 chain_id, string memory key) public view returns (string memory) { + return stringsOf[chain_id][key]; + } + + function readBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { + return bytesOf[chain_id][key]; + } + + function readBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { + return boolArraysOf[chain_id][key]; + } + + function readUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { + return uintArraysOf[chain_id][key]; + } + + function readAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { + return addressArraysOf[chain_id][key]; + } + + function readBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { + return bytes32ArraysOf[chain_id][key]; + } + + function readStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { + return stringArraysOf[chain_id][key]; + } + + function readBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { + return bytesArraysOf[chain_id][key]; + } +} From 03c82f02d7fc76adf4f30685dda22a41e24cd4ba Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Sat, 23 Aug 2025 08:58:20 +0200 Subject: [PATCH 02/54] feat: auto-load config --- src/Base.sol | 53 ++++- src/Config.sol | 237 -------------------- src/Script.sol | 3 +- src/StdConfig.sol | 561 ++++++++++++++++++++++++++++++++++++++++++++++ src/Test.sol | 3 +- src/Vm.sol | 144 +----------- 6 files changed, 622 insertions(+), 379 deletions(-) delete mode 100644 src/Config.sol create mode 100644 src/StdConfig.sol diff --git a/src/Base.sol b/src/Base.sol index 5b618c67..8961dc6f 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -2,28 +2,35 @@ pragma solidity >=0.6.2 <0.9.0; import {StdStorage} from "./StdStorage.sol"; +import {StdConfig} from "./StdConfig.sol"; import {Vm, VmSafe} from "./Vm.sol"; abstract contract CommonBase { /// @dev Cheat code address. /// Calculated as `address(uint160(uint256(keccak256("hevm cheat code"))))`. address internal constant VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + /// @dev console.sol and console2.sol work by executing a staticcall to this address. /// Calculated as `address(uint160(uint88(bytes11("console.log"))))`. address internal constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67; + /// @dev Used when deploying with create2. /// Taken from https://github.com/Arachnid/deterministic-deployment-proxy. address internal constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + /// @dev The default address for tx.origin and msg.sender. /// Calculated as `address(uint160(uint256(keccak256("foundry default caller"))))`. address internal constant DEFAULT_SENDER = 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38; + /// @dev The address of the first contract `CREATE`d by a running test contract. /// When running tests, each test contract is `CREATE`d by `DEFAULT_SENDER` with nonce 1. /// Calculated as `VM.computeCreateAddress(VM.computeCreateAddress(DEFAULT_SENDER, 1), 1)`. address internal constant DEFAULT_TEST_CONTRACT = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; + /// @dev Deterministic deployment address of the Multicall3 contract. /// Taken from https://www.multicall3.com. address internal constant MULTICALL3_ADDRESS = 0xcA11bde05977b3631167028862bE2a173976CA11; + /// @dev The order of the secp256k1 curve. uint256 internal constant SECP256K1_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337; @@ -35,8 +42,50 @@ abstract contract CommonBase { StdStorage internal stdstore; } -abstract contract TestBase is CommonBase {} -abstract contract ScriptBase is CommonBase { +/// @notice Boilerplate to streamline the setup of multi-chain testing environments. +abstract contract CommonConfig is CommonBase { + + // -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------ + + StdConfig internal config; + uint256[] internal chainIds; + mapping(uint256 chainId => uint256 forkId) internal forkOf; + + // -- HELPER FUNCTIONS ----------------------------------------------------- + + /// @notice Loads configuration from a file. + /// + /// @dev This function instantiates a `Config` contract, caching all its config variables. + /// + /// @param filePath: the path to the TOML configuration file. + function _loadConfig(string memory filePath) internal { + config = new StdConfig(filePath); + vm.makePersistent(address(config)); + } + + /// @notice Loads configuration from a file and creates forks for each specified chain. + /// + /// @dev This function instantiates a `Config` contract, caching all its config variables, + /// reads the configured chain ids, and iterates through them to create a fork for each one. + /// It also creates a map `forkOf[chainId] -> forkId` to easily switch between forks. + /// + /// @param filePath: the path to the TOML configuration file. + function _loadConfigAndForks(string memory filePath) internal { + _loadConfig(filePath); + + uint256[] memory chains = config.readChainIds(); + for (uint i = 0; i < chains.length; i++) { + uint256 chainId = chains[i]; + uint256 forkId = vm.createFork(config.readRpcUrl(chainId)); + forkOf[chainId] = forkId; + chainIds.push(chainId); + } + } +} + +abstract contract TestBase is CommonConfig {} + +abstract contract ScriptBase is CommonConfig { VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); } diff --git a/src/Config.sol b/src/Config.sol deleted file mode 100644 index 4a3e8fd7..00000000 --- a/src/Config.sol +++ /dev/null @@ -1,237 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {VmSafe} from "./Vm.sol"; - -/** - * @title Config - * @notice An abstract contract to parse a TOML configuration file and load its - * variables into structured storage on deployment. - * @dev This contract assumes a flat TOML structure where top-level keys - * represent chain IDs or profiles, and the keys under them are the - * configuration variables. Nested tables are ignored by this implementation. - * - * Supported TOML Format: - * ``` - * [mainnet] - * endpoint_url = "https://eth.llamarpc.com" - * - * [mainnet.vars] - * is_live = true - * weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" - * whitelisted_admins = [ - * "0x0000000000000000000000000000000000000001", - * "0x0000000000000000000000000000000000000002" - * ] - * - * [optimism] - * endpoint_url = "https://mainnet.optimism.io" - * - * [optimism.vars] - * is_live = false - * weth = "0x4200000000000000000000000000000000000006" - * ``` - */ -abstract contract Config { - VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); - - // --- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ - - // Storage for the configured RPC url. - mapping(uint256 chainId => string url) private rpcOf; - - // Storage for variable types. - mapping(uint256 chainId => mapping(string key => bool value)) private boolsOf; - mapping(uint256 chainId => mapping(string key => uint256 value)) private uintsOf; - mapping(uint256 chainId => mapping(string key => address value)) private addressesOf; - mapping(uint256 chainId => mapping(string key => bytes32 value)) private bytes32sOf; - mapping(uint256 chainId => mapping(string key => string value)) private stringsOf; - mapping(uint256 chainId => mapping(string key => bytes value)) private bytesOf; - - // Storage for array variable types. - mapping(uint256 chainId => mapping(string key => bool[] value)) private boolArraysOf; - mapping(uint256 chainId => mapping(string key => uint256[] value)) private uintArraysOf; - mapping(uint256 chainId => mapping(string key => address[] value)) private addressArraysOf; - mapping(uint256 chainId => mapping(string key => bytes32[] value)) private bytes32ArraysOf; - mapping(uint256 chainId => mapping(string key => string[] value)) private stringArraysOf; - mapping(uint256 chainId => mapping(string key => bytes[] value)) private bytesArraysOf; - - // --- CONSTRUCTOR --------------------------------------------------------- - - /// @notice Reads the TOML file and iterates through each top-level key, which is - /// assumed to be a chain name or ID. For each chain, it caches its RPC - /// endpoint and all variables defined in its `vars` sub-table. - /// - /// The constructor uses a series of try-catch blocks to determine the type - /// of each variable. It attempts to parse array types (e.g., `address[]`) - /// before their singular counterparts (`address`) to ensure correct type - /// inference. If a variable cannot be parsed as any of the supported types, - /// the constructor will revert with an error. - /// - /// @param configFilePath: The local path to the TOML configuration file. - constructor(string memory configFilePath) { - string memory content = vm.readFile(configFilePath); - string[] memory chain_keys = vm.parseTomlKeys(content, "$"); - - // Cache the entire configuration to storage - for (uint i = 0; i < chain_keys.length; i++) { - string memory chain_key = chain_keys[i]; - uint256 chain_id = resolveChainId(chain_key); - - // Cache the configure rpc endpoint for that chain. - // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured. - try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { - rpcOf[chain_id] = url; - } catch { - rpcOf[chain_id] = vm.rpcUrl(chain_key); - } - - string memory path_to_chain = string.concat("$.", chain_key, ".vars"); - string[] memory var_keys = vm.parseTomlKeys(content, path_to_chain); - - // Parse and cache variables - for (uint j = 0; j < var_keys.length; j++) { - string memory var_key = var_keys[j]; - string memory path_to_var = string.concat(path_to_chain, ".", var_key); - - // Attempt to parse as a boolean array - try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { - boolArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a boolean - try vm.parseTomlBool(content, path_to_var) returns (bool val) { - boolsOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as an address array - try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { - addressArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as an address - try vm.parseTomlAddress(content, path_to_var) returns (address val) { - addressesOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a uint256 array - try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { - uintArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a uint256 - try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { - uintsOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a bytes32 array - try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { - bytes32ArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a bytes32 - try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { - bytes32sOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a bytes array - try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { - bytesArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as bytes - try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { - bytesOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a string array - try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { - stringArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a string (last, as it can be a fallback) - try vm.parseTomlString(content, path_to_var) returns (string memory val) { - stringsOf[chain_id][var_key] = val; - continue; - } catch {} - - revert(string.concat("unable to parse variable: '", var_key, "' from '[", chain_key, "']")); - } - } - } - - // --- HELPER FUNCTIONS ---------------------------------------------------- - - function resolveChainId(string memory aliasOrId) private view returns (uint256) { - try vm.parseUint(aliasOrId) returns (uint256 chainId) { - return chainId; - } catch { - try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) { - return chainInfo.chainId; - } catch { - revert(string.concat("chain key: '", aliasOrId, "' is not a valid alias nor a number.")); - } - } - } - - // --- GETTER FUNCTIONS ---------------------------------------------------- - - function readBool(uint256 chain_id, string memory key) public view returns (bool) { - return boolsOf[chain_id][key]; - } - - function readUint(uint256 chain_id, string memory key) public view returns (uint256) { - return uintsOf[chain_id][key]; - } - - function readAddress(uint256 chain_id, string memory key) public view returns (address) { - return addressesOf[chain_id][key]; - } - - function readBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { - return bytes32sOf[chain_id][key]; - } - - function readString(uint256 chain_id, string memory key) public view returns (string memory) { - return stringsOf[chain_id][key]; - } - - function readBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { - return bytesOf[chain_id][key]; - } - - function readBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { - return boolArraysOf[chain_id][key]; - } - - function readUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { - return uintArraysOf[chain_id][key]; - } - - function readAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { - return addressArraysOf[chain_id][key]; - } - - function readBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { - return bytes32ArraysOf[chain_id][key]; - } - - function readStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { - return stringArraysOf[chain_id][key]; - } - - function readBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { - return bytesArraysOf[chain_id][key]; - } -} diff --git a/src/Script.sol b/src/Script.sol index a2e2aa1c..5167e8cd 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -16,13 +16,14 @@ import {stdMath} from "./StdMath.sol"; import {StdStorage, stdStorageSafe} from "./StdStorage.sol"; import {StdStyle} from "./StdStyle.sol"; import {StdUtils} from "./StdUtils.sol"; +import {ConfigSetup} form "./Config.sol"; import {VmSafe} from "./Vm.sol"; // 📦 BOILERPLATE import {ScriptBase} from "./Base.sol"; // ⭐️ SCRIPT -abstract contract Script is ScriptBase, StdChains, StdCheatsSafe, StdUtils { +abstract contract Script is ConfigSetup, ScriptBase, StdChains, StdCheatsSafe, StdUtils { // Note: IS_SCRIPT() must return true. bool public IS_SCRIPT = true; } diff --git a/src/StdConfig.sol b/src/StdConfig.sol new file mode 100644 index 00000000..efa9bf55 --- /dev/null +++ b/src/StdConfig.sol @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {VmSafe} from "./Vm.sol"; + +/// @notice A contract that parses a toml configuration file and load its +/// variables into storage, automatically casting them, on deployment. +/// +/// @dev This contract assumes a flat toml structure where top-level keys +/// represent chain ids or profiles, and the keys under them are the +/// configuration variables. Nested tables are ignored by this implementation. +/// +/// Supported format: +/// ``` +/// [mainnet] +/// endpoint_url = "https://eth.llamarpc.com" +/// +/// [mainnet.vars] +/// is_live = true +/// weth = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" +/// whitelisted_admins = [ +/// "0x0000000000000000000000000000000000000001", +/// "0x0000000000000000000000000000000000000002" +/// ] +/// +/// [optimism] +/// endpoint_url = "https://mainnet.optimism.io" +/// +/// [optimism.vars] +/// is_live = false +/// weth = "0x4200000000000000000000000000000000000006" +/// ``` +contract StdConfig { + VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + + // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------- + + // File path + string private _filePath; + + // Keys of the configured chains. + string[] private _chainKeys; + + // Storage for the configured RPC url. + mapping(uint256 chainId => string url) private _rpcOf; + + // Storage for variable types. + mapping(uint256 chainId => mapping(string key => bool value)) private _boolsOf; + mapping(uint256 chainId => mapping(string key => uint256 value)) private _uintsOf; + mapping(uint256 chainId => mapping(string key => address value)) private _addressesOf; + mapping(uint256 chainId => mapping(string key => bytes32 value)) private _bytes32sOf; + mapping(uint256 chainId => mapping(string key => string value)) private _stringsOf; + mapping(uint256 chainId => mapping(string key => bytes value)) private _bytesOf; + + // Storage for array variable types. + mapping(uint256 chainId => mapping(string key => bool[] value)) private _boolArraysOf; + mapping(uint256 chainId => mapping(string key => uint256[] value)) private _uintArraysOf; + mapping(uint256 chainId => mapping(string key => address[] value)) private _addressArraysOf; + mapping(uint256 chainId => mapping(string key => bytes32[] value)) private _bytes32ArraysOf; + mapping(uint256 chainId => mapping(string key => string[] value)) private _stringArraysOf; + mapping(uint256 chainId => mapping(string key => bytes[] value)) private _bytesArraysOf; + + // -- CONSTRUCTOR ---------------------------------------------------------- + + /// @notice Reads the TOML file and iterates through each top-level key, which is + /// assumed to be a chain name or ID. For each chain, it caches its RPC + /// endpoint and all variables defined in its `vars` sub-table. + /// + /// The constructor uses a series of try-catch blocks to determine the type + /// of each variable. It attempts to parse array types (e.g., `address[]`) + /// before their singular counterparts (`address`) to ensure correct type + /// inference. If a variable cannot be parsed as any of the supported types, + /// the constructor will revert with an error. + /// + /// @param configFilePath: The local path to the TOML configuration file. + constructor(string memory configFilePath) { + _filePath = configFilePath; + string memory content = vm.resolveEnv(vm.readFile(configFilePath)); + string[] memory chain_keys = vm.parseTomlKeys(content, "$"); + + // Cache the entire configuration to storage + for (uint i = 0; i < chain_keys.length; i++) { + string memory chain_key = chain_keys[i]; + uint256 chain_id = resolveChainId(chain_key); + _chainKeys.push(chain_key); + + // Cache the configure rpc endpoint for that chain. + // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured. + try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { + _rpcOf[chain_id] = vm.resolveEnv(url); + } catch { + _rpcOf[chain_id] = vm.resolveEnv(vm.rpcUrl(chain_key)); + } + + string memory path_to_chain = string.concat("$.", chain_key, ".vars"); + string[] memory var_keys = vm.parseTomlKeys(content, path_to_chain); + + // Parse and cache variables + for (uint j = 0; j < var_keys.length; j++) { + string memory var_key = var_keys[j]; + string memory path_to_var = string.concat(path_to_chain, ".", var_key); + + // Attempt to parse as a boolean array + try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { + _boolArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a boolean + try vm.parseTomlBool(content, path_to_var) returns (bool val) { + _boolsOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as an address array + try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { + _addressArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as an address + try vm.parseTomlAddress(content, path_to_var) returns (address val) { + _addressesOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a uint256 array + try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { + _uintArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a uint256 + try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { + _uintsOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a bytes32 array + try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { + _bytes32ArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a bytes32 + try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { + _bytes32sOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a bytes array + try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { + _bytesArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as bytes + try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { + _bytesOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a string array + try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { + _stringArraysOf[chain_id][var_key] = val; + continue; + } catch {} + + // Attempt to parse as a string (last, as it can be a fallback) + try vm.parseTomlString(content, path_to_var) returns (string memory val) { + _stringsOf[chain_id][var_key] = val; + continue; + } catch {} + + revert(string.concat("unable to parse variable: '", var_key, "' from '[", chain_key, "']")); + } + } + } + + // -- HELPER FUNCTIONS ----------------------------------------------------- + + function resolveChainId(string memory aliasOrId) public view returns (uint256) { + try vm.parseUint(aliasOrId) returns (uint256 chainId) { + return chainId; + } catch { + try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) { + return chainInfo.chainId; + } catch { + revert(string.concat("chain key: '", aliasOrId, "' is not a valid alias nor a number.")); + } + } + } + + function _getChainKeyFromId(uint256 chainId) private view returns (string memory) { + for (uint i = 0; i < _chainKeys.length; i++) { + if (resolveChainId(_chainKeys[i]) == chainId) { + return _chainKeys[i]; + } + } + revert(string.concat("chain id: '", vm.toString(chainId), "' not found in configuration")); + } + + /// @dev Wraps a string in double quotes for JSON compatibility. + function _quote(string memory s) private pure returns (string memory) { + return string.concat('"', s, '"'); + } + + /// @dev Writes a JSON-formatted value to a specific key in the TOML file. + function _writeToToml(uint256 chainId, string memory key, string memory jsonValue) private { + string memory chainKey = _getChainKeyFromId(chainId); + string memory valueKey = string.concat("$.", chainKey, ".vars.", key); + vm.writeToml(jsonValue, _filePath, valueKey); + } + + // -- GETTER FUNCTIONS ----------------------------------------------------- + + function readChainIds() public view returns (uint256[] memory) { + string[] memory keys = _chainKeys; + + uint256[] memory ids = new uint256[](keys.length); + for (uint i = 0; i < keys.length; i++) { + ids[i] = resolveChainId(keys[i]); + } + + return ids; + } + + function readRpcUrl(uint256 chain_id) public view returns (string memory) { + return _rpcOf[chain_id]; + } + + function readRpcUrl() public view returns (string memory) { + return _rpcOf[vm.activeChain()]; + } + + function readBool(uint256 chain_id, string memory key) public view returns (bool) { + return _boolsOf[chain_id][key]; + } + + function readBool(string memory key) public view returns (bool) { + return _boolsOf[vm.activeChain()][key]; + } + + function readUint(uint256 chain_id, string memory key) public view returns (uint256) { + return _uintsOf[chain_id][key]; + } + + function readUint(string memory key) public view returns (uint256) { + return _uintsOf[vm.activeChain()][key]; + } + + function readAddress(uint256 chain_id, string memory key) public view returns (address) { + return _addressesOf[chain_id][key]; + } + + function readAddress(string memory key) public view returns (address) { + return _addressesOf[vm.activeChain()][key]; + } + + function readBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { + return _bytes32sOf[chain_id][key]; + } + + function readBytes32(string memory key) public view returns (bytes32) { + return _bytes32sOf[vm.activeChain()][key]; + } + + function readString(uint256 chain_id, string memory key) public view returns (string memory) { + return _stringsOf[chain_id][key]; + } + + function readString(string memory key) public view returns (string memory) { + return _stringsOf[vm.activeChain()][key]; + } + + function readBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { + return _bytesOf[chain_id][key]; + } + + function readBytes(string memory key) public view returns (bytes memory) { + return _bytesOf[vm.activeChain()][key]; + } + + function readBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { + return _boolArraysOf[chain_id][key]; + } + + function readBoolArray(string memory key) public view returns (bool[] memory) { + return _boolArraysOf[vm.activeChain()][key]; + } + + function readUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { + return _uintArraysOf[chain_id][key]; + } + + function readUintArray(string memory key) public view returns (uint256[] memory) { + return _uintArraysOf[vm.activeChain()][key]; + } + + function readAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { + return _addressArraysOf[chain_id][key]; + } + + function readAddressArray(string memory key) public view returns (address[] memory) { + return _addressArraysOf[vm.activeChain()][key]; + } + + function readBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { + return _bytes32ArraysOf[chain_id][key]; + } + + function readBytes32Array(string memory key) public view returns (bytes32[] memory) { + return _bytes32ArraysOf[vm.activeChain()][key]; + } + + function readStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { + return _stringArraysOf[chain_id][key]; + } + + function readStringArray(string memory key) public view returns (string[] memory) { + return _stringArraysOf[vm.activeChain()][key]; + } + + function readBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { + return _bytesArraysOf[chain_id][key]; + } + + function readBytesArray(string memory key) public view returns (bytes[] memory) { + return _bytesArraysOf[vm.activeChain()][key]; + } + + // -- SETTER FUNCTIONS ----------------------------------------------------- + + function update(uint256 chainId, string memory key, bool value, bool write) public { + _boolsOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, vm.toString(value)); + } + + function update(string memory key, bool value, bool write) public { + uint256 chainId = vm.activeChain(); + _boolsOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, vm.toString(value)); + } + + function update(uint256 chainId, string memory key, uint256 value, bool write) public { + _uintsOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, vm.toString(value)); + } + + function update(string memory key, uint256 value, bool write) public { + uint256 chainId = vm.activeChain(); + _uintsOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, vm.toString(value)); + } + + function update(uint256 chainId, string memory key, address value, bool write) public { + _addressesOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + } + + function update(string memory key, address value, bool write) public { + uint256 chainId = vm.activeChain(); + _addressesOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + } + + function update(uint256 chainId, string memory key, bytes32 value, bool write) public { + _bytes32sOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + } + + function update(string memory key, bytes32 value, bool write) public { + uint256 chainId = vm.activeChain(); + _bytes32sOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + } + + function update(uint256 chainId, string memory key, string memory value, bool write) public { + _stringsOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(value)); + } + + function update(string memory key, string memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _stringsOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(value)); + } + + function update(uint256 chainId, string memory key, bytes memory value, bool write) public { + _bytesOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + } + + function update(string memory key, bytes memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _bytesOf[chainId][key] = value; + if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + } + + function update(uint256 chainId, string memory key, bool[] memory value, bool write) public { + _boolArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(string memory key, bool[] memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _boolArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(uint256 chainId, string memory key, uint256[] memory value, bool write) public { + _uintArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(string memory key, uint256[] memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _uintArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(uint256 chainId, string memory key, address[] memory value, bool write) public { + _addressArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(string memory key, address[] memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _addressArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { + _bytes32ArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(string memory key, bytes32[] memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _bytes32ArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(uint256 chainId, string memory key, string[] memory value, bool write) public { + _stringArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(string memory key, string[] memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _stringArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(uint256 chainId, string memory key, bytes[] memory value, bool write) public { + _bytesArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } + + function update(string memory key, bytes[] memory value, bool write) public { + uint256 chainId = vm.activeChain(); + _bytesArraysOf[chainId][key] = value; + if (write) { + string memory json = "["; + for (uint i = 0; i < value.length; i++) { + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); + } + json = string.concat(json, "]"); + _writeToToml(chainId, key, json); + } + } +} diff --git a/src/Test.sol b/src/Test.sol index 11b18f29..e5dd275b 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -22,13 +22,14 @@ import {StdStorage, stdStorage} from "./StdStorage.sol"; import {StdStyle} from "./StdStyle.sol"; import {stdToml} from "./StdToml.sol"; import {StdUtils} from "./StdUtils.sol"; +import {ConfigSetup} form "./Config.sol"; import {Vm} from "./Vm.sol"; // 📦 BOILERPLATE import {TestBase} from "./Base.sol"; // ⭐️ TEST -abstract contract Test is TestBase, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils { +abstract contract Test is ConfigSetup, TestBase, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils { // Note: IS_TEST() must return true. bool public IS_TEST = true; } diff --git a/src/Vm.sol b/src/Vm.sol index e5c01e29..c5e913a7 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -449,6 +449,9 @@ interface VmSafe { // ======== Environment ======== + /// Resolves all the env variable placeholders (`${ENV_VAR}`) of the input string. + function resolveEnv(string calldata input) external view returns (string memory); + /// 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); @@ -940,144 +943,6 @@ interface VmSafe { /// `path` is relative to the project root. function writeLine(string calldata path, string calldata data) external; - // ======== Forking ======== - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as `address`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkAddress(string calldata key) external view returns (address); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as an array of `address`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkAddressArray(string calldata key) external view returns (address[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as `bool`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkBool(string calldata key) external view returns (bool); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as an array of `bool`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkBoolArray(string calldata key) external view returns (bool[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as `bytes`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkBytes(string calldata key) external view returns (bytes memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as `bytes32`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkBytes32(string calldata key) external view returns (bytes32); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as an array of `bytes32`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkBytes32Array(string calldata key) external view returns (bytes32[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as an array of `bytes`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkBytesArray(string calldata key) external view returns (bytes[] memory); - - /// Returns the chain name of the currently selected fork. - function readForkChain() external view returns (string memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as `address`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkChainAddress(uint256 chain, string calldata key) external view returns (address); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as an array of `address`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkChainAddressArray(uint256 chain, string calldata key) external view returns (address[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as `bool`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkChainBool(uint256 chain, string calldata key) external view returns (bool); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as an array of `bool`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkChainBoolArray(uint256 chain, string calldata key) external view returns (bool[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as `bytes`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkChainBytes(uint256 chain, string calldata key) external view returns (bytes memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as `bytes32`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkChainBytes32(uint256 chain, string calldata key) external view returns (bytes32); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as an array of `bytes32`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkChainBytes32Array(uint256 chain, string calldata key) external view returns (bytes32[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as an array of `bytes`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkChainBytesArray(uint256 chain, string calldata key) external view returns (bytes[] memory); - - /// Returns the chain id of the currently selected fork. - function readForkChainId() external view returns (uint256); - - /// Returns an array with the ids of all the configured fork chains. - /// Note that the configured fork chains are subsections of the `[fork]` section of 'foundry.toml'. - function readForkChainIds() external view returns (uint256[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as `int256`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkChainInt(uint256 chain, string calldata key) external view returns (int256); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as an array of `int256`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkChainIntArray(uint256 chain, string calldata key) external view returns (int256[] memory); - - /// Returns the rpc url of the corresponding chain id. - /// By default, the rpc url of each fork is derived from the `[rpc_endpoints]`, unless - /// the rpc config is specifically informed in the fork config for that specific chain. - function readForkChainRpcUrl(uint256 id) external view returns (string memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as `string`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkChainString(uint256 chain, string calldata key) external view returns (string memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as an array of `string`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkChainStringArray(uint256 chain, string calldata key) external view returns (string[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as `uint256`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkChainUint(uint256 chain, string calldata key) external view returns (uint256); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the specified chain and parses it as an array of `uint256`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkChainUintArray(uint256 chain, string calldata key) external view returns (uint256[] memory); - - /// Returns an array with the name of all the configured fork chains. - /// Note that the configured fork chains are subsections of the `[fork]` section of 'foundry.toml'. - function readForkChains() external view returns (string[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as `int256`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkInt(string calldata key) external view returns (int256); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as an array of `int256`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkIntArray(string calldata key) external view returns (int256[] memory); - - /// Returns the rpc url of the currently selected fork. - /// By default, the rpc url of each fork is derived from the `[rpc_endpoints]`, unless - /// the rpc config is specifically informed in the fork config for that specific chain. - function readForkRpcUrl() external view returns (string memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as `string`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkString(string calldata key) external view returns (string memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as an array of `string`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkStringArray(string calldata key) external view returns (string[] memory); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as `uint256`. - /// Reverts if the key was not found or the value could not be parsed. - function readForkUint(string calldata key) external view returns (uint256); - - /// Gets the value for the key `key` from the `[fork.]` section of `foundry.toml` for the currently active fork and parses it as an array of `uint256`. - /// Reverts if a key was not found or any of the values could not be parsed. - function readForkUintArray(string calldata key) external view returns (uint256[] memory); - // ======== JSON ======== /// Checks if `key` exists in a JSON object. @@ -2133,6 +1998,9 @@ interface Vm is VmSafe { /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. function activeFork() external view returns (uint256 forkId); + /// Returns the chain id of the currently active fork. Reverts if no fork is currently active. + function activeChain() external view returns(uint256); + /// In forking mode, explicitly grant the given address cheatcode access. function allowCheatcodes(address account) external; From bbd5f8dfb32e57ec5a2a0b9741f44336d6083e72 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Sun, 24 Aug 2025 23:36:53 +0200 Subject: [PATCH 03/54] add unit tests --- .env | 7 ++++++ src/Base.sol | 8 +++++++ src/Script.sol | 3 +-- src/Test.sol | 3 +-- src/Vm.sol | 12 +++++----- test/CommonConfig.t.sol | 47 +++++++++++++++++++++++++++++++++++++++ test/fixtures/config.toml | 21 +++++++++++++++++ 7 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 .env create mode 100644 test/CommonConfig.t.sol create mode 100644 test/fixtures/config.toml diff --git a/.env b/.env new file mode 100644 index 00000000..7d78d054 --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +# ETHEREUM MAINNET +MAINNET_RPC="https://eth.llamarpc.com" +WETH_MAINNET="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + +# OPTIMISM +OPTIMISM_RPC="https://mainnet.optimism.io" +WETH_OPTIMISM="0x4200000000000000000000000000000000000006" diff --git a/src/Base.sol b/src/Base.sol index 8961dc6f..8301cc48 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +import {console} from "./console.sol"; import {StdStorage} from "./StdStorage.sol"; import {StdConfig} from "./StdConfig.sol"; import {Vm, VmSafe} from "./Vm.sol"; @@ -60,8 +61,12 @@ abstract contract CommonConfig is CommonBase { /// /// @param filePath: the path to the TOML configuration file. function _loadConfig(string memory filePath) internal { + console.log("----------"); + console.log(string.concat("Loading config from '", filePath, "'")); config = new StdConfig(filePath); vm.makePersistent(address(config)); + console.log("Config successfully loaded"); + console.log("----------"); } /// @notice Loads configuration from a file and creates forks for each specified chain. @@ -74,6 +79,7 @@ abstract contract CommonConfig is CommonBase { function _loadConfigAndForks(string memory filePath) internal { _loadConfig(filePath); + console.log("Setting up forks for the configured chains..."); uint256[] memory chains = config.readChainIds(); for (uint i = 0; i < chains.length; i++) { uint256 chainId = chains[i]; @@ -81,6 +87,8 @@ abstract contract CommonConfig is CommonBase { forkOf[chainId] = forkId; chainIds.push(chainId); } + console.log("Forks successfully created"); + console.log("----------"); } } diff --git a/src/Script.sol b/src/Script.sol index 5167e8cd..a2e2aa1c 100644 --- a/src/Script.sol +++ b/src/Script.sol @@ -16,14 +16,13 @@ import {stdMath} from "./StdMath.sol"; import {StdStorage, stdStorageSafe} from "./StdStorage.sol"; import {StdStyle} from "./StdStyle.sol"; import {StdUtils} from "./StdUtils.sol"; -import {ConfigSetup} form "./Config.sol"; import {VmSafe} from "./Vm.sol"; // 📦 BOILERPLATE import {ScriptBase} from "./Base.sol"; // ⭐️ SCRIPT -abstract contract Script is ConfigSetup, ScriptBase, StdChains, StdCheatsSafe, StdUtils { +abstract contract Script is ScriptBase, StdChains, StdCheatsSafe, StdUtils { // Note: IS_SCRIPT() must return true. bool public IS_SCRIPT = true; } diff --git a/src/Test.sol b/src/Test.sol index e5dd275b..11b18f29 100644 --- a/src/Test.sol +++ b/src/Test.sol @@ -22,14 +22,13 @@ import {StdStorage, stdStorage} from "./StdStorage.sol"; import {StdStyle} from "./StdStyle.sol"; import {stdToml} from "./StdToml.sol"; import {StdUtils} from "./StdUtils.sol"; -import {ConfigSetup} form "./Config.sol"; import {Vm} from "./Vm.sol"; // 📦 BOILERPLATE import {TestBase} from "./Base.sol"; // ⭐️ TEST -abstract contract Test is ConfigSetup, TestBase, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils { +abstract contract Test is TestBase, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils { // Note: IS_TEST() must return true. bool public IS_TEST = true; } diff --git a/src/Vm.sol b/src/Vm.sol index c5e913a7..524186eb 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -610,6 +610,12 @@ interface VmSafe { // ======== EVM ======== + /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. + function activeFork() external view returns (uint256 forkId); + + /// Returns the chain id of the currently active fork. Reverts if no fork is currently active. + function activeChain() external view returns(uint256); + /// 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); @@ -1995,12 +2001,6 @@ interface Vm is VmSafe { /// Utility cheatcode to set an EIP-2930 access list for all subsequent transactions. function accessList(AccessListItem[] calldata access) external; - /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. - function activeFork() external view returns (uint256 forkId); - - /// Returns the chain id of the currently active fork. Reverts if no fork is currently active. - function activeChain() external view returns(uint256); - /// In forking mode, explicitly grant the given address cheatcode access. function allowCheatcodes(address account) external; diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol new file mode 100644 index 00000000..41f72cf0 --- /dev/null +++ b/test/CommonConfig.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0 <0.9.0; + +import {Test} from "../src/Test.sol"; + +contract CommonConfigTest is Test { + function test_loadConfig() public { + _loadConfig("./test/fixtures/config.toml"); + + address weth; + address[] memory deps; + string memory url; + + // mainnet + weth = config.readAddress(1, "weth"); + deps = config.readAddressArray(1, "deps"); + url = config.readRpcUrl(1); + + assertEq(weth, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + assertEq(deps[0], 0x0000000000000000000000000000000000000000); + assertEq(deps[1], 0x1111111111111111111111111111111111111111); + assertEq(url, "https://eth.llamarpc.com"); + + // optimism + weth = config.readAddress(10, "weth"); + deps = config.readAddressArray(10, "deps"); + url = config.readRpcUrl(10); + + assertEq(weth, 0x4200000000000000000000000000000000000006); + assertEq(deps[0], 0x2222222222222222222222222222222222222222); + assertEq(deps[1], 0x3333333333333333333333333333333333333333); + assertEq(url, "https://mainnet.optimism.io"); + } + + function test_loadConfigAndForks() public { + _loadConfigAndForks("./test/fixtures/config.toml"); + + // assert that the map of chain id and fork ids is created and that the chain ids actually match + assertEq(forkOf[1], 0); + vm.selectFork(forkOf[1]); + assertEq(vm.activeChain(), 1); + + assertEq(forkOf[10], 1); + vm.selectFork(forkOf[10]); + assertEq(vm.activeChain(), 10); + } +} diff --git a/test/fixtures/config.toml b/test/fixtures/config.toml new file mode 100644 index 00000000..59a06497 --- /dev/null +++ b/test/fixtures/config.toml @@ -0,0 +1,21 @@ +[mainnet] +endpoint_url = "${MAINNET_RPC}" + +[mainnet.vars] +is_live = true +weth = "${WETH_MAINNET}" +deps = [ + "0x0000000000000000000000000000000000000000", + "0x1111111111111111111111111111111111111111", +] + +[optimism] +endpoint_url = "${OPTIMISM_RPC}" + +[optimism.vars] +is_live = false +weth = "${WETH_OPTIMISM}" +deps = [ + "0x2222222222222222222222222222222222222222", + "0x3333333333333333333333333333333333333333", +] From 7e67e9c3eaed93d552c11619aa7d5b8d0f030f8d Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 01:05:43 +0200 Subject: [PATCH 04/54] test all types --- src/StdConfig.sol | 312 ++++++++++++++++---------------------- test/CommonConfig.t.sol | 114 ++++++++++---- test/fixtures/config.toml | 57 ++++++- 3 files changed, 276 insertions(+), 207 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index efa9bf55..bf01fbbd 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -1,3 +1,4 @@ + // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; @@ -6,29 +7,27 @@ import {VmSafe} from "./Vm.sol"; /// @notice A contract that parses a toml configuration file and load its /// variables into storage, automatically casting them, on deployment. /// -/// @dev This contract assumes a flat toml structure where top-level keys -/// represent chain ids or profiles, and the keys under them are the -/// configuration variables. Nested tables are ignored by this implementation. +/// @dev This contract assumes a toml structure where top-level keys +/// represent chain ids or profiles. Under each chain key, variables are +/// organized by type in separate sub-tables. /// /// Supported format: /// ``` /// [mainnet] /// endpoint_url = "https://eth.llamarpc.com" /// -/// [mainnet.vars] +/// [mainnet.bool] /// is_live = true +/// +/// [mainnet.address] /// weth = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" /// whitelisted_admins = [ /// "0x0000000000000000000000000000000000000001", /// "0x0000000000000000000000000000000000000002" /// ] /// -/// [optimism] -/// endpoint_url = "https://mainnet.optimism.io" -/// -/// [optimism.vars] -/// is_live = false -/// weth = "0x4200000000000000000000000000000000000006" +/// [optimism.uint] +/// important_number = 123 /// ``` contract StdConfig { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -64,13 +63,12 @@ contract StdConfig { /// @notice Reads the TOML file and iterates through each top-level key, which is /// assumed to be a chain name or ID. For each chain, it caches its RPC - /// endpoint and all variables defined in its `vars` sub-table. + /// endpoint and all variables defined in typed sub-tables like `[.]`, + /// where type must be: `bool`, `address`, `uint`, `bytes32`, `string`, or `bytes`. /// - /// The constructor uses a series of try-catch blocks to determine the type - /// of each variable. It attempts to parse array types (e.g., `address[]`) - /// before their singular counterparts (`address`) to ensure correct type - /// inference. If a variable cannot be parsed as any of the supported types, - /// the constructor will revert with an error. + /// The constructor attempts to parse each variable first as a single value, + /// and if that fails, as an array of that type. If a variable cannot be + /// parsed as either, the constructor will revert with an error. /// /// @param configFilePath: The local path to the TOML configuration file. constructor(string memory configFilePath) { @@ -78,9 +76,21 @@ contract StdConfig { string memory content = vm.resolveEnv(vm.readFile(configFilePath)); string[] memory chain_keys = vm.parseTomlKeys(content, "$"); + string[] memory types = new string[](6); + types[0] = "bool"; + types[1] = "address"; + types[2] = "uint"; + types[3] = "bytes32"; + types[4] = "string"; + types[5] = "bytes"; + // Cache the entire configuration to storage for (uint i = 0; i < chain_keys.length; i++) { string memory chain_key = chain_keys[i]; + // Top-level keys that are not tables should be ignored (e.g. `profile = "default"`). + if (vm.parseTomlKeys(content, string.concat("$.", chain_key)).length == 0) { + continue; + } uint256 chain_id = resolveChainId(chain_key); _chainKeys.push(chain_key); @@ -92,92 +102,92 @@ contract StdConfig { _rpcOf[chain_id] = vm.resolveEnv(vm.rpcUrl(chain_key)); } - string memory path_to_chain = string.concat("$.", chain_key, ".vars"); - string[] memory var_keys = vm.parseTomlKeys(content, path_to_chain); - - // Parse and cache variables - for (uint j = 0; j < var_keys.length; j++) { - string memory var_key = var_keys[j]; - string memory path_to_var = string.concat(path_to_chain, ".", var_key); - - // Attempt to parse as a boolean array - try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { - _boolArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a boolean - try vm.parseTomlBool(content, path_to_var) returns (bool val) { - _boolsOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as an address array - try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { - _addressArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as an address - try vm.parseTomlAddress(content, path_to_var) returns (address val) { - _addressesOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a uint256 array - try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { - _uintArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a uint256 - try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { - _uintsOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a bytes32 array - try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { - _bytes32ArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a bytes32 - try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { - _bytes32sOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a bytes array - try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { - _bytesArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as bytes - try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { - _bytesOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a string array - try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { - _stringArraysOf[chain_id][var_key] = val; - continue; - } catch {} - - // Attempt to parse as a string (last, as it can be a fallback) - try vm.parseTomlString(content, path_to_var) returns (string memory val) { - _stringsOf[chain_id][var_key] = val; - continue; - } catch {} - - revert(string.concat("unable to parse variable: '", var_key, "' from '[", chain_key, "']")); + for (uint t = 0; t < types.length; t++) { + string memory var_type = types[t]; + string memory path_to_type = string.concat("$.", chain_key, ".", var_type); + + try vm.parseTomlKeys(content, path_to_type) returns (string[] memory var_keys) { + for (uint j = 0; j < var_keys.length; j++) { + string memory var_key = var_keys[j]; + string memory path_to_var = string.concat(path_to_type, ".", var_key); + bool success = false; + + if (keccak256(bytes(var_type)) == keccak256(bytes("bool"))) { + try vm.parseTomlBool(content, path_to_var) returns (bool val) { + _boolsOf[chain_id][var_key] = val; + success = true; + } catch { + try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { + _boolArraysOf[chain_id][var_key] = val; + success = true; + } catch {} + } + } else if (keccak256(bytes(var_type)) == keccak256(bytes("address"))) { + try vm.parseTomlAddress(content, path_to_var) returns (address val) { + _addressesOf[chain_id][var_key] = val; + success = true; + } catch { + try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { + _addressArraysOf[chain_id][var_key] = val; + success = true; + } catch {} + } + } else if (keccak256(bytes(var_type)) == keccak256(bytes("uint"))) { + try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { + _uintsOf[chain_id][var_key] = val; + success = true; + } catch { + try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { + _uintArraysOf[chain_id][var_key] = val; + success = true; + } catch {} + } + } else if (keccak256(bytes(var_type)) == keccak256(bytes("bytes32"))) { + try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { + _bytes32sOf[chain_id][var_key] = val; + success = true; + } catch { + try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { + _bytes32ArraysOf[chain_id][var_key] = val; + success = true; + } catch {} + } + } else if (keccak256(bytes(var_type)) == keccak256(bytes("bytes"))) { + try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { + _bytesOf[chain_id][var_key] = val; + success = true; + } catch { + try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { + _bytesArraysOf[chain_id][var_key] = val; + success = true; + } catch {} + } + } else if (keccak256(bytes(var_type)) == keccak256(bytes("string"))) { + try vm.parseTomlString(content, path_to_var) returns (string memory val) { + _stringsOf[chain_id][var_key] = val; + success = true; + } catch { + try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { + _stringArraysOf[chain_id][var_key] = val; + success = true; + } catch {} + } + } + + if (!success) { + revert( + string.concat( + "Unable to parse variable '", var_key, "' from '[", chain_key, ".", var_type, "]'" + ) + ); + } + } + } catch {} // Section does not exist, ignore. } } } - // -- HELPER FUNCTIONS ----------------------------------------------------- + // -- HELPER FUNCTIONS -----------------------------------------------------\n function resolveChainId(string memory aliasOrId) public view returns (uint256) { try vm.parseUint(aliasOrId) returns (uint256 chainId) { @@ -206,9 +216,9 @@ contract StdConfig { } /// @dev Writes a JSON-formatted value to a specific key in the TOML file. - function _writeToToml(uint256 chainId, string memory key, string memory jsonValue) private { + function _writeToToml(uint256 chainId, string memory ty, string memory key, string memory jsonValue) private { string memory chainKey = _getChainKeyFromId(chainId); - string memory valueKey = string.concat("$.", chainKey, ".vars.", key); + string memory valueKey = string.concat("$.", chainKey, ".", ty, ".", key); vm.writeToml(jsonValue, _filePath, valueKey); } @@ -333,68 +343,68 @@ contract StdConfig { function update(uint256 chainId, string memory key, bool value, bool write) public { _boolsOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, vm.toString(value)); + if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } function update(string memory key, bool value, bool write) public { uint256 chainId = vm.activeChain(); _boolsOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, vm.toString(value)); + if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } function update(uint256 chainId, string memory key, uint256 value, bool write) public { _uintsOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, vm.toString(value)); + if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } function update(string memory key, uint256 value, bool write) public { uint256 chainId = vm.activeChain(); _uintsOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, vm.toString(value)); + if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } function update(uint256 chainId, string memory key, address value, bool write) public { _addressesOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } function update(string memory key, address value, bool write) public { uint256 chainId = vm.activeChain(); _addressesOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } function update(uint256 chainId, string memory key, bytes32 value, bool write) public { _bytes32sOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } function update(string memory key, bytes32 value, bool write) public { uint256 chainId = vm.activeChain(); _bytes32sOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } function update(uint256 chainId, string memory key, string memory value, bool write) public { _stringsOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(value)); + if (write) _writeToToml(chainId, "string", key, _quote(value)); } function update(string memory key, string memory value, bool write) public { uint256 chainId = vm.activeChain(); _stringsOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(value)); + if (write) _writeToToml(chainId, "string", key, _quote(value)); } function update(uint256 chainId, string memory key, bytes memory value, bool write) public { _bytesOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } function update(string memory key, bytes memory value, bool write) public { uint256 chainId = vm.activeChain(); _bytesOf[chainId][key] = value; - if (write) _writeToToml(chainId, key, _quote(vm.toString(value))); + if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } function update(uint256 chainId, string memory key, bool[] memory value, bool write) public { @@ -406,22 +416,13 @@ contract StdConfig { if (i < value.length - 1) json = string.concat(json, ","); } json = string.concat(json, "]"); - _writeToToml(chainId, key, json); + _writeToToml(chainId, "bool", key, json); } } function update(string memory key, bool[] memory value, bool write) public { uint256 chainId = vm.activeChain(); - _boolArraysOf[chainId][key] = value; - if (write) { - string memory json = "["; - for (uint i = 0; i < value.length; i++) { - json = string.concat(json, vm.toString(value[i])); - if (i < value.length - 1) json = string.concat(json, ","); - } - json = string.concat(json, "]"); - _writeToToml(chainId, key, json); - } + update(chainId, key, value, write); } function update(uint256 chainId, string memory key, uint256[] memory value, bool write) public { @@ -433,22 +434,13 @@ contract StdConfig { if (i < value.length - 1) json = string.concat(json, ","); } json = string.concat(json, "]"); - _writeToToml(chainId, key, json); + _writeToToml(chainId, "uint", key, json); } } function update(string memory key, uint256[] memory value, bool write) public { uint256 chainId = vm.activeChain(); - _uintArraysOf[chainId][key] = value; - if (write) { - string memory json = "["; - for (uint i = 0; i < value.length; i++) { - json = string.concat(json, vm.toString(value[i])); - if (i < value.length - 1) json = string.concat(json, ","); - } - json = string.concat(json, "]"); - _writeToToml(chainId, key, json); - } + update(chainId, key, value, write); } function update(uint256 chainId, string memory key, address[] memory value, bool write) public { @@ -460,22 +452,13 @@ contract StdConfig { if (i < value.length - 1) json = string.concat(json, ","); } json = string.concat(json, "]"); - _writeToToml(chainId, key, json); + _writeToToml(chainId, "address", key, json); } } function update(string memory key, address[] memory value, bool write) public { uint256 chainId = vm.activeChain(); - _addressArraysOf[chainId][key] = value; - if (write) { - string memory json = "["; - for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = string.concat(json, ","); - } - json = string.concat(json, "]"); - _writeToToml(chainId, key, json); - } + update(chainId, key, value, write); } function update(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { @@ -487,22 +470,13 @@ contract StdConfig { if (i < value.length - 1) json = string.concat(json, ","); } json = string.concat(json, "]"); - _writeToToml(chainId, key, json); + _writeToToml(chainId, "bytes32", key, json); } } function update(string memory key, bytes32[] memory value, bool write) public { uint256 chainId = vm.activeChain(); - _bytes32ArraysOf[chainId][key] = value; - if (write) { - string memory json = "["; - for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = string.concat(json, ","); - } - json = string.concat(json, "]"); - _writeToToml(chainId, key, json); - } + update(chainId, key, value, write); } function update(uint256 chainId, string memory key, string[] memory value, bool write) public { @@ -514,22 +488,13 @@ contract StdConfig { if (i < value.length - 1) json = string.concat(json, ","); } json = string.concat(json, "]"); - _writeToToml(chainId, key, json); + _writeToToml(chainId, "string", key, json); } } function update(string memory key, string[] memory value, bool write) public { uint256 chainId = vm.activeChain(); - _stringArraysOf[chainId][key] = value; - if (write) { - string memory json = "["; - for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(value[i])); - if (i < value.length - 1) json = string.concat(json, ","); - } - json = string.concat(json, "]"); - _writeToToml(chainId, key, json); - } + update(chainId, key, value, write); } function update(uint256 chainId, string memory key, bytes[] memory value, bool write) public { @@ -541,21 +506,12 @@ contract StdConfig { if (i < value.length - 1) json = string.concat(json, ","); } json = string.concat(json, "]"); - _writeToToml(chainId, key, json); + _writeToToml(chainId, "bytes", key, json); } } function update(string memory key, bytes[] memory value, bool write) public { uint256 chainId = vm.activeChain(); - _bytesArraysOf[chainId][key] = value; - if (write) { - string memory json = "["; - for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = string.concat(json, ","); - } - json = string.concat(json, "]"); - _writeToToml(chainId, key, json); - } + update(chainId, key, value, write); } } diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol index 41f72cf0..17bfac9d 100644 --- a/test/CommonConfig.t.sol +++ b/test/CommonConfig.t.sol @@ -4,33 +4,93 @@ pragma solidity >=0.7.0 <0.9.0; import {Test} from "../src/Test.sol"; contract CommonConfigTest is Test { - function test_loadConfig() public { - _loadConfig("./test/fixtures/config.toml"); - - address weth; - address[] memory deps; - string memory url; - - // mainnet - weth = config.readAddress(1, "weth"); - deps = config.readAddressArray(1, "deps"); - url = config.readRpcUrl(1); - - assertEq(weth, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - assertEq(deps[0], 0x0000000000000000000000000000000000000000); - assertEq(deps[1], 0x1111111111111111111111111111111111111111); - assertEq(url, "https://eth.llamarpc.com"); - - // optimism - weth = config.readAddress(10, "weth"); - deps = config.readAddressArray(10, "deps"); - url = config.readRpcUrl(10); - - assertEq(weth, 0x4200000000000000000000000000000000000006); - assertEq(deps[0], 0x2222222222222222222222222222222222222222); - assertEq(deps[1], 0x3333333333333333333333333333333333333333); - assertEq(url, "https://mainnet.optimism.io"); - } +function test_loadConfig() public { + // Deploy the config contract with the test fixture. + _loadConfig("./test/fixtures/config.toml"); + + // -- MAINNET -------------------------------------------------------------- + + // Read and assert RPC URL for Mainnet (chain ID 1) + assertEq(config.readRpcUrl(1), "https://eth.llamarpc.com"); + + // Read and assert boolean values + assertTrue(config.readBool(1, "is_live")); + bool[] memory bool_array = config.readBoolArray(1, "bool_array"); + assertTrue(bool_array[0]); + assertFalse(bool_array[1]); + + // Read and assert address values + assertEq(config.readAddress(1, "weth"), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address[] memory address_array = config.readAddressArray(1, "deps"); + assertEq(address_array[0], 0x0000000000000000000000000000000000000000); + assertEq(address_array[1], 0x1111111111111111111111111111111111111111); + + // Read and assert uint values + assertEq(config.readUint(1, "number"), 1234); + uint256[] memory uint_array = config.readUintArray(1, "number_array"); + assertEq(uint_array[0], 5678); + assertEq(uint_array[1], 9999); + + // Read and assert bytes32 values + assertEq(config.readBytes32(1, "word"), bytes32(uint256(1234))); + bytes32[] memory bytes32_array = config.readBytes32Array(1, "word_array"); + assertEq(bytes32_array[0], bytes32(uint256(5678))); + assertEq(bytes32_array[1], bytes32(uint256(9999))); + + // Read and assert bytes values + assertEq(config.readBytes(1, "b"), hex"abcd"); + bytes[] memory bytes_array = config.readBytesArray(1, "b_array"); + assertEq(bytes_array[0], hex"dead"); + assertEq(bytes_array[1], hex"beef"); + + // Read and assert string values + assertEq(config.readString(1, "str"), "foo"); + string[] memory string_array = config.readStringArray(1, "str_array"); + assertEq(string_array[0], "bar"); + assertEq(string_array[1], "baz"); + + // -- OPTIMISM ------------------------------------------------------------- + + // Read and assert RPC URL for Optimism (chain ID 10) + assertEq(config.readRpcUrl(10), "https://mainnet.optimism.io"); + + // Read and assert boolean values + assertFalse(config.readBool(10, "is_live")); + bool_array = config.readBoolArray(10, "bool_array"); + assertFalse(bool_array[0]); + assertTrue(bool_array[1]); + + // Read and assert address values + assertEq(config.readAddress(10, "weth"), 0x4200000000000000000000000000000000000006); + address_array = config.readAddressArray(10, "deps"); + assertEq(address_array[0], 0x2222222222222222222222222222222222222222); + assertEq(address_array[1], 0x3333333333333333333333333333333333333333); + + // Read and assert uint values + assertEq(config.readUint(10, "number"), 9999); + uint_array = config.readUintArray(10, "number_array"); + assertEq(uint_array[0], 1234); + assertEq(uint_array[1], 5678); + + // Read and assert bytes32 values + assertEq(config.readBytes32(10, "word"), bytes32(uint256(9999))); + bytes32_array = config.readBytes32Array(10, "word_array"); + assertEq(bytes32_array[0], bytes32(uint256(1234))); + assertEq(bytes32_array[1], bytes32(uint256(5678))); + + // Read and assert bytes values + assertEq(config.readBytes(10, "b"), hex"dcba"); + bytes_array = config.readBytesArray(10, "b_array"); + assertEq(bytes_array[0], hex"c0ffee"); + assertEq(bytes_array[1], hex"babe"); + + // Read and assert string values + assertEq(config.readString(10, "str"), "alice"); + string_array = config.readStringArray(10, "str_array"); + assertEq(string_array[0], "bob"); + assertEq(string_array[1], "charlie"); +} + function test_loadConfigAndForks() public { _loadConfigAndForks("./test/fixtures/config.toml"); diff --git a/test/fixtures/config.toml b/test/fixtures/config.toml index 59a06497..e1cb70b3 100644 --- a/test/fixtures/config.toml +++ b/test/fixtures/config.toml @@ -1,21 +1,74 @@ +# ------------------------------------------------ +# MAINNET +# ------------------------------------------------ + [mainnet] endpoint_url = "${MAINNET_RPC}" -[mainnet.vars] +[mainnet.bool] is_live = true +bool_array = [true, false] + +[mainnet.address] weth = "${WETH_MAINNET}" deps = [ "0x0000000000000000000000000000000000000000", "0x1111111111111111111111111111111111111111", ] +[mainnet.uint] +number = 1234 +number_array = [5678, 9999] + +[mainnet.bytes32] +word = "0x00000000000000000000000000000000000000000000000000000000000004d2" # 1234 +word_array = [ + "0x000000000000000000000000000000000000000000000000000000000000162e", # 5678 + "0x000000000000000000000000000000000000000000000000000000000000270f", # 9999 +] + +[mainnet.bytes] +b = "0xabcd" +b_array = ["0xdead", "0xbeef"] + +[mainnet.string] +str = "foo" +str_array = ["bar", "baz"] + + +# ------------------------------------------------ +# OPTIMISM +# ------------------------------------------------ + [optimism] endpoint_url = "${OPTIMISM_RPC}" -[optimism.vars] +[optimism.bool] is_live = false +bool_array = [false, true] + +[optimism.address] weth = "${WETH_OPTIMISM}" deps = [ "0x2222222222222222222222222222222222222222", "0x3333333333333333333333333333333333333333", ] + +[optimism.uint] +number = 9999 +number_array = [1234, 5678] + +[optimism.bytes32] +word = "0x000000000000000000000000000000000000000000000000000000000000270f" # 9999 +word_array = [ + "0x00000000000000000000000000000000000000000000000000000000000004d2", # 1234 + "0x000000000000000000000000000000000000000000000000000000000000162e", # 5678 +] + +[optimism.bytes] +b = "0xdcba" +b_array = ["0xc0ffee", "0xbabe"] + +[optimism.string] +str = "alice" +str_array = ["bob", "charlie"] From df41ccfc160cafc271f8358e2a54f498a006f577 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 08:19:14 +0200 Subject: [PATCH 05/54] test: write config --- test/CommonConfig.t.sol | 265 +++++++++++++++++++++++++++------------- 1 file changed, 178 insertions(+), 87 deletions(-) diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol index 17bfac9d..96c5b0a0 100644 --- a/test/CommonConfig.t.sol +++ b/test/CommonConfig.t.sol @@ -4,93 +4,92 @@ pragma solidity >=0.7.0 <0.9.0; import {Test} from "../src/Test.sol"; contract CommonConfigTest is Test { -function test_loadConfig() public { - // Deploy the config contract with the test fixture. - _loadConfig("./test/fixtures/config.toml"); - - // -- MAINNET -------------------------------------------------------------- - - // Read and assert RPC URL for Mainnet (chain ID 1) - assertEq(config.readRpcUrl(1), "https://eth.llamarpc.com"); - - // Read and assert boolean values - assertTrue(config.readBool(1, "is_live")); - bool[] memory bool_array = config.readBoolArray(1, "bool_array"); - assertTrue(bool_array[0]); - assertFalse(bool_array[1]); - - // Read and assert address values - assertEq(config.readAddress(1, "weth"), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - address[] memory address_array = config.readAddressArray(1, "deps"); - assertEq(address_array[0], 0x0000000000000000000000000000000000000000); - assertEq(address_array[1], 0x1111111111111111111111111111111111111111); - - // Read and assert uint values - assertEq(config.readUint(1, "number"), 1234); - uint256[] memory uint_array = config.readUintArray(1, "number_array"); - assertEq(uint_array[0], 5678); - assertEq(uint_array[1], 9999); - - // Read and assert bytes32 values - assertEq(config.readBytes32(1, "word"), bytes32(uint256(1234))); - bytes32[] memory bytes32_array = config.readBytes32Array(1, "word_array"); - assertEq(bytes32_array[0], bytes32(uint256(5678))); - assertEq(bytes32_array[1], bytes32(uint256(9999))); - - // Read and assert bytes values - assertEq(config.readBytes(1, "b"), hex"abcd"); - bytes[] memory bytes_array = config.readBytesArray(1, "b_array"); - assertEq(bytes_array[0], hex"dead"); - assertEq(bytes_array[1], hex"beef"); - - // Read and assert string values - assertEq(config.readString(1, "str"), "foo"); - string[] memory string_array = config.readStringArray(1, "str_array"); - assertEq(string_array[0], "bar"); - assertEq(string_array[1], "baz"); - - // -- OPTIMISM ------------------------------------------------------------- - - // Read and assert RPC URL for Optimism (chain ID 10) - assertEq(config.readRpcUrl(10), "https://mainnet.optimism.io"); - - // Read and assert boolean values - assertFalse(config.readBool(10, "is_live")); - bool_array = config.readBoolArray(10, "bool_array"); - assertFalse(bool_array[0]); - assertTrue(bool_array[1]); - - // Read and assert address values - assertEq(config.readAddress(10, "weth"), 0x4200000000000000000000000000000000000006); - address_array = config.readAddressArray(10, "deps"); - assertEq(address_array[0], 0x2222222222222222222222222222222222222222); - assertEq(address_array[1], 0x3333333333333333333333333333333333333333); - - // Read and assert uint values - assertEq(config.readUint(10, "number"), 9999); - uint_array = config.readUintArray(10, "number_array"); - assertEq(uint_array[0], 1234); - assertEq(uint_array[1], 5678); - - // Read and assert bytes32 values - assertEq(config.readBytes32(10, "word"), bytes32(uint256(9999))); - bytes32_array = config.readBytes32Array(10, "word_array"); - assertEq(bytes32_array[0], bytes32(uint256(1234))); - assertEq(bytes32_array[1], bytes32(uint256(5678))); - - // Read and assert bytes values - assertEq(config.readBytes(10, "b"), hex"dcba"); - bytes_array = config.readBytesArray(10, "b_array"); - assertEq(bytes_array[0], hex"c0ffee"); - assertEq(bytes_array[1], hex"babe"); - - // Read and assert string values - assertEq(config.readString(10, "str"), "alice"); - string_array = config.readStringArray(10, "str_array"); - assertEq(string_array[0], "bob"); - assertEq(string_array[1], "charlie"); -} - + function test_loadConfig() public { + // Deploy the config contract with the test fixture. + _loadConfig("./test/fixtures/config.toml"); + + // -- MAINNET -------------------------------------------------------------- + + // Read and assert RPC URL for Mainnet (chain ID 1) + assertEq(config.readRpcUrl(1), "https://eth.llamarpc.com"); + + // Read and assert boolean values + assertTrue(config.readBool(1, "is_live")); + bool[] memory bool_array = config.readBoolArray(1, "bool_array"); + assertTrue(bool_array[0]); + assertFalse(bool_array[1]); + + // Read and assert address values + assertEq(config.readAddress(1, "weth"), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address[] memory address_array = config.readAddressArray(1, "deps"); + assertEq(address_array[0], 0x0000000000000000000000000000000000000000); + assertEq(address_array[1], 0x1111111111111111111111111111111111111111); + + // Read and assert uint values + assertEq(config.readUint(1, "number"), 1234); + uint256[] memory uint_array = config.readUintArray(1, "number_array"); + assertEq(uint_array[0], 5678); + assertEq(uint_array[1], 9999); + + // Read and assert bytes32 values + assertEq(config.readBytes32(1, "word"), bytes32(uint256(1234))); + bytes32[] memory bytes32_array = config.readBytes32Array(1, "word_array"); + assertEq(bytes32_array[0], bytes32(uint256(5678))); + assertEq(bytes32_array[1], bytes32(uint256(9999))); + + // Read and assert bytes values + assertEq(config.readBytes(1, "b"), hex"abcd"); + bytes[] memory bytes_array = config.readBytesArray(1, "b_array"); + assertEq(bytes_array[0], hex"dead"); + assertEq(bytes_array[1], hex"beef"); + + // Read and assert string values + assertEq(config.readString(1, "str"), "foo"); + string[] memory string_array = config.readStringArray(1, "str_array"); + assertEq(string_array[0], "bar"); + assertEq(string_array[1], "baz"); + + // -- OPTIMISM ------------------------------------------------------------- + + // Read and assert RPC URL for Optimism (chain ID 10) + assertEq(config.readRpcUrl(10), "https://mainnet.optimism.io"); + + // Read and assert boolean values + assertFalse(config.readBool(10, "is_live")); + bool_array = config.readBoolArray(10, "bool_array"); + assertFalse(bool_array[0]); + assertTrue(bool_array[1]); + + // Read and assert address values + assertEq(config.readAddress(10, "weth"), 0x4200000000000000000000000000000000000006); + address_array = config.readAddressArray(10, "deps"); + assertEq(address_array[0], 0x2222222222222222222222222222222222222222); + assertEq(address_array[1], 0x3333333333333333333333333333333333333333); + + // Read and assert uint values + assertEq(config.readUint(10, "number"), 9999); + uint_array = config.readUintArray(10, "number_array"); + assertEq(uint_array[0], 1234); + assertEq(uint_array[1], 5678); + + // Read and assert bytes32 values + assertEq(config.readBytes32(10, "word"), bytes32(uint256(9999))); + bytes32_array = config.readBytes32Array(10, "word_array"); + assertEq(bytes32_array[0], bytes32(uint256(1234))); + assertEq(bytes32_array[1], bytes32(uint256(5678))); + + // Read and assert bytes values + assertEq(config.readBytes(10, "b"), hex"dcba"); + bytes_array = config.readBytesArray(10, "b_array"); + assertEq(bytes_array[0], hex"c0ffee"); + assertEq(bytes_array[1], hex"babe"); + + // Read and assert string values + assertEq(config.readString(10, "str"), "alice"); + string_array = config.readStringArray(10, "str_array"); + assertEq(string_array[0], "bob"); + assertEq(string_array[1], "charlie"); + } function test_loadConfigAndForks() public { _loadConfigAndForks("./test/fixtures/config.toml"); @@ -104,4 +103,96 @@ function test_loadConfig() public { vm.selectFork(forkOf[10]); assertEq(vm.activeChain(), 10); } + + function test_writeConfig() public { + // Create a temporary copy of the config file to avoid modifying the original. + string memory originalConfig = "./test/fixtures/config.toml"; + string memory testConfig = "./test/fixtures/config.t.toml"; + vm.copyFile(originalConfig, testConfig); + + // Deploy the config contract with the temporary fixture. + _loadConfig(testConfig); + + // Update a single boolean value and verify the change. + config.update(1, "is_live", false, true); + + assertFalse(config.readBool(1, "is_live")); + + string memory content = vm.readFile(testConfig); + assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live")); + + // Update a single address value and verify the change. + address new_addr = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; + config.update(1, "weth", new_addr, true); + + assertEq(config.readAddress(1, "weth"), new_addr); + + content = vm.readFile(testConfig); + assertEq(vm.parseTomlAddress(content, "$.mainnet.address.weth"), new_addr); + + // Update a uint array and verify the change. + uint256[] memory new_numbers = new uint256[](3); + new_numbers[0] = 1; + new_numbers[1] = 2; + new_numbers[2] = 3; + config.update(10, "number_array", new_numbers, true); + + uint256[] memory updated_numbers_mem = config.readUintArray(10, "number_array"); + assertEq(updated_numbers_mem.length, 3); + assertEq(updated_numbers_mem[0], 1); + assertEq(updated_numbers_mem[1], 2); + assertEq(updated_numbers_mem[2], 3); + + content = vm.readFile(testConfig); + uint256[] memory updated_numbers_disk = vm.parseTomlUintArray(content, "$.optimism.uint.number_array"); + assertEq(updated_numbers_disk.length, 3); + assertEq(updated_numbers_disk[0], 1); + assertEq(updated_numbers_disk[1], 2); + assertEq(updated_numbers_disk[2], 3); + + // Update a string array and verify the change. + string[] memory new_strings = new string[](2); + new_strings[0] = "hello"; + new_strings[1] = "world"; + config.update(1, "str_array", new_strings, true); + + string[] memory updated_strings_mem = config.readStringArray(1, "str_array"); + assertEq(updated_strings_mem.length, 2); + assertEq(updated_strings_mem[0], "hello"); + assertEq(updated_strings_mem[1], "world"); + + content = vm.readFile(testConfig); + string[] memory updated_strings_disk = vm.parseTomlStringArray(content, "$.mainnet.string.str_array"); + assertEq(updated_strings_disk.length, 2); + assertEq(updated_strings_disk[0], "hello"); + assertEq(updated_strings_disk[1], "world"); + + // Create a new uint variable and verify the change. + config.update(1, "new_uint", 42, true); + + assertEq(config.readUint(1, "new_uint"), 42); + + content = vm.readFile(testConfig); + assertEq(vm.parseTomlUint(content, "$.mainnet.uint.new_uint"), 42); + + // Create a new bytes32 array and verify the change. + bytes32[] memory new_words = new bytes32[](2); + new_words[0] = bytes32(uint256(0xDEAD)); + new_words[1] = bytes32(uint256(0xBEEF)); + config.update(10, "new_words", new_words, true); + + bytes32[] memory updated_words_mem = config.readBytes32Array(10, "new_words"); + assertEq(updated_words_mem.length, 2); + assertEq(updated_words_mem[0], new_words[0]); + assertEq(updated_words_mem[1], new_words[1]); + + content = vm.readFile(testConfig); + bytes32[] memory updated_words_disk = vm.parseTomlBytes32Array(content, "$.optimism.bytes32.new_words"); + assertEq(updated_words_disk.length, 2); + assertEq(vm.toString(updated_words_disk[0]), vm.toString(new_words[0])); + assertEq(vm.toString(updated_words_disk[1]), vm.toString(new_words[1])); + + // Clean up the temporary file. + vm.removeFile(testConfig); + } } From cd346a6d119d716958e2a3cb950ba9cd79e29eda Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 08:58:16 +0200 Subject: [PATCH 06/54] update Vm docs --- src/StdConfig.sol | 2 +- src/Vm.sol | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index bf01fbbd..52f80dff 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -219,7 +219,7 @@ contract StdConfig { function _writeToToml(uint256 chainId, string memory ty, string memory key, string memory jsonValue) private { string memory chainKey = _getChainKeyFromId(chainId); string memory valueKey = string.concat("$.", chainKey, ".", ty, ".", key); - vm.writeToml(jsonValue, _filePath, valueKey); + vm.writeTomlUpsert(jsonValue, _filePath, valueKey); } // -- GETTER FUNCTIONS ----------------------------------------------------- diff --git a/src/Vm.sol b/src/Vm.sol index 524186eb..90108806 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -450,7 +450,7 @@ interface VmSafe { // ======== Environment ======== /// Resolves all the env variable placeholders (`${ENV_VAR}`) of the input string. - function resolveEnv(string calldata input) external view returns (string memory); + function resolveEnv(string calldata input) external view returns (string memory resolvedInput); /// Gets the environment variable `name` and parses it as `address`. /// Reverts if the variable was not found or could not be parsed. @@ -614,7 +614,7 @@ interface VmSafe { function activeFork() external view returns (uint256 forkId); /// Returns the chain id of the currently active fork. Reverts if no fork is currently active. - function activeChain() external view returns(uint256); + function activeChain() external view returns(uint256 chainId); /// 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); @@ -1854,6 +1854,11 @@ interface VmSafe { /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. function writeToml(string calldata json, string calldata path, string calldata valueKey) external; + /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = + /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. + /// Unlike `writeToml`, this cheatcode will create new keys if they do not already exist. + function writeTomlUpsert(string calldata json, string calldata path, string calldata valueKey) external; + // ======== Utilities ======== /// Compute the address of a contract created with CREATE2 using the given CREATE2 deployer. From 8128951e56d02e0bb7f84debf3b9b7feff2072b6 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 12:56:07 +0200 Subject: [PATCH 07/54] rename to `vm.activeChain()` to `vm.getChainId()` --- src/StdConfig.sol | 50 ++++++++++++++++++++--------------------- src/Vm.sol | 6 +++++ test/CommonConfig.t.sol | 4 ++-- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 52f80dff..65831567 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -240,7 +240,7 @@ contract StdConfig { } function readRpcUrl() public view returns (string memory) { - return _rpcOf[vm.activeChain()]; + return _rpcOf[vm.getChainId()]; } function readBool(uint256 chain_id, string memory key) public view returns (bool) { @@ -248,7 +248,7 @@ contract StdConfig { } function readBool(string memory key) public view returns (bool) { - return _boolsOf[vm.activeChain()][key]; + return _boolsOf[vm.getChainId()][key]; } function readUint(uint256 chain_id, string memory key) public view returns (uint256) { @@ -256,7 +256,7 @@ contract StdConfig { } function readUint(string memory key) public view returns (uint256) { - return _uintsOf[vm.activeChain()][key]; + return _uintsOf[vm.getChainId()][key]; } function readAddress(uint256 chain_id, string memory key) public view returns (address) { @@ -264,7 +264,7 @@ contract StdConfig { } function readAddress(string memory key) public view returns (address) { - return _addressesOf[vm.activeChain()][key]; + return _addressesOf[vm.getChainId()][key]; } function readBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { @@ -272,7 +272,7 @@ contract StdConfig { } function readBytes32(string memory key) public view returns (bytes32) { - return _bytes32sOf[vm.activeChain()][key]; + return _bytes32sOf[vm.getChainId()][key]; } function readString(uint256 chain_id, string memory key) public view returns (string memory) { @@ -280,7 +280,7 @@ contract StdConfig { } function readString(string memory key) public view returns (string memory) { - return _stringsOf[vm.activeChain()][key]; + return _stringsOf[vm.getChainId()][key]; } function readBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { @@ -288,7 +288,7 @@ contract StdConfig { } function readBytes(string memory key) public view returns (bytes memory) { - return _bytesOf[vm.activeChain()][key]; + return _bytesOf[vm.getChainId()][key]; } function readBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { @@ -296,7 +296,7 @@ contract StdConfig { } function readBoolArray(string memory key) public view returns (bool[] memory) { - return _boolArraysOf[vm.activeChain()][key]; + return _boolArraysOf[vm.getChainId()][key]; } function readUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { @@ -304,7 +304,7 @@ contract StdConfig { } function readUintArray(string memory key) public view returns (uint256[] memory) { - return _uintArraysOf[vm.activeChain()][key]; + return _uintArraysOf[vm.getChainId()][key]; } function readAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { @@ -312,7 +312,7 @@ contract StdConfig { } function readAddressArray(string memory key) public view returns (address[] memory) { - return _addressArraysOf[vm.activeChain()][key]; + return _addressArraysOf[vm.getChainId()][key]; } function readBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { @@ -320,7 +320,7 @@ contract StdConfig { } function readBytes32Array(string memory key) public view returns (bytes32[] memory) { - return _bytes32ArraysOf[vm.activeChain()][key]; + return _bytes32ArraysOf[vm.getChainId()][key]; } function readStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { @@ -328,7 +328,7 @@ contract StdConfig { } function readStringArray(string memory key) public view returns (string[] memory) { - return _stringArraysOf[vm.activeChain()][key]; + return _stringArraysOf[vm.getChainId()][key]; } function readBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { @@ -336,7 +336,7 @@ contract StdConfig { } function readBytesArray(string memory key) public view returns (bytes[] memory) { - return _bytesArraysOf[vm.activeChain()][key]; + return _bytesArraysOf[vm.getChainId()][key]; } // -- SETTER FUNCTIONS ----------------------------------------------------- @@ -347,7 +347,7 @@ contract StdConfig { } function update(string memory key, bool value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); _boolsOf[chainId][key] = value; if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } @@ -358,7 +358,7 @@ contract StdConfig { } function update(string memory key, uint256 value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); _uintsOf[chainId][key] = value; if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } @@ -369,7 +369,7 @@ contract StdConfig { } function update(string memory key, address value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); _addressesOf[chainId][key] = value; if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } @@ -380,7 +380,7 @@ contract StdConfig { } function update(string memory key, bytes32 value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); _bytes32sOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } @@ -391,7 +391,7 @@ contract StdConfig { } function update(string memory key, string memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); _stringsOf[chainId][key] = value; if (write) _writeToToml(chainId, "string", key, _quote(value)); } @@ -402,7 +402,7 @@ contract StdConfig { } function update(string memory key, bytes memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); _bytesOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } @@ -421,7 +421,7 @@ contract StdConfig { } function update(string memory key, bool[] memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } @@ -439,7 +439,7 @@ contract StdConfig { } function update(string memory key, uint256[] memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } @@ -457,7 +457,7 @@ contract StdConfig { } function update(string memory key, address[] memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } @@ -475,7 +475,7 @@ contract StdConfig { } function update(string memory key, bytes32[] memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } @@ -493,7 +493,7 @@ contract StdConfig { } function update(string memory key, string[] memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } @@ -511,7 +511,7 @@ contract StdConfig { } function update(string memory key, bytes[] memory value, bool write) public { - uint256 chainId = vm.activeChain(); + uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } } diff --git a/src/Vm.sol b/src/Vm.sol index 90108806..f8b6aa31 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -645,6 +645,12 @@ interface VmSafe { /// See https://github.com/foundry-rs/foundry/issues/6180 function getBlockTimestamp() external view returns (uint256 timestamp); + /// Gets the current `block.chainid` of the currently selected environment. + /// You should use this instead of `block.chainid` if you use `vm.selectFork` or `vm.createSelectFork`, as `block.chainid` could be 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 getChainId() external view returns (uint256 blockChainId); + /// 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/CommonConfig.t.sol b/test/CommonConfig.t.sol index 96c5b0a0..16e4ef28 100644 --- a/test/CommonConfig.t.sol +++ b/test/CommonConfig.t.sol @@ -97,11 +97,11 @@ contract CommonConfigTest is Test { // assert that the map of chain id and fork ids is created and that the chain ids actually match assertEq(forkOf[1], 0); vm.selectFork(forkOf[1]); - assertEq(vm.activeChain(), 1); + assertEq(vm.getChainId(), 1); assertEq(forkOf[10], 1); vm.selectFork(forkOf[10]); - assertEq(vm.activeChain(), 10); + assertEq(vm.getChainId(), 10); } function test_writeConfig() public { From a1fe691bb15c12bc8df902e7b909efdca1f4000f Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 13:54:55 +0200 Subject: [PATCH 08/54] improve docs --- src/StdConfig.sol | 112 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 11 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 65831567..1a17a395 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -9,12 +9,13 @@ import {VmSafe} from "./Vm.sol"; /// /// @dev This contract assumes a toml structure where top-level keys /// represent chain ids or profiles. Under each chain key, variables are -/// organized by type in separate sub-tables. +/// organized by type in separate sub-tables like `[.]`, where +/// type must be: `bool`, `address`, `uint`, `bytes32`, `string`, or `bytes`. /// /// Supported format: /// ``` /// [mainnet] -/// endpoint_url = "https://eth.llamarpc.com" +/// endpoint_url = "${MAINNET_RPC}" /// /// [mainnet.bool] /// is_live = true @@ -22,28 +23,32 @@ import {VmSafe} from "./Vm.sol"; /// [mainnet.address] /// weth = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" /// whitelisted_admins = [ -/// "0x0000000000000000000000000000000000000001", -/// "0x0000000000000000000000000000000000000002" +/// "${MAINNET_ADMIN}", +/// "0x00000000000000000000000000000000deadbeef", +/// "0x000000000000000000000000000000c0ffeebabe" /// ] /// -/// [optimism.uint] +/// [mainnet.uint] /// important_number = 123 /// ``` contract StdConfig { + // -- CONSTANTS ------------------------------------------------------------ + + /// @dev Forge Standard Library VM interface for cheat codes. VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); - // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------- + // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ - // File path + // Path to the loaded TOML configuration file. string private _filePath; - // Keys of the configured chains. + // List of top-level keys found in the TOML file, assumed to be chain names/aliases. string[] private _chainKeys; - // Storage for the configured RPC url. + // Storage for the configured RPC URL for each chain. mapping(uint256 chainId => string url) private _rpcOf; - // Storage for variable types. + // Storage for single value types organized by chain ID and variable key. mapping(uint256 chainId => mapping(string key => bool value)) private _boolsOf; mapping(uint256 chainId => mapping(string key => uint256 value)) private _uintsOf; mapping(uint256 chainId => mapping(string key => address value)) private _addressesOf; @@ -51,7 +56,7 @@ contract StdConfig { mapping(uint256 chainId => mapping(string key => string value)) private _stringsOf; mapping(uint256 chainId => mapping(string key => bytes value)) private _bytesOf; - // Storage for array variable types. + // Storage for array value types organized by chain ID and variable key. mapping(uint256 chainId => mapping(string key => bool[] value)) private _boolArraysOf; mapping(uint256 chainId => mapping(string key => uint256[] value)) private _uintArraysOf; mapping(uint256 chainId => mapping(string key => address[] value)) private _addressArraysOf; @@ -189,6 +194,11 @@ contract StdConfig { // -- HELPER FUNCTIONS -----------------------------------------------------\n + /// @notice Resolves a chain alias or a chain id string to its numerical chain id. + /// @param aliasOrId The string representing the chain alias (i.e. "mainnet") or a numerical ID (i.e. "1"). + /// @return The numerical chain ID. + /// @dev It first attempts to parse the input as a number. If that fails, it uses `vm.getChain` to resolve a named alias. + /// Reverts if the alias is not valid or not a number. function resolveChainId(string memory aliasOrId) public view returns (uint256) { try vm.parseUint(aliasOrId) returns (uint256 chainId) { return chainId; @@ -201,6 +211,7 @@ contract StdConfig { } } + /// @dev Retrieves the chain key/alias from the configuration based on the chain ID. function _getChainKeyFromId(uint256 chainId) private view returns (string memory) { for (uint i = 0; i < _chainKeys.length; i++) { if (resolveChainId(_chainKeys[i]) == chainId) { @@ -216,6 +227,10 @@ contract StdConfig { } /// @dev Writes a JSON-formatted value to a specific key in the TOML file. + /// @param chainId The chain id to write under. + /// @param ty The type category ('bool', 'address', 'uint', 'bytes32', 'string', or 'bytes'). + /// @param key The variable key name. + /// @param jsonValue The JSON-formatted value to write. function _writeToToml(uint256 chainId, string memory ty, string memory key, string memory jsonValue) private { string memory chainKey = _getChainKeyFromId(chainId); string memory valueKey = string.concat("$.", chainKey, ".", ty, ".", key); @@ -224,6 +239,7 @@ contract StdConfig { // -- GETTER FUNCTIONS ----------------------------------------------------- + /// @notice Returns the numerical chain ids for all configured chains. function readChainIds() public view returns (uint256[] memory) { string[] memory keys = _chainKeys; @@ -235,178 +251,230 @@ contract StdConfig { return ids; } + /// @notice Reads the RPC URL for a specific chain id. function readRpcUrl(uint256 chain_id) public view returns (string memory) { return _rpcOf[chain_id]; } + /// @notice Reads the RPC URL for the current chain. function readRpcUrl() public view returns (string memory) { return _rpcOf[vm.getChainId()]; } + /// @notice Reads a boolean value for a given key and chain ID. function readBool(uint256 chain_id, string memory key) public view returns (bool) { return _boolsOf[chain_id][key]; } + /// @notice Reads a boolean value for a given key on the current chain. function readBool(string memory key) public view returns (bool) { return _boolsOf[vm.getChainId()][key]; } + /// @notice Reads a uint256 value for a given key and chain ID. function readUint(uint256 chain_id, string memory key) public view returns (uint256) { return _uintsOf[chain_id][key]; } + /// @notice Reads a uint256 value for a given key on the current chain. function readUint(string memory key) public view returns (uint256) { return _uintsOf[vm.getChainId()][key]; } + /// @notice Reads an address value for a given key and chain ID. function readAddress(uint256 chain_id, string memory key) public view returns (address) { return _addressesOf[chain_id][key]; } + /// @notice Reads an address value for a given key on the current chain. function readAddress(string memory key) public view returns (address) { return _addressesOf[vm.getChainId()][key]; } + /// @notice Reads a bytes32 value for a given key and chain ID. function readBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { return _bytes32sOf[chain_id][key]; } + /// @notice Reads a bytes32 value for a given key on the current chain. function readBytes32(string memory key) public view returns (bytes32) { return _bytes32sOf[vm.getChainId()][key]; } + /// @notice Reads a string value for a given key and chain ID. function readString(uint256 chain_id, string memory key) public view returns (string memory) { return _stringsOf[chain_id][key]; } + /// @notice Reads a string value for a given key on the current chain. function readString(string memory key) public view returns (string memory) { return _stringsOf[vm.getChainId()][key]; } + /// @notice Reads a bytes value for a given key and chain ID. function readBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { return _bytesOf[chain_id][key]; } + /// @notice Reads a bytes value for a given key on the current chain. function readBytes(string memory key) public view returns (bytes memory) { return _bytesOf[vm.getChainId()][key]; } + /// @notice Reads a boolean array for a given key and chain ID. function readBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { return _boolArraysOf[chain_id][key]; } + /// @notice Reads a boolean array for a given key on the current chain. function readBoolArray(string memory key) public view returns (bool[] memory) { return _boolArraysOf[vm.getChainId()][key]; } + /// @notice Reads a uint256 array for a given key and chain ID. function readUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { return _uintArraysOf[chain_id][key]; } + /// @notice Reads a uint256 array for a given key on the current chain. function readUintArray(string memory key) public view returns (uint256[] memory) { return _uintArraysOf[vm.getChainId()][key]; } + /// @notice Reads an address array for a given key and chain ID. function readAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { return _addressArraysOf[chain_id][key]; } + /// @notice Reads an address array for a given key on the current chain. function readAddressArray(string memory key) public view returns (address[] memory) { return _addressArraysOf[vm.getChainId()][key]; } + /// @notice Reads a bytes32 array for a given key and chain ID. function readBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { return _bytes32ArraysOf[chain_id][key]; } + /// @notice Reads a bytes32 array for a given key on the current chain. function readBytes32Array(string memory key) public view returns (bytes32[] memory) { return _bytes32ArraysOf[vm.getChainId()][key]; } + /// @notice Reads a string array for a given key and chain ID. function readStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { return _stringArraysOf[chain_id][key]; } + /// @notice Reads a string array for a given key on the current chain. function readStringArray(string memory key) public view returns (string[] memory) { return _stringArraysOf[vm.getChainId()][key]; } + /// @notice Reads a bytes array for a given key and chain ID. function readBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { return _bytesArraysOf[chain_id][key]; } + /// @notice Reads a bytes array for a given key on the current chain. function readBytesArray(string memory key) public view returns (bytes[] memory) { return _bytesArraysOf[vm.getChainId()][key]; } // -- SETTER FUNCTIONS ----------------------------------------------------- + /// @notice Updates a boolean value for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, bool value, bool write) public { _boolsOf[chainId][key] = value; if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } + /// @notice Updates a boolean value for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, bool value, bool write) public { uint256 chainId = vm.getChainId(); _boolsOf[chainId][key] = value; if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } + /// @notice Updates a uint256 value for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, uint256 value, bool write) public { _uintsOf[chainId][key] = value; if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } + /// @notice Updates a uint256 value for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, uint256 value, bool write) public { uint256 chainId = vm.getChainId(); _uintsOf[chainId][key] = value; if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } + /// @notice Updates an address value for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, address value, bool write) public { _addressesOf[chainId][key] = value; if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } + /// @notice Updates an address value for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, address value, bool write) public { uint256 chainId = vm.getChainId(); _addressesOf[chainId][key] = value; if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } + /// @notice Updates a bytes32 value for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, bytes32 value, bool write) public { _bytes32sOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } + /// @notice Updates a bytes32 value for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, bytes32 value, bool write) public { uint256 chainId = vm.getChainId(); _bytes32sOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } + /// @notice Updates a string value for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, string memory value, bool write) public { _stringsOf[chainId][key] = value; if (write) _writeToToml(chainId, "string", key, _quote(value)); } + /// @notice Updates a string value for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, string memory value, bool write) public { uint256 chainId = vm.getChainId(); _stringsOf[chainId][key] = value; if (write) _writeToToml(chainId, "string", key, _quote(value)); } + /// @notice Updates a bytes value for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, bytes memory value, bool write) public { _bytesOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } + /// @notice Updates a bytes value for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, bytes memory value, bool write) public { uint256 chainId = vm.getChainId(); _bytesOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } + /// @notice Updates a boolean array for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, bool[] memory value, bool write) public { _boolArraysOf[chainId][key] = value; if (write) { @@ -420,11 +488,15 @@ contract StdConfig { } } + /// @notice Updates a boolean array for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, bool[] memory value, bool write) public { uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } + /// @notice Updates a uint256 array for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, uint256[] memory value, bool write) public { _uintArraysOf[chainId][key] = value; if (write) { @@ -438,11 +510,15 @@ contract StdConfig { } } + /// @notice Updates a uint256 array for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, uint256[] memory value, bool write) public { uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } + /// @notice Updates an address array for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, address[] memory value, bool write) public { _addressArraysOf[chainId][key] = value; if (write) { @@ -456,11 +532,15 @@ contract StdConfig { } } + /// @notice Updates an address array for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, address[] memory value, bool write) public { uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } + /// @notice Updates a bytes32 array for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { _bytes32ArraysOf[chainId][key] = value; if (write) { @@ -474,11 +554,15 @@ contract StdConfig { } } + /// @notice Updates a bytes32 array for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, bytes32[] memory value, bool write) public { uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } + /// @notice Updates a string array for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, string[] memory value, bool write) public { _stringArraysOf[chainId][key] = value; if (write) { @@ -492,11 +576,15 @@ contract StdConfig { } } + /// @notice Updates a string array for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, string[] memory value, bool write) public { uint256 chainId = vm.getChainId(); update(chainId, key, value, write); } + /// @notice Updates a bytes array for a given key and chain ID. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(uint256 chainId, string memory key, bytes[] memory value, bool write) public { _bytesArraysOf[chainId][key] = value; if (write) { @@ -510,6 +598,8 @@ contract StdConfig { } } + /// @notice Updates a bytes array for a given key on the current chain. + /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. function update(string memory key, bytes[] memory value, bool write) public { uint256 chainId = vm.getChainId(); update(chainId, key, value, write); From 405d958f055cfce692c45ff7d89b7b59ccf08e79 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 13:58:20 +0200 Subject: [PATCH 09/54] more docs --- test/fixtures/config.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/fixtures/config.toml b/test/fixtures/config.toml index e1cb70b3..3597f90c 100644 --- a/test/fixtures/config.toml +++ b/test/fixtures/config.toml @@ -1,7 +1,9 @@ # ------------------------------------------------ -# MAINNET +# EXAMPLE DEPLOYMENT CONFIG # ------------------------------------------------ +# -- MAINNET ------------------------------------- + [mainnet] endpoint_url = "${MAINNET_RPC}" @@ -35,10 +37,7 @@ b_array = ["0xdead", "0xbeef"] str = "foo" str_array = ["bar", "baz"] - -# ------------------------------------------------ -# OPTIMISM -# ------------------------------------------------ +# -- OPTIMISM ------------------------------------ [optimism] endpoint_url = "${OPTIMISM_RPC}" From 158f4db5a09fe3dd166f036614c3925cdf63343e Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 14:07:17 +0200 Subject: [PATCH 10/54] fix doc typo --- src/StdConfig.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 1a17a395..a139bb12 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -8,7 +8,7 @@ import {VmSafe} from "./Vm.sol"; /// variables into storage, automatically casting them, on deployment. /// /// @dev This contract assumes a toml structure where top-level keys -/// represent chain ids or profiles. Under each chain key, variables are +/// represent chain ids or aliases. Under each chain key, variables are /// organized by type in separate sub-tables like `[.]`, where /// type must be: `bool`, `address`, `uint`, `bytes32`, `string`, or `bytes`. /// From bad0161d89408279ffa261dc755a5a0861992424 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 15:00:17 +0200 Subject: [PATCH 11/54] update fn names to `get` and `set` for consistency --- src/Base.sol | 4 +- src/StdConfig.sol | 210 ++++++++++++++++++++-------------------- test/CommonConfig.t.sol | 76 +++++++-------- 3 files changed, 145 insertions(+), 145 deletions(-) diff --git a/src/Base.sol b/src/Base.sol index 8301cc48..5b65710d 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -80,10 +80,10 @@ abstract contract CommonConfig is CommonBase { _loadConfig(filePath); console.log("Setting up forks for the configured chains..."); - uint256[] memory chains = config.readChainIds(); + uint256[] memory chains = config.getChainIds(); for (uint i = 0; i < chains.length; i++) { uint256 chainId = chains[i]; - uint256 forkId = vm.createFork(config.readRpcUrl(chainId)); + uint256 forkId = vm.createFork(config.getRpcUrl(chainId)); forkOf[chainId] = forkId; chainIds.push(chainId); } diff --git a/src/StdConfig.sol b/src/StdConfig.sol index a139bb12..4ea1d845 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -240,7 +240,7 @@ contract StdConfig { // -- GETTER FUNCTIONS ----------------------------------------------------- /// @notice Returns the numerical chain ids for all configured chains. - function readChainIds() public view returns (uint256[] memory) { + function getChainIds() public view returns (uint256[] memory) { string[] memory keys = _chainKeys; uint256[] memory ids = new uint256[](keys.length); @@ -252,230 +252,230 @@ contract StdConfig { } /// @notice Reads the RPC URL for a specific chain id. - function readRpcUrl(uint256 chain_id) public view returns (string memory) { + function getRpcUrl(uint256 chain_id) public view returns (string memory) { return _rpcOf[chain_id]; } /// @notice Reads the RPC URL for the current chain. - function readRpcUrl() public view returns (string memory) { + function getRpcUrl() public view returns (string memory) { return _rpcOf[vm.getChainId()]; } /// @notice Reads a boolean value for a given key and chain ID. - function readBool(uint256 chain_id, string memory key) public view returns (bool) { + function getBool(uint256 chain_id, string memory key) public view returns (bool) { return _boolsOf[chain_id][key]; } /// @notice Reads a boolean value for a given key on the current chain. - function readBool(string memory key) public view returns (bool) { + function getBool(string memory key) public view returns (bool) { return _boolsOf[vm.getChainId()][key]; } /// @notice Reads a uint256 value for a given key and chain ID. - function readUint(uint256 chain_id, string memory key) public view returns (uint256) { + function getUint(uint256 chain_id, string memory key) public view returns (uint256) { return _uintsOf[chain_id][key]; } /// @notice Reads a uint256 value for a given key on the current chain. - function readUint(string memory key) public view returns (uint256) { + function getUint(string memory key) public view returns (uint256) { return _uintsOf[vm.getChainId()][key]; } /// @notice Reads an address value for a given key and chain ID. - function readAddress(uint256 chain_id, string memory key) public view returns (address) { + function getAddress(uint256 chain_id, string memory key) public view returns (address) { return _addressesOf[chain_id][key]; } /// @notice Reads an address value for a given key on the current chain. - function readAddress(string memory key) public view returns (address) { + function getAddress(string memory key) public view returns (address) { return _addressesOf[vm.getChainId()][key]; } /// @notice Reads a bytes32 value for a given key and chain ID. - function readBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { + function getBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { return _bytes32sOf[chain_id][key]; } /// @notice Reads a bytes32 value for a given key on the current chain. - function readBytes32(string memory key) public view returns (bytes32) { + function getBytes32(string memory key) public view returns (bytes32) { return _bytes32sOf[vm.getChainId()][key]; } /// @notice Reads a string value for a given key and chain ID. - function readString(uint256 chain_id, string memory key) public view returns (string memory) { + function getString(uint256 chain_id, string memory key) public view returns (string memory) { return _stringsOf[chain_id][key]; } /// @notice Reads a string value for a given key on the current chain. - function readString(string memory key) public view returns (string memory) { + function getString(string memory key) public view returns (string memory) { return _stringsOf[vm.getChainId()][key]; } /// @notice Reads a bytes value for a given key and chain ID. - function readBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { + function getBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { return _bytesOf[chain_id][key]; } /// @notice Reads a bytes value for a given key on the current chain. - function readBytes(string memory key) public view returns (bytes memory) { + function getBytes(string memory key) public view returns (bytes memory) { return _bytesOf[vm.getChainId()][key]; } /// @notice Reads a boolean array for a given key and chain ID. - function readBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { + function getBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { return _boolArraysOf[chain_id][key]; } /// @notice Reads a boolean array for a given key on the current chain. - function readBoolArray(string memory key) public view returns (bool[] memory) { + function getBoolArray(string memory key) public view returns (bool[] memory) { return _boolArraysOf[vm.getChainId()][key]; } /// @notice Reads a uint256 array for a given key and chain ID. - function readUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { + function getUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { return _uintArraysOf[chain_id][key]; } /// @notice Reads a uint256 array for a given key on the current chain. - function readUintArray(string memory key) public view returns (uint256[] memory) { + function getUintArray(string memory key) public view returns (uint256[] memory) { return _uintArraysOf[vm.getChainId()][key]; } /// @notice Reads an address array for a given key and chain ID. - function readAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { + function getAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { return _addressArraysOf[chain_id][key]; } /// @notice Reads an address array for a given key on the current chain. - function readAddressArray(string memory key) public view returns (address[] memory) { + function getAddressArray(string memory key) public view returns (address[] memory) { return _addressArraysOf[vm.getChainId()][key]; } /// @notice Reads a bytes32 array for a given key and chain ID. - function readBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { + function getBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { return _bytes32ArraysOf[chain_id][key]; } /// @notice Reads a bytes32 array for a given key on the current chain. - function readBytes32Array(string memory key) public view returns (bytes32[] memory) { + function getBytes32Array(string memory key) public view returns (bytes32[] memory) { return _bytes32ArraysOf[vm.getChainId()][key]; } /// @notice Reads a string array for a given key and chain ID. - function readStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { + function getStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { return _stringArraysOf[chain_id][key]; } /// @notice Reads a string array for a given key on the current chain. - function readStringArray(string memory key) public view returns (string[] memory) { + function getStringArray(string memory key) public view returns (string[] memory) { return _stringArraysOf[vm.getChainId()][key]; } /// @notice Reads a bytes array for a given key and chain ID. - function readBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { + function getBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { return _bytesArraysOf[chain_id][key]; } /// @notice Reads a bytes array for a given key on the current chain. - function readBytesArray(string memory key) public view returns (bytes[] memory) { + function getBytesArray(string memory key) public view returns (bytes[] memory) { return _bytesArraysOf[vm.getChainId()][key]; } // -- SETTER FUNCTIONS ----------------------------------------------------- - /// @notice Updates a boolean value for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, bool value, bool write) public { + /// @notice Sets a boolean value for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, bool value, bool write) public { _boolsOf[chainId][key] = value; if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } - /// @notice Updates a boolean value for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, bool value, bool write) public { + /// @notice Sets a boolean value for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, bool value, bool write) public { uint256 chainId = vm.getChainId(); _boolsOf[chainId][key] = value; if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } - /// @notice Updates a uint256 value for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, uint256 value, bool write) public { + /// @notice Sets a uint256 value for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, uint256 value, bool write) public { _uintsOf[chainId][key] = value; if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } - /// @notice Updates a uint256 value for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, uint256 value, bool write) public { + /// @notice Sets a uint256 value for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, uint256 value, bool write) public { uint256 chainId = vm.getChainId(); _uintsOf[chainId][key] = value; if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } - /// @notice Updates an address value for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, address value, bool write) public { + /// @notice Sets an address value for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, address value, bool write) public { _addressesOf[chainId][key] = value; if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } - /// @notice Updates an address value for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, address value, bool write) public { + /// @notice Sets an address value for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, address value, bool write) public { uint256 chainId = vm.getChainId(); _addressesOf[chainId][key] = value; if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } - /// @notice Updates a bytes32 value for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, bytes32 value, bool write) public { + /// @notice Sets a bytes32 value for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, bytes32 value, bool write) public { _bytes32sOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } - /// @notice Updates a bytes32 value for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, bytes32 value, bool write) public { + /// @notice Sets a bytes32 value for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, bytes32 value, bool write) public { uint256 chainId = vm.getChainId(); _bytes32sOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } - /// @notice Updates a string value for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, string memory value, bool write) public { + /// @notice Sets a string value for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, string memory value, bool write) public { _stringsOf[chainId][key] = value; if (write) _writeToToml(chainId, "string", key, _quote(value)); } - /// @notice Updates a string value for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, string memory value, bool write) public { + /// @notice Sets a string value for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, string memory value, bool write) public { uint256 chainId = vm.getChainId(); _stringsOf[chainId][key] = value; if (write) _writeToToml(chainId, "string", key, _quote(value)); } - /// @notice Updates a bytes value for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, bytes memory value, bool write) public { + /// @notice Sets a bytes value for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, bytes memory value, bool write) public { _bytesOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } - /// @notice Updates a bytes value for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, bytes memory value, bool write) public { + /// @notice Sets a bytes value for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, bytes memory value, bool write) public { uint256 chainId = vm.getChainId(); _bytesOf[chainId][key] = value; if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } - /// @notice Updates a boolean array for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, bool[] memory value, bool write) public { + /// @notice Sets a boolean array for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, bool[] memory value, bool write) public { _boolArraysOf[chainId][key] = value; if (write) { string memory json = "["; @@ -488,16 +488,16 @@ contract StdConfig { } } - /// @notice Updates a boolean array for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, bool[] memory value, bool write) public { + /// @notice Sets a boolean array for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, bool[] memory value, bool write) public { uint256 chainId = vm.getChainId(); - update(chainId, key, value, write); + set(chainId, key, value, write); } - /// @notice Updates a uint256 array for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, uint256[] memory value, bool write) public { + /// @notice Sets a uint256 array for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, uint256[] memory value, bool write) public { _uintArraysOf[chainId][key] = value; if (write) { string memory json = "["; @@ -510,16 +510,16 @@ contract StdConfig { } } - /// @notice Updates a uint256 array for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, uint256[] memory value, bool write) public { + /// @notice Sets a uint256 array for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, uint256[] memory value, bool write) public { uint256 chainId = vm.getChainId(); - update(chainId, key, value, write); + set(chainId, key, value, write); } - /// @notice Updates an address array for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, address[] memory value, bool write) public { + /// @notice Sets an address array for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, address[] memory value, bool write) public { _addressArraysOf[chainId][key] = value; if (write) { string memory json = "["; @@ -532,16 +532,16 @@ contract StdConfig { } } - /// @notice Updates an address array for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, address[] memory value, bool write) public { + /// @notice Sets an address array for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, address[] memory value, bool write) public { uint256 chainId = vm.getChainId(); - update(chainId, key, value, write); + set(chainId, key, value, write); } - /// @notice Updates a bytes32 array for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { + /// @notice Sets a bytes32 array for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { _bytes32ArraysOf[chainId][key] = value; if (write) { string memory json = "["; @@ -554,16 +554,16 @@ contract StdConfig { } } - /// @notice Updates a bytes32 array for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, bytes32[] memory value, bool write) public { + /// @notice Sets a bytes32 array for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, bytes32[] memory value, bool write) public { uint256 chainId = vm.getChainId(); - update(chainId, key, value, write); + set(chainId, key, value, write); } - /// @notice Updates a string array for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, string[] memory value, bool write) public { + /// @notice Sets a string array for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, string[] memory value, bool write) public { _stringArraysOf[chainId][key] = value; if (write) { string memory json = "["; @@ -576,16 +576,16 @@ contract StdConfig { } } - /// @notice Updates a string array for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, string[] memory value, bool write) public { + /// @notice Sets a string array for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, string[] memory value, bool write) public { uint256 chainId = vm.getChainId(); - update(chainId, key, value, write); + set(chainId, key, value, write); } - /// @notice Updates a bytes array for a given key and chain ID. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(uint256 chainId, string memory key, bytes[] memory value, bool write) public { + /// @notice Sets a bytes array for a given key and chain ID. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(uint256 chainId, string memory key, bytes[] memory value, bool write) public { _bytesArraysOf[chainId][key] = value; if (write) { string memory json = "["; @@ -598,10 +598,10 @@ contract StdConfig { } } - /// @notice Updates a bytes array for a given key on the current chain. - /// @dev Updates the cached value in storage and optionally writes the change back to the TOML file. - function update(string memory key, bytes[] memory value, bool write) public { + /// @notice Sets a bytes array for a given key on the current chain. + /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. + function set(string memory key, bytes[] memory value, bool write) public { uint256 chainId = vm.getChainId(); - update(chainId, key, value, write); + set(chainId, key, value, write); } } diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol index 16e4ef28..bf4bcee8 100644 --- a/test/CommonConfig.t.sol +++ b/test/CommonConfig.t.sol @@ -11,82 +11,82 @@ contract CommonConfigTest is Test { // -- MAINNET -------------------------------------------------------------- // Read and assert RPC URL for Mainnet (chain ID 1) - assertEq(config.readRpcUrl(1), "https://eth.llamarpc.com"); + assertEq(config.getRpcUrl(1), "https://eth.llamarpc.com"); // Read and assert boolean values - assertTrue(config.readBool(1, "is_live")); - bool[] memory bool_array = config.readBoolArray(1, "bool_array"); + assertTrue(config.getBool(1, "is_live")); + bool[] memory bool_array = config.getBoolArray(1, "bool_array"); assertTrue(bool_array[0]); assertFalse(bool_array[1]); // Read and assert address values - assertEq(config.readAddress(1, "weth"), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - address[] memory address_array = config.readAddressArray(1, "deps"); + assertEq(config.getAddress(1, "weth"), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address[] memory address_array = config.getAddressArray(1, "deps"); assertEq(address_array[0], 0x0000000000000000000000000000000000000000); assertEq(address_array[1], 0x1111111111111111111111111111111111111111); // Read and assert uint values - assertEq(config.readUint(1, "number"), 1234); - uint256[] memory uint_array = config.readUintArray(1, "number_array"); + assertEq(config.getUint(1, "number"), 1234); + uint256[] memory uint_array = config.getUintArray(1, "number_array"); assertEq(uint_array[0], 5678); assertEq(uint_array[1], 9999); // Read and assert bytes32 values - assertEq(config.readBytes32(1, "word"), bytes32(uint256(1234))); - bytes32[] memory bytes32_array = config.readBytes32Array(1, "word_array"); + assertEq(config.getBytes32(1, "word"), bytes32(uint256(1234))); + bytes32[] memory bytes32_array = config.getBytes32Array(1, "word_array"); assertEq(bytes32_array[0], bytes32(uint256(5678))); assertEq(bytes32_array[1], bytes32(uint256(9999))); // Read and assert bytes values - assertEq(config.readBytes(1, "b"), hex"abcd"); - bytes[] memory bytes_array = config.readBytesArray(1, "b_array"); + assertEq(config.getBytes(1, "b"), hex"abcd"); + bytes[] memory bytes_array = config.getBytesArray(1, "b_array"); assertEq(bytes_array[0], hex"dead"); assertEq(bytes_array[1], hex"beef"); // Read and assert string values - assertEq(config.readString(1, "str"), "foo"); - string[] memory string_array = config.readStringArray(1, "str_array"); + assertEq(config.getString(1, "str"), "foo"); + string[] memory string_array = config.getStringArray(1, "str_array"); assertEq(string_array[0], "bar"); assertEq(string_array[1], "baz"); // -- OPTIMISM ------------------------------------------------------------- // Read and assert RPC URL for Optimism (chain ID 10) - assertEq(config.readRpcUrl(10), "https://mainnet.optimism.io"); + assertEq(config.getRpcUrl(10), "https://mainnet.optimism.io"); // Read and assert boolean values - assertFalse(config.readBool(10, "is_live")); - bool_array = config.readBoolArray(10, "bool_array"); + assertFalse(config.getBool(10, "is_live")); + bool_array = config.getBoolArray(10, "bool_array"); assertFalse(bool_array[0]); assertTrue(bool_array[1]); // Read and assert address values - assertEq(config.readAddress(10, "weth"), 0x4200000000000000000000000000000000000006); - address_array = config.readAddressArray(10, "deps"); + assertEq(config.getAddress(10, "weth"), 0x4200000000000000000000000000000000000006); + address_array = config.getAddressArray(10, "deps"); assertEq(address_array[0], 0x2222222222222222222222222222222222222222); assertEq(address_array[1], 0x3333333333333333333333333333333333333333); // Read and assert uint values - assertEq(config.readUint(10, "number"), 9999); - uint_array = config.readUintArray(10, "number_array"); + assertEq(config.getUint(10, "number"), 9999); + uint_array = config.getUintArray(10, "number_array"); assertEq(uint_array[0], 1234); assertEq(uint_array[1], 5678); // Read and assert bytes32 values - assertEq(config.readBytes32(10, "word"), bytes32(uint256(9999))); - bytes32_array = config.readBytes32Array(10, "word_array"); + assertEq(config.getBytes32(10, "word"), bytes32(uint256(9999))); + bytes32_array = config.getBytes32Array(10, "word_array"); assertEq(bytes32_array[0], bytes32(uint256(1234))); assertEq(bytes32_array[1], bytes32(uint256(5678))); // Read and assert bytes values - assertEq(config.readBytes(10, "b"), hex"dcba"); - bytes_array = config.readBytesArray(10, "b_array"); + assertEq(config.getBytes(10, "b"), hex"dcba"); + bytes_array = config.getBytesArray(10, "b_array"); assertEq(bytes_array[0], hex"c0ffee"); assertEq(bytes_array[1], hex"babe"); // Read and assert string values - assertEq(config.readString(10, "str"), "alice"); - string_array = config.readStringArray(10, "str_array"); + assertEq(config.getString(10, "str"), "alice"); + string_array = config.getStringArray(10, "str_array"); assertEq(string_array[0], "bob"); assertEq(string_array[1], "charlie"); } @@ -114,18 +114,18 @@ contract CommonConfigTest is Test { _loadConfig(testConfig); // Update a single boolean value and verify the change. - config.update(1, "is_live", false, true); + config.set(1, "is_live", false, true); - assertFalse(config.readBool(1, "is_live")); + assertFalse(config.getBool(1, "is_live")); string memory content = vm.readFile(testConfig); assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live")); // Update a single address value and verify the change. address new_addr = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; - config.update(1, "weth", new_addr, true); + config.set(1, "weth", new_addr, true); - assertEq(config.readAddress(1, "weth"), new_addr); + assertEq(config.getAddress(1, "weth"), new_addr); content = vm.readFile(testConfig); assertEq(vm.parseTomlAddress(content, "$.mainnet.address.weth"), new_addr); @@ -135,9 +135,9 @@ contract CommonConfigTest is Test { new_numbers[0] = 1; new_numbers[1] = 2; new_numbers[2] = 3; - config.update(10, "number_array", new_numbers, true); + config.set(10, "number_array", new_numbers, true); - uint256[] memory updated_numbers_mem = config.readUintArray(10, "number_array"); + uint256[] memory updated_numbers_mem = config.getUintArray(10, "number_array"); assertEq(updated_numbers_mem.length, 3); assertEq(updated_numbers_mem[0], 1); assertEq(updated_numbers_mem[1], 2); @@ -154,9 +154,9 @@ contract CommonConfigTest is Test { string[] memory new_strings = new string[](2); new_strings[0] = "hello"; new_strings[1] = "world"; - config.update(1, "str_array", new_strings, true); + config.set(1, "str_array", new_strings, true); - string[] memory updated_strings_mem = config.readStringArray(1, "str_array"); + string[] memory updated_strings_mem = config.getStringArray(1, "str_array"); assertEq(updated_strings_mem.length, 2); assertEq(updated_strings_mem[0], "hello"); assertEq(updated_strings_mem[1], "world"); @@ -168,9 +168,9 @@ contract CommonConfigTest is Test { assertEq(updated_strings_disk[1], "world"); // Create a new uint variable and verify the change. - config.update(1, "new_uint", 42, true); + config.set(1, "new_uint", 42, true); - assertEq(config.readUint(1, "new_uint"), 42); + assertEq(config.getUint(1, "new_uint"), 42); content = vm.readFile(testConfig); assertEq(vm.parseTomlUint(content, "$.mainnet.uint.new_uint"), 42); @@ -179,9 +179,9 @@ contract CommonConfigTest is Test { bytes32[] memory new_words = new bytes32[](2); new_words[0] = bytes32(uint256(0xDEAD)); new_words[1] = bytes32(uint256(0xBEEF)); - config.update(10, "new_words", new_words, true); + config.set(10, "new_words", new_words, true); - bytes32[] memory updated_words_mem = config.readBytes32Array(10, "new_words"); + bytes32[] memory updated_words_mem = config.getBytes32Array(10, "new_words"); assertEq(updated_words_mem.length, 2); assertEq(updated_words_mem[0], new_words[0]); assertEq(updated_words_mem[1], new_words[1]); From a3c4e60380354473501093943db567fb78d82bbd Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 15:36:30 +0200 Subject: [PATCH 12/54] use backwards compatible solidity --- src/StdConfig.sol | 36 ++++++++++++++++++------------------ test/CommonConfig.t.sol | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 4ea1d845..9b53cd47 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -45,24 +45,24 @@ contract StdConfig { // List of top-level keys found in the TOML file, assumed to be chain names/aliases. string[] private _chainKeys; - // Storage for the configured RPC URL for each chain. - mapping(uint256 chainId => string url) private _rpcOf; - - // Storage for single value types organized by chain ID and variable key. - mapping(uint256 chainId => mapping(string key => bool value)) private _boolsOf; - mapping(uint256 chainId => mapping(string key => uint256 value)) private _uintsOf; - mapping(uint256 chainId => mapping(string key => address value)) private _addressesOf; - mapping(uint256 chainId => mapping(string key => bytes32 value)) private _bytes32sOf; - mapping(uint256 chainId => mapping(string key => string value)) private _stringsOf; - mapping(uint256 chainId => mapping(string key => bytes value)) private _bytesOf; - - // Storage for array value types organized by chain ID and variable key. - mapping(uint256 chainId => mapping(string key => bool[] value)) private _boolArraysOf; - mapping(uint256 chainId => mapping(string key => uint256[] value)) private _uintArraysOf; - mapping(uint256 chainId => mapping(string key => address[] value)) private _addressArraysOf; - mapping(uint256 chainId => mapping(string key => bytes32[] value)) private _bytes32ArraysOf; - mapping(uint256 chainId => mapping(string key => string[] value)) private _stringArraysOf; - mapping(uint256 chainId => mapping(string key => bytes[] value)) private _bytesArraysOf; + // Storage for the configured RPC URL for each chain [`chainId` -> `url`]. + mapping(uint256 => string) private _rpcOf; + + // Storage for single value types organized by chain ID and variable key [`chainId` -> `key` -> `value`]. + mapping(uint256 => mapping(string => bool)) private _boolsOf; + mapping(uint256 => mapping(string => uint256)) private _uintsOf; + mapping(uint256 => mapping(string => address)) private _addressesOf; + mapping(uint256 => mapping(string => bytes32)) private _bytes32sOf; + mapping(uint256 => mapping(string => string)) private _stringsOf; + mapping(uint256 => mapping(string => bytes)) private _bytesOf; + + // Storage for array value types organized by chain ID and variable key [`chainId` -> `key` -> `value`]. + mapping(uint256 => mapping(string => bool[])) private _boolArraysOf; + mapping(uint256 => mapping(string => uint256[])) private _uintArraysOf; + mapping(uint256 => mapping(string => address[])) private _addressArraysOf; + mapping(uint256 => mapping(string => bytes32[])) private _bytes32ArraysOf; + mapping(uint256 => mapping(string => string[])) private _stringArraysOf; + mapping(uint256 => mapping(string => bytes[])) private _bytesArraysOf; // -- CONSTRUCTOR ---------------------------------------------------------- diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol index bf4bcee8..268230d4 100644 --- a/test/CommonConfig.t.sol +++ b/test/CommonConfig.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; +pragma solidity >=0.6.2 <0.9.0; import {Test} from "../src/Test.sol"; From 2be7d97cbed02586acca5a578526d0129424b137 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 15:37:43 +0200 Subject: [PATCH 13/54] fix: pragma --- src/StdConfig.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 9b53cd47..329283b4 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity >=0.6.2 <0.9.0; import {VmSafe} from "./Vm.sol"; From 3dbe7d3adfbf04935c08038e71ec614b784468ad Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 15:41:41 +0200 Subject: [PATCH 14/54] fix: use backwards compatible solidity --- src/Base.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Base.sol b/src/Base.sol index 5b65710d..3998545b 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -51,7 +51,7 @@ abstract contract CommonConfig is CommonBase { StdConfig internal config; uint256[] internal chainIds; - mapping(uint256 chainId => uint256 forkId) internal forkOf; + mapping(uint256 => uint256) internal forkOf; // [chainId -> forkId] // -- HELPER FUNCTIONS ----------------------------------------------------- From f30626e5cfd43f1a3efa7afff4e5c05fa0c1b355 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 16:13:13 +0200 Subject: [PATCH 15/54] don't use `string.concat` --- src/Base.sol | 2 +- src/StdConfig.sol | 71 ++++++++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/Base.sol b/src/Base.sol index 3998545b..1439730a 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -62,7 +62,7 @@ abstract contract CommonConfig is CommonBase { /// @param filePath: the path to the TOML configuration file. function _loadConfig(string memory filePath) internal { console.log("----------"); - console.log(string.concat("Loading config from '", filePath, "'")); + console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); config = new StdConfig(filePath); vm.makePersistent(address(config)); console.log("Config successfully loaded"); diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 329283b4..a9102d5c 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -93,7 +93,7 @@ contract StdConfig { for (uint i = 0; i < chain_keys.length; i++) { string memory chain_key = chain_keys[i]; // Top-level keys that are not tables should be ignored (e.g. `profile = "default"`). - if (vm.parseTomlKeys(content, string.concat("$.", chain_key)).length == 0) { + if (vm.parseTomlKeys(content, _concat("$.", chain_key)).length == 0) { continue; } uint256 chain_id = resolveChainId(chain_key); @@ -101,7 +101,7 @@ contract StdConfig { // Cache the configure rpc endpoint for that chain. // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured. - try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { + try vm.parseTomlString(content, _concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { _rpcOf[chain_id] = vm.resolveEnv(url); } catch { _rpcOf[chain_id] = vm.resolveEnv(vm.rpcUrl(chain_key)); @@ -109,12 +109,12 @@ contract StdConfig { for (uint t = 0; t < types.length; t++) { string memory var_type = types[t]; - string memory path_to_type = string.concat("$.", chain_key, ".", var_type); + string memory path_to_type = _concat("$.", chain_key, ".", var_type); try vm.parseTomlKeys(content, path_to_type) returns (string[] memory var_keys) { for (uint j = 0; j < var_keys.length; j++) { string memory var_key = var_keys[j]; - string memory path_to_var = string.concat(path_to_type, ".", var_key); + string memory path_to_var = _concat(path_to_type, ".", var_key); bool success = false; if (keccak256(bytes(var_type)) == keccak256(bytes("bool"))) { @@ -181,9 +181,7 @@ contract StdConfig { if (!success) { revert( - string.concat( - "Unable to parse variable '", var_key, "' from '[", chain_key, ".", var_type, "]'" - ) + _concat("Unable to parse variable '", var_key, "' from '[", _concat(chain_key, ".", var_type, "]'")) ); } } @@ -206,7 +204,7 @@ contract StdConfig { try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) { return chainInfo.chainId; } catch { - revert(string.concat("chain key: '", aliasOrId, "' is not a valid alias nor a number.")); + revert(_concat("chain key: '", aliasOrId, "' is not a valid alias nor a number.")); } } } @@ -218,12 +216,27 @@ contract StdConfig { return _chainKeys[i]; } } - revert(string.concat("chain id: '", vm.toString(chainId), "' not found in configuration")); + revert(_concat("chain id: '", vm.toString(chainId), "' not found in configuration")); + } + + /// @dev concatenates two strings + function _concat(string memory s1, string memory s2) private pure returns (string memory) { + return string(abi.encodePacked(s1, s2)); + } + + /// @dev concatenates three strings + function _concat(string memory s1, string memory s2, string memory s3) private pure returns (string memory) { + return string(abi.encodePacked(s1, s2, s3)); + } + + /// @dev concatenates four strings + function _concat(string memory s1, string memory s2, string memory s3, string memory s4) private pure returns (string memory) { + return string(abi.encodePacked(s1, s2, s3, s4)); } /// @dev Wraps a string in double quotes for JSON compatibility. function _quote(string memory s) private pure returns (string memory) { - return string.concat('"', s, '"'); + return _concat('"', s, '"'); } /// @dev Writes a JSON-formatted value to a specific key in the TOML file. @@ -233,7 +246,7 @@ contract StdConfig { /// @param jsonValue The JSON-formatted value to write. function _writeToToml(uint256 chainId, string memory ty, string memory key, string memory jsonValue) private { string memory chainKey = _getChainKeyFromId(chainId); - string memory valueKey = string.concat("$.", chainKey, ".", ty, ".", key); + string memory valueKey = _concat("$.", chainKey, ".", _concat(ty, ".", key)); vm.writeTomlUpsert(jsonValue, _filePath, valueKey); } @@ -480,10 +493,10 @@ contract StdConfig { if (write) { string memory json = "["; for (uint i = 0; i < value.length; i++) { - json = string.concat(json, vm.toString(value[i])); - if (i < value.length - 1) json = string.concat(json, ","); + json = _concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = _concat(json, ","); } - json = string.concat(json, "]"); + json = _concat(json, "]"); _writeToToml(chainId, "bool", key, json); } } @@ -502,10 +515,10 @@ contract StdConfig { if (write) { string memory json = "["; for (uint i = 0; i < value.length; i++) { - json = string.concat(json, vm.toString(value[i])); - if (i < value.length - 1) json = string.concat(json, ","); + json = _concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = _concat(json, ","); } - json = string.concat(json, "]"); + json = _concat(json, "]"); _writeToToml(chainId, "uint", key, json); } } @@ -524,10 +537,10 @@ contract StdConfig { if (write) { string memory json = "["; for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = string.concat(json, ","); + json = _concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = _concat(json, ","); } - json = string.concat(json, "]"); + json = _concat(json, "]"); _writeToToml(chainId, "address", key, json); } } @@ -546,10 +559,10 @@ contract StdConfig { if (write) { string memory json = "["; for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = string.concat(json, ","); + json = _concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = _concat(json, ","); } - json = string.concat(json, "]"); + json = _concat(json, "]"); _writeToToml(chainId, "bytes32", key, json); } } @@ -568,10 +581,10 @@ contract StdConfig { if (write) { string memory json = "["; for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(value[i])); - if (i < value.length - 1) json = string.concat(json, ","); + json = _concat(json, _quote(value[i])); + if (i < value.length - 1) json = _concat(json, ","); } - json = string.concat(json, "]"); + json = _concat(json, "]"); _writeToToml(chainId, "string", key, json); } } @@ -590,10 +603,10 @@ contract StdConfig { if (write) { string memory json = "["; for (uint i = 0; i < value.length; i++) { - json = string.concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = string.concat(json, ","); + json = _concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = _concat(json, ","); } - json = string.concat(json, "]"); + json = _concat(json, "]"); _writeToToml(chainId, "bytes", key, json); } } From 53a4cc53f71f1965257b9f624868da864cb146b0 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 16:52:31 +0200 Subject: [PATCH 16/54] add abidecoder v2 --- src/Base.sol | 1 + src/StdChains.sol | 1 + src/StdConfig.sol | 2 +- test/CommonConfig.t.sol | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Base.sol b/src/Base.sol index 1439730a..dd53c3b5 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +pragma abicoder v2; import {console} from "./console.sol"; import {StdStorage} from "./StdStorage.sol"; diff --git a/src/StdChains.sol b/src/StdChains.sol index 0a872e7b..3b6d0185 100644 --- a/src/StdChains.sol +++ b/src/StdChains.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +pragma abicoder v2; import {VmSafe} from "./Vm.sol"; diff --git a/src/StdConfig.sol b/src/StdConfig.sol index a9102d5c..b68dc693 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -1,6 +1,6 @@ - // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +pragma abicoder v2; import {VmSafe} from "./Vm.sol"; diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol index 268230d4..62d9febe 100644 --- a/test/CommonConfig.t.sol +++ b/test/CommonConfig.t.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +pragma abicoder v2; import {Test} from "../src/Test.sol"; From 279f9e79527797b60de69ee08567964e3d0b0209 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 16:54:55 +0200 Subject: [PATCH 17/54] use pragma experimental ABIEncoderV2 --- src/Base.sol | 2 +- src/StdChains.sol | 2 +- src/StdConfig.sol | 2 +- test/CommonConfig.t.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Base.sol b/src/Base.sol index dd53c3b5..af1a9e5b 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -pragma abicoder v2; +pragma experimental ABIEncoderV2; import {console} from "./console.sol"; import {StdStorage} from "./StdStorage.sol"; diff --git a/src/StdChains.sol b/src/StdChains.sol index 3b6d0185..3bc5f43a 100644 --- a/src/StdChains.sol +++ b/src/StdChains.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -pragma abicoder v2; +pragma experimental ABIEncoderV2; import {VmSafe} from "./Vm.sol"; diff --git a/src/StdConfig.sol b/src/StdConfig.sol index b68dc693..a9d24ad4 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -pragma abicoder v2; +pragma experimental ABIEncoderV2; import {VmSafe} from "./Vm.sol"; diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol index 62d9febe..08e8cbef 100644 --- a/test/CommonConfig.t.sol +++ b/test/CommonConfig.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -pragma abicoder v2; +pragma experimental ABIEncoderV2; import {Test} from "../src/Test.sol"; From fae314f0bb69d5294008565addfeb4c82e870325 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 17:14:42 +0200 Subject: [PATCH 18/54] fix: constructor visibility --- src/StdConfig.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index a9d24ad4..5d439e9f 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -76,7 +76,7 @@ contract StdConfig { /// parsed as either, the constructor will revert with an error. /// /// @param configFilePath: The local path to the TOML configuration file. - constructor(string memory configFilePath) { + constructor(string memory configFilePath) public { _filePath = configFilePath; string memory content = vm.resolveEnv(vm.readFile(configFilePath)); string[] memory chain_keys = vm.parseTomlKeys(content, "$"); From b98e7a4791a4ceef1b4a425910284c8dc7045407 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 17:23:37 +0200 Subject: [PATCH 19/54] use initializer rather than constructor --- src/Base.sol | 3 ++- src/StdConfig.sol | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Base.sol b/src/Base.sol index af1a9e5b..e3f68075 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -64,7 +64,8 @@ abstract contract CommonConfig is CommonBase { function _loadConfig(string memory filePath) internal { console.log("----------"); console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); - config = new StdConfig(filePath); + config = new StdConfig(); + config.initialize(filePath); vm.makePersistent(address(config)); console.log("Config successfully loaded"); console.log("----------"); diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 5d439e9f..14cc8ff7 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -64,7 +64,7 @@ contract StdConfig { mapping(uint256 => mapping(string => string[])) private _stringArraysOf; mapping(uint256 => mapping(string => bytes[])) private _bytesArraysOf; - // -- CONSTRUCTOR ---------------------------------------------------------- + // -- INITIALIZER ---------------------------------------------------------- /// @notice Reads the TOML file and iterates through each top-level key, which is /// assumed to be a chain name or ID. For each chain, it caches its RPC @@ -76,7 +76,7 @@ contract StdConfig { /// parsed as either, the constructor will revert with an error. /// /// @param configFilePath: The local path to the TOML configuration file. - constructor(string memory configFilePath) public { + function initialize(string memory configFilePath) public { _filePath = configFilePath; string memory content = vm.resolveEnv(vm.readFile(configFilePath)); string[] memory chain_keys = vm.parseTomlKeys(content, "$"); From 4ad83e207a9c822ed5afe893d223c6e3dd5dbc41 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 17:26:44 +0200 Subject: [PATCH 20/54] style: fmt --- src/Base.sol | 6 ++---- src/StdConfig.sol | 35 ++++++++++++++++++++++------------- src/Vm.sol | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Base.sol b/src/Base.sol index e3f68075..deca0029 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -44,15 +44,13 @@ abstract contract CommonBase { StdStorage internal stdstore; } - /// @notice Boilerplate to streamline the setup of multi-chain testing environments. abstract contract CommonConfig is CommonBase { - // -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------ StdConfig internal config; uint256[] internal chainIds; - mapping(uint256 => uint256) internal forkOf; // [chainId -> forkId] + mapping(uint256 => uint256) internal forkOf; // [chainId -> forkId] // -- HELPER FUNCTIONS ----------------------------------------------------- @@ -83,7 +81,7 @@ abstract contract CommonConfig is CommonBase { console.log("Setting up forks for the configured chains..."); uint256[] memory chains = config.getChainIds(); - for (uint i = 0; i < chains.length; i++) { + for (uint256 i = 0; i < chains.length; i++) { uint256 chainId = chains[i]; uint256 forkId = vm.createFork(config.getRpcUrl(chainId)); forkOf[chainId] = forkId; diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 14cc8ff7..561d43fd 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -90,7 +90,7 @@ contract StdConfig { types[5] = "bytes"; // Cache the entire configuration to storage - for (uint i = 0; i < chain_keys.length; i++) { + for (uint256 i = 0; i < chain_keys.length; i++) { string memory chain_key = chain_keys[i]; // Top-level keys that are not tables should be ignored (e.g. `profile = "default"`). if (vm.parseTomlKeys(content, _concat("$.", chain_key)).length == 0) { @@ -107,12 +107,12 @@ contract StdConfig { _rpcOf[chain_id] = vm.resolveEnv(vm.rpcUrl(chain_key)); } - for (uint t = 0; t < types.length; t++) { + for (uint256 t = 0; t < types.length; t++) { string memory var_type = types[t]; string memory path_to_type = _concat("$.", chain_key, ".", var_type); try vm.parseTomlKeys(content, path_to_type) returns (string[] memory var_keys) { - for (uint j = 0; j < var_keys.length; j++) { + for (uint256 j = 0; j < var_keys.length; j++) { string memory var_key = var_keys[j]; string memory path_to_var = _concat(path_to_type, ".", var_key); bool success = false; @@ -181,7 +181,12 @@ contract StdConfig { if (!success) { revert( - _concat("Unable to parse variable '", var_key, "' from '[", _concat(chain_key, ".", var_type, "]'")) + _concat( + "Unable to parse variable '", + var_key, + "' from '[", + _concat(chain_key, ".", var_type, "]'") + ) ); } } @@ -211,7 +216,7 @@ contract StdConfig { /// @dev Retrieves the chain key/alias from the configuration based on the chain ID. function _getChainKeyFromId(uint256 chainId) private view returns (string memory) { - for (uint i = 0; i < _chainKeys.length; i++) { + for (uint256 i = 0; i < _chainKeys.length; i++) { if (resolveChainId(_chainKeys[i]) == chainId) { return _chainKeys[i]; } @@ -230,7 +235,11 @@ contract StdConfig { } /// @dev concatenates four strings - function _concat(string memory s1, string memory s2, string memory s3, string memory s4) private pure returns (string memory) { + function _concat(string memory s1, string memory s2, string memory s3, string memory s4) + private + pure + returns (string memory) + { return string(abi.encodePacked(s1, s2, s3, s4)); } @@ -257,7 +266,7 @@ contract StdConfig { string[] memory keys = _chainKeys; uint256[] memory ids = new uint256[](keys.length); - for (uint i = 0; i < keys.length; i++) { + for (uint256 i = 0; i < keys.length; i++) { ids[i] = resolveChainId(keys[i]); } @@ -492,7 +501,7 @@ contract StdConfig { _boolArraysOf[chainId][key] = value; if (write) { string memory json = "["; - for (uint i = 0; i < value.length; i++) { + for (uint256 i = 0; i < value.length; i++) { json = _concat(json, vm.toString(value[i])); if (i < value.length - 1) json = _concat(json, ","); } @@ -514,7 +523,7 @@ contract StdConfig { _uintArraysOf[chainId][key] = value; if (write) { string memory json = "["; - for (uint i = 0; i < value.length; i++) { + for (uint256 i = 0; i < value.length; i++) { json = _concat(json, vm.toString(value[i])); if (i < value.length - 1) json = _concat(json, ","); } @@ -536,7 +545,7 @@ contract StdConfig { _addressArraysOf[chainId][key] = value; if (write) { string memory json = "["; - for (uint i = 0; i < value.length; i++) { + for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); if (i < value.length - 1) json = _concat(json, ","); } @@ -558,7 +567,7 @@ contract StdConfig { _bytes32ArraysOf[chainId][key] = value; if (write) { string memory json = "["; - for (uint i = 0; i < value.length; i++) { + for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); if (i < value.length - 1) json = _concat(json, ","); } @@ -580,7 +589,7 @@ contract StdConfig { _stringArraysOf[chainId][key] = value; if (write) { string memory json = "["; - for (uint i = 0; i < value.length; i++) { + for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(value[i])); if (i < value.length - 1) json = _concat(json, ","); } @@ -602,7 +611,7 @@ contract StdConfig { _bytesArraysOf[chainId][key] = value; if (write) { string memory json = "["; - for (uint i = 0; i < value.length; i++) { + for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); if (i < value.length - 1) json = _concat(json, ","); } diff --git a/src/Vm.sol b/src/Vm.sol index f8b6aa31..14030179 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -614,7 +614,7 @@ interface VmSafe { function activeFork() external view returns (uint256 forkId); /// Returns the chain id of the currently active fork. Reverts if no fork is currently active. - function activeChain() external view returns(uint256 chainId); + function activeChain() external view returns (uint256 chainId); /// 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); From 251258e85b08f03c3b7b834f40226e8b4680ea2e Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 17:46:28 +0200 Subject: [PATCH 21/54] smarter storage layout --- src/StdConfig.sol | 310 +++++++++++++++++++++++----------------------- 1 file changed, 154 insertions(+), 156 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 561d43fd..91051abb 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -48,21 +48,9 @@ contract StdConfig { // Storage for the configured RPC URL for each chain [`chainId` -> `url`]. mapping(uint256 => string) private _rpcOf; - // Storage for single value types organized by chain ID and variable key [`chainId` -> `key` -> `value`]. - mapping(uint256 => mapping(string => bool)) private _boolsOf; - mapping(uint256 => mapping(string => uint256)) private _uintsOf; - mapping(uint256 => mapping(string => address)) private _addressesOf; - mapping(uint256 => mapping(string => bytes32)) private _bytes32sOf; - mapping(uint256 => mapping(string => string)) private _stringsOf; - mapping(uint256 => mapping(string => bytes)) private _bytesOf; - - // Storage for array value types organized by chain ID and variable key [`chainId` -> `key` -> `value`]. - mapping(uint256 => mapping(string => bool[])) private _boolArraysOf; - mapping(uint256 => mapping(string => uint256[])) private _uintArraysOf; - mapping(uint256 => mapping(string => address[])) private _addressArraysOf; - mapping(uint256 => mapping(string => bytes32[])) private _bytes32ArraysOf; - mapping(uint256 => mapping(string => string[])) private _stringArraysOf; - mapping(uint256 => mapping(string => bytes[])) private _bytesArraysOf; + // Storage for values and arrays, organized by chain ID and variable key [`chainId` -> `key` -> `value`]. + mapping(uint256 => mapping(string => bytes)) private _valuesOf; + mapping(uint256 => mapping(string => bytes)) private _arraysOf; // -- INITIALIZER ---------------------------------------------------------- @@ -92,7 +80,7 @@ contract StdConfig { // Cache the entire configuration to storage for (uint256 i = 0; i < chain_keys.length; i++) { string memory chain_key = chain_keys[i]; - // Top-level keys that are not tables should be ignored (e.g. `profile = "default"`). + // Ignore top-level keys that are not tables if (vm.parseTomlKeys(content, _concat("$.", chain_key)).length == 0) { continue; } @@ -115,86 +103,90 @@ contract StdConfig { for (uint256 j = 0; j < var_keys.length; j++) { string memory var_key = var_keys[j]; string memory path_to_var = _concat(path_to_type, ".", var_key); - bool success = false; - - if (keccak256(bytes(var_type)) == keccak256(bytes("bool"))) { - try vm.parseTomlBool(content, path_to_var) returns (bool val) { - _boolsOf[chain_id][var_key] = val; - success = true; - } catch { - try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { - _boolArraysOf[chain_id][var_key] = val; - success = true; - } catch {} - } - } else if (keccak256(bytes(var_type)) == keccak256(bytes("address"))) { - try vm.parseTomlAddress(content, path_to_var) returns (address val) { - _addressesOf[chain_id][var_key] = val; - success = true; - } catch { - try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { - _addressArraysOf[chain_id][var_key] = val; - success = true; - } catch {} - } - } else if (keccak256(bytes(var_type)) == keccak256(bytes("uint"))) { - try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { - _uintsOf[chain_id][var_key] = val; - success = true; - } catch { - try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { - _uintArraysOf[chain_id][var_key] = val; - success = true; - } catch {} - } - } else if (keccak256(bytes(var_type)) == keccak256(bytes("bytes32"))) { - try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { - _bytes32sOf[chain_id][var_key] = val; - success = true; - } catch { - try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { - _bytes32ArraysOf[chain_id][var_key] = val; - success = true; - } catch {} - } - } else if (keccak256(bytes(var_type)) == keccak256(bytes("bytes"))) { - try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { - _bytesOf[chain_id][var_key] = val; - success = true; - } catch { - try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { - _bytesArraysOf[chain_id][var_key] = val; - success = true; - } catch {} - } - } else if (keccak256(bytes(var_type)) == keccak256(bytes("string"))) { - try vm.parseTomlString(content, path_to_var) returns (string memory val) { - _stringsOf[chain_id][var_key] = val; - success = true; - } catch { - try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { - _stringArraysOf[chain_id][var_key] = val; - success = true; - } catch {} - } - } - - if (!success) { - revert( - _concat( - "Unable to parse variable '", - var_key, - "' from '[", - _concat(chain_key, ".", var_type, "]'") - ) - ); - } + _loadAndCacheValue(content, path_to_var, chain_id, var_key, var_type); } } catch {} // Section does not exist, ignore. } } } + function _loadAndCacheValue( + string memory content, + string memory path_to_var, + uint256 chain_id, + string memory var_key, + string memory var_type + ) private { + bytes32 typeHash = keccak256(bytes(var_type)); + bool success = false; + + if (typeHash == keccak256(bytes("bool"))) { + try vm.parseTomlBool(content, path_to_var) returns (bool val) { + _valuesOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch { + try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { + _arraysOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch {} + } + } else if (typeHash == keccak256(bytes("address"))) { + try vm.parseTomlAddress(content, path_to_var) returns (address val) { + _valuesOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch { + try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { + _arraysOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch {} + } + } else if (typeHash == keccak256(bytes("uint"))) { + try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { + _valuesOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch { + try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { + _arraysOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch {} + } + } else if (typeHash == keccak256(bytes("bytes32"))) { + try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { + _valuesOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch { + try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { + _arraysOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch {} + } + } else if (typeHash == keccak256(bytes("bytes"))) { + try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { + _valuesOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch { + try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { + _arraysOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch {} + } + } else if (typeHash == keccak256(bytes("string"))) { + try vm.parseTomlString(content, path_to_var) returns (string memory val) { + _valuesOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch { + try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { + _arraysOf[chain_id][var_key] = abi.encode(val); + success = true; + } catch {} + } + } + + if (!success) { + revert(string.concat("Unable to parse variable '", var_key, "'")); + } + } + // -- HELPER FUNCTIONS -----------------------------------------------------\n /// @notice Resolves a chain alias or a chain id string to its numerical chain id. @@ -285,122 +277,146 @@ contract StdConfig { /// @notice Reads a boolean value for a given key and chain ID. function getBool(uint256 chain_id, string memory key) public view returns (bool) { - return _boolsOf[chain_id][key]; + bytes memory val = _valuesOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (bool)); } /// @notice Reads a boolean value for a given key on the current chain. function getBool(string memory key) public view returns (bool) { - return _boolsOf[vm.getChainId()][key]; + return getBool(vm.getChainId(), key); } /// @notice Reads a uint256 value for a given key and chain ID. function getUint(uint256 chain_id, string memory key) public view returns (uint256) { - return _uintsOf[chain_id][key]; + bytes memory val = _valuesOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (uint256)); } /// @notice Reads a uint256 value for a given key on the current chain. function getUint(string memory key) public view returns (uint256) { - return _uintsOf[vm.getChainId()][key]; + return getUint(vm.getChainId(), key); } /// @notice Reads an address value for a given key and chain ID. function getAddress(uint256 chain_id, string memory key) public view returns (address) { - return _addressesOf[chain_id][key]; + bytes memory val = _valuesOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (address)); } /// @notice Reads an address value for a given key on the current chain. function getAddress(string memory key) public view returns (address) { - return _addressesOf[vm.getChainId()][key]; + return getAddress(vm.getChainId(), key); } /// @notice Reads a bytes32 value for a given key and chain ID. function getBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { - return _bytes32sOf[chain_id][key]; + bytes memory val = _valuesOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (bytes32)); } /// @notice Reads a bytes32 value for a given key on the current chain. function getBytes32(string memory key) public view returns (bytes32) { - return _bytes32sOf[vm.getChainId()][key]; + return getBytes32(vm.getChainId(), key); } /// @notice Reads a string value for a given key and chain ID. function getString(uint256 chain_id, string memory key) public view returns (string memory) { - return _stringsOf[chain_id][key]; + bytes memory val = _valuesOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (string)); } /// @notice Reads a string value for a given key on the current chain. function getString(string memory key) public view returns (string memory) { - return _stringsOf[vm.getChainId()][key]; + return getString(vm.getChainId(), key); } /// @notice Reads a bytes value for a given key and chain ID. function getBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { - return _bytesOf[chain_id][key]; + bytes memory val = _valuesOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (bytes)); } /// @notice Reads a bytes value for a given key on the current chain. function getBytes(string memory key) public view returns (bytes memory) { - return _bytesOf[vm.getChainId()][key]; + return getBytes(vm.getChainId(), key); } /// @notice Reads a boolean array for a given key and chain ID. function getBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { - return _boolArraysOf[chain_id][key]; + bytes memory val = _arraysOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (bool[])); } /// @notice Reads a boolean array for a given key on the current chain. function getBoolArray(string memory key) public view returns (bool[] memory) { - return _boolArraysOf[vm.getChainId()][key]; + return getBoolArray(vm.getChainId(), key); } /// @notice Reads a uint256 array for a given key and chain ID. function getUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { - return _uintArraysOf[chain_id][key]; + bytes memory val = _arraysOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (uint256[])); } /// @notice Reads a uint256 array for a given key on the current chain. function getUintArray(string memory key) public view returns (uint256[] memory) { - return _uintArraysOf[vm.getChainId()][key]; + return getUintArray(vm.getChainId(), key); } /// @notice Reads an address array for a given key and chain ID. function getAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { - return _addressArraysOf[chain_id][key]; + bytes memory val = _arraysOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (address[])); } /// @notice Reads an address array for a given key on the current chain. function getAddressArray(string memory key) public view returns (address[] memory) { - return _addressArraysOf[vm.getChainId()][key]; + return getAddressArray(vm.getChainId(), key); } /// @notice Reads a bytes32 array for a given key and chain ID. function getBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { - return _bytes32ArraysOf[chain_id][key]; + bytes memory val = _arraysOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (bytes32[])); } /// @notice Reads a bytes32 array for a given key on the current chain. function getBytes32Array(string memory key) public view returns (bytes32[] memory) { - return _bytes32ArraysOf[vm.getChainId()][key]; + return getBytes32Array(vm.getChainId(), key); } /// @notice Reads a string array for a given key and chain ID. function getStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { - return _stringArraysOf[chain_id][key]; + bytes memory val = _arraysOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (string[])); } /// @notice Reads a string array for a given key on the current chain. function getStringArray(string memory key) public view returns (string[] memory) { - return _stringArraysOf[vm.getChainId()][key]; + return getStringArray(vm.getChainId(), key); } /// @notice Reads a bytes array for a given key and chain ID. function getBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { - return _bytesArraysOf[chain_id][key]; + bytes memory val = _arraysOf[chain_id][key]; + require(val.length > 0, "Value not found"); + return abi.decode(val, (bytes[])); } /// @notice Reads a bytes array for a given key on the current chain. function getBytesArray(string memory key) public view returns (bytes[] memory) { - return _bytesArraysOf[vm.getChainId()][key]; + return getBytesArray(vm.getChainId(), key); } // -- SETTER FUNCTIONS ----------------------------------------------------- @@ -408,97 +424,85 @@ contract StdConfig { /// @notice Sets a boolean value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bool value, bool write) public { - _boolsOf[chainId][key] = value; + _valuesOf[chainId][key] = abi.encode(value); if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); } /// @notice Sets a boolean value for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, bool value, bool write) public { - uint256 chainId = vm.getChainId(); - _boolsOf[chainId][key] = value; - if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); + set(vm.getChainId(), key, value, write); } /// @notice Sets a uint256 value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, uint256 value, bool write) public { - _uintsOf[chainId][key] = value; + _valuesOf[chainId][key] = abi.encode(value); if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); } /// @notice Sets a uint256 value for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, uint256 value, bool write) public { - uint256 chainId = vm.getChainId(); - _uintsOf[chainId][key] = value; - if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); + set(vm.getChainId(), key, value, write); } /// @notice Sets an address value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, address value, bool write) public { - _addressesOf[chainId][key] = value; + _valuesOf[chainId][key] = abi.encode(value); if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); } /// @notice Sets an address value for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, address value, bool write) public { - uint256 chainId = vm.getChainId(); - _addressesOf[chainId][key] = value; - if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); + set(vm.getChainId(), key, value, write); } /// @notice Sets a bytes32 value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes32 value, bool write) public { - _bytes32sOf[chainId][key] = value; + _valuesOf[chainId][key] = abi.encode(value); if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); } /// @notice Sets a bytes32 value for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, bytes32 value, bool write) public { - uint256 chainId = vm.getChainId(); - _bytes32sOf[chainId][key] = value; - if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); + set(vm.getChainId(), key, value, write); } /// @notice Sets a string value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, string memory value, bool write) public { - _stringsOf[chainId][key] = value; + _valuesOf[chainId][key] = abi.encode(value); if (write) _writeToToml(chainId, "string", key, _quote(value)); } /// @notice Sets a string value for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, string memory value, bool write) public { - uint256 chainId = vm.getChainId(); - _stringsOf[chainId][key] = value; - if (write) _writeToToml(chainId, "string", key, _quote(value)); + set(vm.getChainId(), key, value, write); } /// @notice Sets a bytes value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes memory value, bool write) public { - _bytesOf[chainId][key] = value; + _valuesOf[chainId][key] = abi.encode(value); if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); } /// @notice Sets a bytes value for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, bytes memory value, bool write) public { - uint256 chainId = vm.getChainId(); - _bytesOf[chainId][key] = value; - if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); + set(vm.getChainId(), key, value, write); } /// @notice Sets a boolean array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bool[] memory value, bool write) public { - _boolArraysOf[chainId][key] = value; + _arraysOf[chainId][key] = abi.encode(value); if (write) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { @@ -513,14 +517,13 @@ contract StdConfig { /// @notice Sets a boolean array for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, bool[] memory value, bool write) public { - uint256 chainId = vm.getChainId(); - set(chainId, key, value, write); + set(vm.getChainId(), key, value, write); } /// @notice Sets a uint256 array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, uint256[] memory value, bool write) public { - _uintArraysOf[chainId][key] = value; + _arraysOf[chainId][key] = abi.encode(value); if (write) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { @@ -535,14 +538,13 @@ contract StdConfig { /// @notice Sets a uint256 array for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, uint256[] memory value, bool write) public { - uint256 chainId = vm.getChainId(); - set(chainId, key, value, write); + set(vm.getChainId(), key, value, write); } /// @notice Sets an address array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, address[] memory value, bool write) public { - _addressArraysOf[chainId][key] = value; + _arraysOf[chainId][key] = abi.encode(value); if (write) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { @@ -557,14 +559,13 @@ contract StdConfig { /// @notice Sets an address array for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, address[] memory value, bool write) public { - uint256 chainId = vm.getChainId(); - set(chainId, key, value, write); + set(vm.getChainId(), key, value, write); } /// @notice Sets a bytes32 array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { - _bytes32ArraysOf[chainId][key] = value; + _arraysOf[chainId][key] = abi.encode(value); if (write) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { @@ -579,14 +580,13 @@ contract StdConfig { /// @notice Sets a bytes32 array for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, bytes32[] memory value, bool write) public { - uint256 chainId = vm.getChainId(); - set(chainId, key, value, write); + set(vm.getChainId(), key, value, write); } /// @notice Sets a string array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, string[] memory value, bool write) public { - _stringArraysOf[chainId][key] = value; + _arraysOf[chainId][key] = abi.encode(value); if (write) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { @@ -601,14 +601,13 @@ contract StdConfig { /// @notice Sets a string array for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, string[] memory value, bool write) public { - uint256 chainId = vm.getChainId(); - set(chainId, key, value, write); + set(vm.getChainId(), key, value, write); } /// @notice Sets a bytes array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes[] memory value, bool write) public { - _bytesArraysOf[chainId][key] = value; + _arraysOf[chainId][key] = abi.encode(value); if (write) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { @@ -623,7 +622,6 @@ contract StdConfig { /// @notice Sets a bytes array for a given key on the current chain. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(string memory key, bytes[] memory value, bool write) public { - uint256 chainId = vm.getChainId(); - set(chainId, key, value, write); + set(vm.getChainId(), key, value, write); } } From 20047e2cb1131dec848411910d69832f9e5e1eb4 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 17:49:31 +0200 Subject: [PATCH 22/54] don't use `string.concat` --- src/StdConfig.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 91051abb..ce8af169 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -183,7 +183,7 @@ contract StdConfig { } if (!success) { - revert(string.concat("Unable to parse variable '", var_key, "'")); + revert(_concat("Unable to parse variable '", var_key, "'")); } } From 07cdba75348a4d3c1badca8c96f83e4b1ee901e4 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 18:10:20 +0200 Subject: [PATCH 23/54] run `vm.py` --- src/Vm.sol | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Vm.sol b/src/Vm.sol index 14030179..b01f96bd 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -449,9 +449,6 @@ interface VmSafe { // ======== Environment ======== - /// Resolves all the env variable placeholders (`${ENV_VAR}`) of the input string. - function resolveEnv(string calldata input) external view returns (string memory resolvedInput); - /// 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); @@ -605,17 +602,14 @@ interface VmSafe { /// Returns true if `forge` command was executed in given context. function isContext(ForgeContext context) external view returns (bool result); + /// Resolves the env variable placeholders of a given input string. + function resolveEnv(string calldata input) external returns (string memory); + /// Sets environment variables. function setEnv(string calldata name, string calldata value) external; // ======== EVM ======== - /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. - function activeFork() external view returns (uint256 forkId); - - /// Returns the chain id of the currently active fork. Reverts if no fork is currently active. - function activeChain() external view returns (uint256 chainId); - /// 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); @@ -1853,17 +1847,22 @@ interface VmSafe { /// ABI-encodes a TOML table at `key`. function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); - /// Takes serialized JSON, converts to TOML and write a serialized TOML to a file. - function writeToml(string calldata json, string calldata path) 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. + /// Unlike `writeJson`, this cheatcode will create new keys if they didn't previously exist. + function writeJsonUpsert(string calldata json, string calldata path, string calldata valueKey) external; /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. - function writeToml(string calldata json, string calldata path, string calldata valueKey) external; + /// Unlike `writeToml`, this cheatcode will create new keys if they didn't previously exist. + function writeTomlUpsert(string calldata json, string calldata path, string calldata valueKey) external; + + /// Takes serialized JSON, converts to TOML and write a serialized TOML to a file. + function writeToml(string calldata json, string calldata path) external; /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. - /// Unlike `writeToml`, this cheatcode will create new keys if they do not already exist. - function writeTomlUpsert(string calldata json, string calldata path, string calldata valueKey) external; + function writeToml(string calldata json, string calldata path, string calldata valueKey) external; // ======== Utilities ======== @@ -2012,6 +2011,9 @@ interface Vm is VmSafe { /// Utility cheatcode to set an EIP-2930 access list for all subsequent transactions. function accessList(AccessListItem[] calldata access) external; + /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. + function activeFork() external view returns (uint256 forkId); + /// In forking mode, explicitly grant the given address cheatcode access. function allowCheatcodes(address account) external; From 6a534264d9e43bf32f2b59992e89658529560e59 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 25 Aug 2025 18:39:16 +0200 Subject: [PATCH 24/54] use constructor and disable warning --- foundry.toml | 12 ++++++++---- src/Base.sol | 3 +-- src/StdConfig.sol | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/foundry.toml b/foundry.toml index 62ca21a6..d9322989 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,12 +1,16 @@ [profile.default] -fs_permissions = [{ access = "read-write", path = "./"}] +fs_permissions = [{ access = "read-write", path = "./" }] optimizer = true optimizer_runs = 200 +# A list of solidity error codes to ignore. +# 2462 = "Visibility for constructor is ignored." +ignored_error_codes = [2462] + [rpc_endpoints] # The RPC URLs are modified versions of the default for testing initialization. -mainnet = "https://eth.merkle.io" # Different API key. -optimism_sepolia = "https://sepolia.optimism.io/" # Adds a trailing slash. +mainnet = "https://eth.merkle.io" # Different API key. +optimism_sepolia = "https://sepolia.optimism.io/" # Adds a trailing slash. arbitrum_one_sepolia = "https://sepolia-rollup.arbitrum.io/rpc/" # Adds a trailing slash. needs_undefined_env_var = "${UNDEFINED_RPC_URL_PLACEHOLDER}" @@ -20,4 +24,4 @@ multiline_func_header = 'attributes_first' quote_style = 'double' number_underscore = 'preserve' single_line_statement_blocks = 'preserve' -ignore = ["src/console.sol", "src/console2.sol"] \ No newline at end of file +ignore = ["src/console.sol", "src/console2.sol"] diff --git a/src/Base.sol b/src/Base.sol index deca0029..1cab41bc 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -62,8 +62,7 @@ abstract contract CommonConfig is CommonBase { function _loadConfig(string memory filePath) internal { console.log("----------"); console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); - config = new StdConfig(); - config.initialize(filePath); + config = new StdConfig(filePath); vm.makePersistent(address(config)); console.log("Config successfully loaded"); console.log("----------"); diff --git a/src/StdConfig.sol b/src/StdConfig.sol index ce8af169..0d1fa17d 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -52,7 +52,7 @@ contract StdConfig { mapping(uint256 => mapping(string => bytes)) private _valuesOf; mapping(uint256 => mapping(string => bytes)) private _arraysOf; - // -- INITIALIZER ---------------------------------------------------------- + // -- CONSTRUCTOR ---------------------------------------------------------- /// @notice Reads the TOML file and iterates through each top-level key, which is /// assumed to be a chain name or ID. For each chain, it caches its RPC @@ -64,7 +64,7 @@ contract StdConfig { /// parsed as either, the constructor will revert with an error. /// /// @param configFilePath: The local path to the TOML configuration file. - function initialize(string memory configFilePath) public { + constructor(string memory configFilePath) public { _filePath = configFilePath; string memory content = vm.resolveEnv(vm.readFile(configFilePath)); string[] memory chain_keys = vm.parseTomlKeys(content, "$"); From c6aecac446e6cb8088ae0edeef87902b4a238ffb Mon Sep 17 00:00:00 2001 From: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:04:50 +0200 Subject: [PATCH 25/54] better security and UX with `LibVariable` (#716) --- src/Base.sol | 54 +---- src/Config.sol | 59 ++++++ src/LibVariable.sol | 232 ++++++++++++++++++++++ src/StdConfig.sol | 426 +++++++++++++++++----------------------- src/Vm.sol | 24 ++- test/CommonConfig.t.sol | 199 ------------------- test/Config.t.sol | 413 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 904 insertions(+), 503 deletions(-) create mode 100644 src/Config.sol create mode 100644 src/LibVariable.sol delete mode 100644 test/CommonConfig.t.sol create mode 100644 test/Config.t.sol diff --git a/src/Base.sol b/src/Base.sol index 1cab41bc..52a50821 100644 --- a/src/Base.sol +++ b/src/Base.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -pragma experimental ABIEncoderV2; -import {console} from "./console.sol"; import {StdStorage} from "./StdStorage.sol"; -import {StdConfig} from "./StdConfig.sol"; import {Vm, VmSafe} from "./Vm.sol"; abstract contract CommonBase { @@ -44,55 +41,8 @@ abstract contract CommonBase { StdStorage internal stdstore; } -/// @notice Boilerplate to streamline the setup of multi-chain testing environments. -abstract contract CommonConfig is CommonBase { - // -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------ +abstract contract TestBase is CommonBase {} - StdConfig internal config; - uint256[] internal chainIds; - mapping(uint256 => uint256) internal forkOf; // [chainId -> forkId] - - // -- HELPER FUNCTIONS ----------------------------------------------------- - - /// @notice Loads configuration from a file. - /// - /// @dev This function instantiates a `Config` contract, caching all its config variables. - /// - /// @param filePath: the path to the TOML configuration file. - function _loadConfig(string memory filePath) internal { - console.log("----------"); - console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); - config = new StdConfig(filePath); - vm.makePersistent(address(config)); - console.log("Config successfully loaded"); - console.log("----------"); - } - - /// @notice Loads configuration from a file and creates forks for each specified chain. - /// - /// @dev This function instantiates a `Config` contract, caching all its config variables, - /// reads the configured chain ids, and iterates through them to create a fork for each one. - /// It also creates a map `forkOf[chainId] -> forkId` to easily switch between forks. - /// - /// @param filePath: the path to the TOML configuration file. - function _loadConfigAndForks(string memory filePath) internal { - _loadConfig(filePath); - - console.log("Setting up forks for the configured chains..."); - uint256[] memory chains = config.getChainIds(); - for (uint256 i = 0; i < chains.length; i++) { - uint256 chainId = chains[i]; - uint256 forkId = vm.createFork(config.getRpcUrl(chainId)); - forkOf[chainId] = forkId; - chainIds.push(chainId); - } - console.log("Forks successfully created"); - console.log("----------"); - } -} - -abstract contract TestBase is CommonConfig {} - -abstract contract ScriptBase is CommonConfig { +abstract contract ScriptBase is CommonBase { VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); } diff --git a/src/Config.sol b/src/Config.sol new file mode 100644 index 00000000..d06bbeaa --- /dev/null +++ b/src/Config.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import {console} from "./console.sol"; +import {StdConfig} from "./StdConfig.sol"; +import {CommonBase} from "./Base.sol"; + +/// @notice Boilerplate to streamline the setup of multi-chain environments. +abstract contract Config is CommonBase { + // -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------ + + /// @notice Contract instance holding the data from the TOML config file. + StdConfig internal config; + + /// @notice Array of chain IDs for which forks have been created. + uint256[] internal chainIds; + + /// @notice A mapping from a chain ID to its initialized fork ID. + mapping(uint256 => uint256) internal forkOf; + + // -- HELPER FUNCTIONS ----------------------------------------------------- + + /// @notice Loads configuration from a file. + /// + /// @dev This function instantiates a `Config` contract, caching all its config variables. + /// + /// @param filePath: the path to the TOML configuration file. + function _loadConfig(string memory filePath) internal { + console.log("----------"); + console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); + config = new StdConfig(filePath); + vm.makePersistent(address(config)); + console.log("Config successfully loaded"); + console.log("----------"); + } + + /// @notice Loads configuration from a file and creates forks for each specified chain. + /// + /// @dev This function instantiates a `Config` contract, caching all its config variables, + /// reads the configured chain ids, and iterates through them to create a fork for each one. + /// It also creates a map `forkOf[chainId] -> forkId` to easily switch between forks. + /// + /// @param filePath: the path to the TOML configuration file. + function _loadConfigAndForks(string memory filePath) internal { + _loadConfig(filePath); + + console.log("Setting up forks for the configured chains..."); + uint256[] memory chains = config.getChainIds(); + for (uint256 i = 0; i < chains.length; i++) { + uint256 chainId = chains[i]; + uint256 forkId = vm.createFork(config.getRpcUrl(chainId)); + forkOf[chainId] = forkId; + chainIds.push(chainId); + } + console.log("Forks successfully created"); + console.log("----------"); + } +} diff --git a/src/LibVariable.sol b/src/LibVariable.sol new file mode 100644 index 00000000..646a865c --- /dev/null +++ b/src/LibVariable.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; + +using LibVariable for Variable global; + +struct Variable { + Type ty; + bytes data; +} + +struct Type { + TypeKind kind; + bool isArray; +} + +enum TypeKind { + None, + Bool, + Address, + Uint256, + Bytes32, + String, + Bytes +} + +/// @notice Library for type-safe coercion of the `Variable` struct to concrete types. +/// +/// @dev Ensures that when a `Variable` is cast to a concrete Solidity type, the operation is safe and the +/// underlying type matches what is expected. +/// Provides functions to check types, convert them to strings, and coerce `Variable` instances into +/// both single values and arrays of various types. +/// +/// Usage example: +/// ```solidity +/// import {LibVariable} from "./LibVariable.sol"; +/// +/// contract MyContract { +/// using LibVariable for Variable; +/// StdConfig config; // Assume 'config' is an instance of `StdConfig` and has already been loaded. +/// +/// function readValues() public { +/// // Retrieve a 'uint256' value from the config. +/// uint256 myNumber = config.get("important_number").toUint(); +/// +/// // Would revert with `TypeMismatch` as 'important_number' isn't a `uint256` in the config file. +/// // string memory notANumber = config.get("important_number").toString(); +/// +/// // Retrieve a address array from the config. +/// string[] memory admins = config.get("whitelisted_admins").toAddressArray(); +/// } +/// } +/// ``` +library LibVariable { + error NotInitialized(); + error TypeMismatch(string expected, string actual); + + // -- TYPE HELPERS ---------------------------------------------------- + + /// @notice Compares two Type instances for equality. + function isEqual(Type memory self, Type memory other) internal pure returns (bool) { + return self.kind == other.kind && self.isArray == other.isArray; + } + + /// @notice Compares two Type instances for equality. Reverts if they are not equal. + function assertEq(Type memory self, Type memory other) internal pure { + if (!isEqual(self, other)) { + revert TypeMismatch(toString(other), toString(self)); + } + } + + /// @notice Converts a Type struct to its full string representation (i.e. "uint256[]"). + function toString(Type memory self) internal pure returns (string memory) { + string memory tyStr = toString(self.kind); + if (!self.isArray || self.kind == TypeKind.None) { + return tyStr; + } else { + return string(abi.encodePacked(tyStr, "[]")); + } + } + + /// @dev Converts a `TypeKind` enum to its base string representation. + function toString(TypeKind self) internal pure returns (string memory) { + if (self == TypeKind.Bool) return "bool"; + if (self == TypeKind.Address) return "address"; + if (self == TypeKind.Uint256) return "uint256"; + if (self == TypeKind.Bytes32) return "bytes32"; + if (self == TypeKind.String) return "string"; + if (self == TypeKind.Bytes) return "bytes"; + return "none"; + } + + /// @dev Converts a `TypeKind` enum to its base string representation. + function toTomlKey(TypeKind self) internal pure returns (string memory) { + if (self == TypeKind.Bool) return "bool"; + if (self == TypeKind.Address) return "address"; + if (self == TypeKind.Uint256) return "uint"; + if (self == TypeKind.Bytes32) return "bytes32"; + if (self == TypeKind.String) return "string"; + if (self == TypeKind.Bytes) return "bytes"; + return "none"; + } + + // -- VARIABLE HELPERS ---------------------------------------------------- + + /// @dev Checks if a `Variable` has been initialized and matches the expected type reverting if not. + modifier check(Variable memory self, Type memory expected) { + assertExists(self); + assertEq(self.ty, expected); + _; + } + + /// @dev Checks if a `Variable` has been initialized, reverting if not. + function assertExists(Variable memory self) public pure { + if (self.ty.kind == TypeKind.None) { + revert NotInitialized(); + } + } + + // -- VARIABLE COERCION FUNCTIONS (SINGLE VALUES) -------------------------- + + /// @notice Coerces a `Variable` to a `bool` value. + function toBool(Variable memory self) internal pure check(self, Type(TypeKind.Bool, false)) returns (bool) { + return abi.decode(self.data, (bool)); + } + + /// @notice Coerces a `Variable` to a `uint256` value. + function toUint(Variable memory self) internal pure check(self, Type(TypeKind.Uint256, false)) returns (uint256) { + return abi.decode(self.data, (uint256)); + } + + /// @notice Coerces a `Variable` to an `address` value. + function toAddress(Variable memory self) + internal + pure + check(self, Type(TypeKind.Address, false)) + returns (address) + { + return abi.decode(self.data, (address)); + } + + /// @notice Coerces a `Variable` to a `bytes32` value. + function toBytes32(Variable memory self) + internal + pure + check(self, Type(TypeKind.Bytes32, false)) + returns (bytes32) + { + return abi.decode(self.data, (bytes32)); + } + + /// @notice Coerces a `Variable` to a `string` value. + function toString(Variable memory self) + internal + pure + check(self, Type(TypeKind.String, false)) + returns (string memory) + { + return abi.decode(self.data, (string)); + } + + /// @notice Coerces a `Variable` to a `bytes` value. + function toBytes(Variable memory self) + internal + pure + check(self, Type(TypeKind.Bytes, false)) + returns (bytes memory) + { + return abi.decode(self.data, (bytes)); + } + + // -- VARIABLE COERCION FUNCTIONS (ARRAYS) --------------------------------- + + /// @notice Coerces a `Variable` to a `bool` array. + function toBoolArray(Variable memory self) + internal + pure + check(self, Type(TypeKind.Bool, true)) + returns (bool[] memory) + { + return abi.decode(self.data, (bool[])); + } + + /// @notice Coerces a `Variable` to a `uint256` array. + function toUintArray(Variable memory self) + internal + pure + check(self, Type(TypeKind.Uint256, true)) + returns (uint256[] memory) + { + return abi.decode(self.data, (uint256[])); + } + + /// @notice Coerces a `Variable` to an `address` array. + function toAddressArray(Variable memory self) + internal + pure + check(self, Type(TypeKind.Address, true)) + returns (address[] memory) + { + return abi.decode(self.data, (address[])); + } + + /// @notice Coerces a `Variable` to a `bytes32` array. + function toBytes32Array(Variable memory self) + internal + pure + check(self, Type(TypeKind.Bytes32, true)) + returns (bytes32[] memory) + { + return abi.decode(self.data, (bytes32[])); + } + + /// @notice Coerces a `Variable` to a `string` array. + function toStringArray(Variable memory self) + internal + pure + check(self, Type(TypeKind.String, true)) + returns (string[] memory) + { + return abi.decode(self.data, (string[])); + } + + /// @notice Coerces a `Variable` to a `bytes` array. + function toBytesArray(Variable memory self) + internal + pure + check(self, Type(TypeKind.Bytes, true)) + returns (bytes[] memory) + { + return abi.decode(self.data, (bytes[])); + } +} diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 0d1fa17d..fef5158b 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -3,6 +3,7 @@ pragma solidity >=0.6.2 <0.9.0; pragma experimental ABIEncoderV2; import {VmSafe} from "./Vm.sol"; +import {Variable, Type, TypeKind, LibVariable} from "./LibVariable.sol"; /// @notice A contract that parses a toml configuration file and load its /// variables into storage, automatically casting them, on deployment. @@ -32,25 +33,38 @@ import {VmSafe} from "./Vm.sol"; /// important_number = 123 /// ``` contract StdConfig { - // -- CONSTANTS ------------------------------------------------------------ + using LibVariable for Type; + using LibVariable for TypeKind; - /// @dev Forge Standard Library VM interface for cheat codes. VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + uint8 private constant NUM_TYPES = 7; + + // -- ERRORS --------------------------------------------------------------- + + error AlreadyInitialized(string key); + error InvalidChainKey(string aliasOrId); + error ChainIdNotFound(uint256 chainId); + error UnableToParseVariable(string key); // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ - // Path to the loaded TOML configuration file. + /// @notice Path to the loaded TOML configuration file. string private _filePath; - // List of top-level keys found in the TOML file, assumed to be chain names/aliases. + /// @notice List of top-level keys found in the TOML file, assumed to be chain names/aliases. string[] private _chainKeys; - // Storage for the configured RPC URL for each chain [`chainId` -> `url`]. + /// @notice Storage for the configured RPC URL for each chain. mapping(uint256 => string) private _rpcOf; - // Storage for values and arrays, organized by chain ID and variable key [`chainId` -> `key` -> `value`]. - mapping(uint256 => mapping(string => bytes)) private _valuesOf; - mapping(uint256 => mapping(string => bytes)) private _arraysOf; + /// @notice Storage for values, organized by chain ID and variable key. + mapping(uint256 => mapping(string => bytes)) private _dataOf; + + /// @notice Type cache for runtime checking when casting. + mapping(uint256 => mapping(string => Type)) private _typeOf; + + /// @notice When enabled, `set` will always write updates back to the configuration file. + bool private _autoWrite; // -- CONSTRUCTOR ---------------------------------------------------------- @@ -69,14 +83,6 @@ contract StdConfig { string memory content = vm.resolveEnv(vm.readFile(configFilePath)); string[] memory chain_keys = vm.parseTomlKeys(content, "$"); - string[] memory types = new string[](6); - types[0] = "bool"; - types[1] = "address"; - types[2] = "uint"; - types[3] = "bytes32"; - types[4] = "string"; - types[5] = "bytes"; - // Cache the entire configuration to storage for (uint256 i = 0; i < chain_keys.length; i++) { string memory chain_key = chain_keys[i]; @@ -84,26 +90,30 @@ contract StdConfig { if (vm.parseTomlKeys(content, _concat("$.", chain_key)).length == 0) { continue; } - uint256 chain_id = resolveChainId(chain_key); + uint256 chainId = resolveChainId(chain_key); _chainKeys.push(chain_key); // Cache the configure rpc endpoint for that chain. // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured. try vm.parseTomlString(content, _concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { - _rpcOf[chain_id] = vm.resolveEnv(url); + _rpcOf[chainId] = vm.resolveEnv(url); } catch { - _rpcOf[chain_id] = vm.resolveEnv(vm.rpcUrl(chain_key)); + _rpcOf[chainId] = vm.resolveEnv(vm.rpcUrl(chain_key)); } - for (uint256 t = 0; t < types.length; t++) { - string memory var_type = types[t]; - string memory path_to_type = _concat("$.", chain_key, ".", var_type); - - try vm.parseTomlKeys(content, path_to_type) returns (string[] memory var_keys) { - for (uint256 j = 0; j < var_keys.length; j++) { - string memory var_key = var_keys[j]; - string memory path_to_var = _concat(path_to_type, ".", var_key); - _loadAndCacheValue(content, path_to_var, chain_id, var_key, var_type); + // Iterate through all the available `TypeKind`s (except `None`) to create the sub-section paths + for (uint8 t = 1; t < NUM_TYPES; t++) { + TypeKind ty = TypeKind(t); + string memory typePath = _concat("$.", chain_key, ".", ty.toTomlKey()); + + try vm.parseTomlKeys(content, typePath) returns (string[] memory keys) { + for (uint256 j = 0; j < keys.length; j++) { + string memory key = keys[j]; + if (_typeOf[chainId][key].kind == TypeKind.None) { + _loadAndCacheValue(content, _concat(typePath, ".", key), chainId, key, ty); + } else { + revert AlreadyInitialized(key); + } } } catch {} // Section does not exist, ignore. } @@ -112,82 +122,97 @@ contract StdConfig { function _loadAndCacheValue( string memory content, - string memory path_to_var, - uint256 chain_id, - string memory var_key, - string memory var_type + string memory path, + uint256 chainId, + string memory key, + TypeKind ty ) private { - bytes32 typeHash = keccak256(bytes(var_type)); bool success = false; - - if (typeHash == keccak256(bytes("bool"))) { - try vm.parseTomlBool(content, path_to_var) returns (bool val) { - _valuesOf[chain_id][var_key] = abi.encode(val); + if (ty == TypeKind.Bool) { + try vm.parseTomlBool(content, path) returns (bool val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bool, false); success = true; } catch { - try vm.parseTomlBoolArray(content, path_to_var) returns (bool[] memory val) { - _arraysOf[chain_id][var_key] = abi.encode(val); + try vm.parseTomlBoolArray(content, path) returns (bool[] memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bool, true); success = true; } catch {} } - } else if (typeHash == keccak256(bytes("address"))) { - try vm.parseTomlAddress(content, path_to_var) returns (address val) { - _valuesOf[chain_id][var_key] = abi.encode(val); + } else if (ty == TypeKind.Address) { + try vm.parseTomlAddress(content, path) returns (address val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Address, false); success = true; } catch { - try vm.parseTomlAddressArray(content, path_to_var) returns (address[] memory val) { - _arraysOf[chain_id][var_key] = abi.encode(val); + try vm.parseTomlAddressArray(content, path) returns (address[] memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Address, true); success = true; } catch {} } - } else if (typeHash == keccak256(bytes("uint"))) { - try vm.parseTomlUint(content, path_to_var) returns (uint256 val) { - _valuesOf[chain_id][var_key] = abi.encode(val); + } else if (ty == TypeKind.Uint256) { + try vm.parseTomlUint(content, path) returns (uint256 val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Uint256, false); success = true; } catch { - try vm.parseTomlUintArray(content, path_to_var) returns (uint256[] memory val) { - _arraysOf[chain_id][var_key] = abi.encode(val); + try vm.parseTomlUintArray(content, path) returns (uint256[] memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Uint256, true); success = true; } catch {} } - } else if (typeHash == keccak256(bytes("bytes32"))) { - try vm.parseTomlBytes32(content, path_to_var) returns (bytes32 val) { - _valuesOf[chain_id][var_key] = abi.encode(val); + } else if (ty == TypeKind.Bytes32) { + try vm.parseTomlBytes32(content, path) returns (bytes32 val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bytes32, false); success = true; } catch { - try vm.parseTomlBytes32Array(content, path_to_var) returns (bytes32[] memory val) { - _arraysOf[chain_id][var_key] = abi.encode(val); + try vm.parseTomlBytes32Array(content, path) returns (bytes32[] memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bytes32, true); success = true; } catch {} } - } else if (typeHash == keccak256(bytes("bytes"))) { - try vm.parseTomlBytes(content, path_to_var) returns (bytes memory val) { - _valuesOf[chain_id][var_key] = abi.encode(val); + } else if (ty == TypeKind.Bytes) { + try vm.parseTomlBytes(content, path) returns (bytes memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bytes, false); success = true; } catch { - try vm.parseTomlBytesArray(content, path_to_var) returns (bytes[] memory val) { - _arraysOf[chain_id][var_key] = abi.encode(val); + try vm.parseTomlBytesArray(content, path) returns (bytes[] memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bytes, true); success = true; } catch {} } - } else if (typeHash == keccak256(bytes("string"))) { - try vm.parseTomlString(content, path_to_var) returns (string memory val) { - _valuesOf[chain_id][var_key] = abi.encode(val); + } else if (ty == TypeKind.String) { + try vm.parseTomlString(content, path) returns (string memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.String, false); success = true; } catch { - try vm.parseTomlStringArray(content, path_to_var) returns (string[] memory val) { - _arraysOf[chain_id][var_key] = abi.encode(val); + try vm.parseTomlStringArray(content, path) returns (string[] memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.String, true); success = true; } catch {} } } if (!success) { - revert(_concat("Unable to parse variable '", var_key, "'")); + revert UnableToParseVariable(key); } } - // -- HELPER FUNCTIONS -----------------------------------------------------\n + // -- HELPER FUNCTIONS ----------------------------------------------------- + + /// @notice Enable or disable automatic writing to the TOML file on `set`. + function setAutoWrite(bool enabled) public { + _autoWrite = enabled; + } /// @notice Resolves a chain alias or a chain id string to its numerical chain id. /// @param aliasOrId The string representing the chain alias (i.e. "mainnet") or a numerical ID (i.e. "1"). @@ -201,7 +226,7 @@ contract StdConfig { try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) { return chainInfo.chainId; } catch { - revert(_concat("chain key: '", aliasOrId, "' is not a valid alias nor a number.")); + revert InvalidChainKey(aliasOrId); } } } @@ -213,7 +238,19 @@ contract StdConfig { return _chainKeys[i]; } } - revert(_concat("chain id: '", vm.toString(chainId), "' not found in configuration")); + revert ChainIdNotFound(chainId); + } + + /// @dev Ensures type consistency when setting a value - prevents changing types unless uninitialized. + /// Updates type only when the previous type was `None`. + function _ensureTypeConsistency(uint256 chainId, string memory key, Type memory ty) private { + Type memory current = _typeOf[chainId][key]; + + if (current.kind == TypeKind.None) { + _typeOf[chainId][key] = ty; + } else { + current.assertEq(ty); + } } /// @dev concatenates two strings @@ -253,6 +290,27 @@ contract StdConfig { // -- GETTER FUNCTIONS ----------------------------------------------------- + /// @dev Reads a variable for a given chain id and key, and returns it in a generic container. + /// The caller should use `LibVariable` to safely coerce the type. + /// Example: `uint256 myVar = config.get("my_key").toUint();` + /// + /// @param chain_id The chain ID to read from. + /// @param key The key of the variable to retrieve. + /// @return `Variable` struct containing the type and the ABI-encoded value. + function get(uint256 chain_id, string memory key) public view returns (Variable memory) { + return Variable(_typeOf[chain_id][key], _dataOf[chain_id][key]); + } + + /// @dev Reads a variable for the current chain and a given key, and returns it in a generic container. + /// The caller should use `LibVariable` to safely coerce the type. + /// Example: `uint256 myVar = config.get("my_key").toUint();` + /// + /// @param key The key of the variable to retrieve. + /// @return `Variable` struct containing the type and the ABI-encoded value. + function get(string memory key) public view returns (Variable memory) { + return get(vm.getChainId(), key); + } + /// @notice Returns the numerical chain ids for all configured chains. function getChainIds() public view returns (uint256[] memory) { string[] memory keys = _chainKeys; @@ -266,8 +324,8 @@ contract StdConfig { } /// @notice Reads the RPC URL for a specific chain id. - function getRpcUrl(uint256 chain_id) public view returns (string memory) { - return _rpcOf[chain_id]; + function getRpcUrl(uint256 chainId) public view returns (string memory) { + return _rpcOf[chainId]; } /// @notice Reads the RPC URL for the current chain. @@ -275,157 +333,15 @@ contract StdConfig { return _rpcOf[vm.getChainId()]; } - /// @notice Reads a boolean value for a given key and chain ID. - function getBool(uint256 chain_id, string memory key) public view returns (bool) { - bytes memory val = _valuesOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (bool)); - } - - /// @notice Reads a boolean value for a given key on the current chain. - function getBool(string memory key) public view returns (bool) { - return getBool(vm.getChainId(), key); - } - - /// @notice Reads a uint256 value for a given key and chain ID. - function getUint(uint256 chain_id, string memory key) public view returns (uint256) { - bytes memory val = _valuesOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (uint256)); - } - - /// @notice Reads a uint256 value for a given key on the current chain. - function getUint(string memory key) public view returns (uint256) { - return getUint(vm.getChainId(), key); - } - - /// @notice Reads an address value for a given key and chain ID. - function getAddress(uint256 chain_id, string memory key) public view returns (address) { - bytes memory val = _valuesOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (address)); - } - - /// @notice Reads an address value for a given key on the current chain. - function getAddress(string memory key) public view returns (address) { - return getAddress(vm.getChainId(), key); - } - - /// @notice Reads a bytes32 value for a given key and chain ID. - function getBytes32(uint256 chain_id, string memory key) public view returns (bytes32) { - bytes memory val = _valuesOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (bytes32)); - } - - /// @notice Reads a bytes32 value for a given key on the current chain. - function getBytes32(string memory key) public view returns (bytes32) { - return getBytes32(vm.getChainId(), key); - } - - /// @notice Reads a string value for a given key and chain ID. - function getString(uint256 chain_id, string memory key) public view returns (string memory) { - bytes memory val = _valuesOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (string)); - } - - /// @notice Reads a string value for a given key on the current chain. - function getString(string memory key) public view returns (string memory) { - return getString(vm.getChainId(), key); - } - - /// @notice Reads a bytes value for a given key and chain ID. - function getBytes(uint256 chain_id, string memory key) public view returns (bytes memory) { - bytes memory val = _valuesOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (bytes)); - } - - /// @notice Reads a bytes value for a given key on the current chain. - function getBytes(string memory key) public view returns (bytes memory) { - return getBytes(vm.getChainId(), key); - } - - /// @notice Reads a boolean array for a given key and chain ID. - function getBoolArray(uint256 chain_id, string memory key) public view returns (bool[] memory) { - bytes memory val = _arraysOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (bool[])); - } - - /// @notice Reads a boolean array for a given key on the current chain. - function getBoolArray(string memory key) public view returns (bool[] memory) { - return getBoolArray(vm.getChainId(), key); - } - - /// @notice Reads a uint256 array for a given key and chain ID. - function getUintArray(uint256 chain_id, string memory key) public view returns (uint256[] memory) { - bytes memory val = _arraysOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (uint256[])); - } - - /// @notice Reads a uint256 array for a given key on the current chain. - function getUintArray(string memory key) public view returns (uint256[] memory) { - return getUintArray(vm.getChainId(), key); - } - - /// @notice Reads an address array for a given key and chain ID. - function getAddressArray(uint256 chain_id, string memory key) public view returns (address[] memory) { - bytes memory val = _arraysOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (address[])); - } - - /// @notice Reads an address array for a given key on the current chain. - function getAddressArray(string memory key) public view returns (address[] memory) { - return getAddressArray(vm.getChainId(), key); - } - - /// @notice Reads a bytes32 array for a given key and chain ID. - function getBytes32Array(uint256 chain_id, string memory key) public view returns (bytes32[] memory) { - bytes memory val = _arraysOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (bytes32[])); - } - - /// @notice Reads a bytes32 array for a given key on the current chain. - function getBytes32Array(string memory key) public view returns (bytes32[] memory) { - return getBytes32Array(vm.getChainId(), key); - } - - /// @notice Reads a string array for a given key and chain ID. - function getStringArray(uint256 chain_id, string memory key) public view returns (string[] memory) { - bytes memory val = _arraysOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (string[])); - } - - /// @notice Reads a string array for a given key on the current chain. - function getStringArray(string memory key) public view returns (string[] memory) { - return getStringArray(vm.getChainId(), key); - } - - /// @notice Reads a bytes array for a given key and chain ID. - function getBytesArray(uint256 chain_id, string memory key) public view returns (bytes[] memory) { - bytes memory val = _arraysOf[chain_id][key]; - require(val.length > 0, "Value not found"); - return abi.decode(val, (bytes[])); - } - - /// @notice Reads a bytes array for a given key on the current chain. - function getBytesArray(string memory key) public view returns (bytes[] memory) { - return getBytesArray(vm.getChainId(), key); - } - // -- SETTER FUNCTIONS ----------------------------------------------------- /// @notice Sets a boolean value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bool value, bool write) public { - _valuesOf[chainId][key] = abi.encode(value); - if (write) _writeToToml(chainId, "bool", key, vm.toString(value)); + Type memory ty = Type(TypeKind.Bool, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); } /// @notice Sets a boolean value for a given key on the current chain. @@ -437,8 +353,10 @@ contract StdConfig { /// @notice Sets a uint256 value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, uint256 value, bool write) public { - _valuesOf[chainId][key] = abi.encode(value); - if (write) _writeToToml(chainId, "uint", key, vm.toString(value)); + Type memory ty = Type(TypeKind.Uint256, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); } /// @notice Sets a uint256 value for a given key on the current chain. @@ -450,8 +368,10 @@ contract StdConfig { /// @notice Sets an address value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, address value, bool write) public { - _valuesOf[chainId][key] = abi.encode(value); - if (write) _writeToToml(chainId, "address", key, _quote(vm.toString(value))); + Type memory ty = Type(TypeKind.Address, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); } /// @notice Sets an address value for a given key on the current chain. @@ -463,8 +383,10 @@ contract StdConfig { /// @notice Sets a bytes32 value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes32 value, bool write) public { - _valuesOf[chainId][key] = abi.encode(value); - if (write) _writeToToml(chainId, "bytes32", key, _quote(vm.toString(value))); + Type memory ty = Type(TypeKind.Bytes32, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); } /// @notice Sets a bytes32 value for a given key on the current chain. @@ -476,8 +398,10 @@ contract StdConfig { /// @notice Sets a string value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, string memory value, bool write) public { - _valuesOf[chainId][key] = abi.encode(value); - if (write) _writeToToml(chainId, "string", key, _quote(value)); + Type memory ty = Type(TypeKind.String, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(value)); } /// @notice Sets a string value for a given key on the current chain. @@ -489,8 +413,10 @@ contract StdConfig { /// @notice Sets a bytes value for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes memory value, bool write) public { - _valuesOf[chainId][key] = abi.encode(value); - if (write) _writeToToml(chainId, "bytes", key, _quote(vm.toString(value))); + Type memory ty = Type(TypeKind.Bytes, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); } /// @notice Sets a bytes value for a given key on the current chain. @@ -502,15 +428,17 @@ contract StdConfig { /// @notice Sets a boolean array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bool[] memory value, bool write) public { - _arraysOf[chainId][key] = abi.encode(value); - if (write) { + Type memory ty = Type(TypeKind.Bool, true); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, vm.toString(value[i])); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); - _writeToToml(chainId, "bool", key, json); + _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -523,15 +451,17 @@ contract StdConfig { /// @notice Sets a uint256 array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, uint256[] memory value, bool write) public { - _arraysOf[chainId][key] = abi.encode(value); - if (write) { + Type memory ty = Type(TypeKind.Uint256, true); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, vm.toString(value[i])); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); - _writeToToml(chainId, "uint", key, json); + _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -544,15 +474,17 @@ contract StdConfig { /// @notice Sets an address array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, address[] memory value, bool write) public { - _arraysOf[chainId][key] = abi.encode(value); - if (write) { + Type memory ty = Type(TypeKind.Address, true); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); - _writeToToml(chainId, "address", key, json); + _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -565,15 +497,17 @@ contract StdConfig { /// @notice Sets a bytes32 array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { - _arraysOf[chainId][key] = abi.encode(value); - if (write) { + Type memory ty = Type(TypeKind.Bytes32, true); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); - _writeToToml(chainId, "bytes32", key, json); + _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -586,15 +520,17 @@ contract StdConfig { /// @notice Sets a string array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, string[] memory value, bool write) public { - _arraysOf[chainId][key] = abi.encode(value); - if (write) { + Type memory ty = Type(TypeKind.String, true); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(value[i])); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); - _writeToToml(chainId, "string", key, json); + _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -607,15 +543,17 @@ contract StdConfig { /// @notice Sets a bytes array for a given key and chain ID. /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. function set(uint256 chainId, string memory key, bytes[] memory value, bool write) public { - _arraysOf[chainId][key] = abi.encode(value); - if (write) { + Type memory ty = Type(TypeKind.Bytes, true); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (write || _autoWrite) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); - _writeToToml(chainId, "bytes", key, json); + _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } diff --git a/src/Vm.sol b/src/Vm.sol index b01f96bd..0f8f9b68 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -611,7 +611,7 @@ interface VmSafe { // ======== 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); + function accesses(address target) external view returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); /// Gets the address for a given private key. function addr(uint256 privateKey) external pure returns (address keyAddr); @@ -619,6 +619,7 @@ interface VmSafe { /// Gets all the logs according to specified filter. function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external + view returns (EthGetLogs[] memory logs); /// Gets the current `block.blobbasefee`. @@ -648,27 +649,28 @@ interface VmSafe { /// Gets the map key and parent of a mapping at a given slot, for a given address. function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external + view returns (bool found, bytes32 key, bytes32 parent); /// 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); + function getMappingLength(address target, bytes32 mappingSlot) external view returns (uint256 length); /// 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); + function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external view returns (bytes32 value); /// Gets the nonce of an account. function getNonce(address account) external view returns (uint64 nonce); /// Get the nonce of a `Wallet`. - function getNonce(Wallet calldata wallet) external returns (uint64 nonce); + function getNonce(Wallet calldata wallet) external view returns (uint64 nonce); /// Gets the RLP encoded block header for a given block number. /// Returns the block header in the same format as `cast block --raw`. function getRawBlockHeader(uint256 blockNumber) external view returns (bytes memory rlpHeader); /// Gets all the recorded logs. - function getRecordedLogs() external returns (Log[] memory logs); + function getRecordedLogs() external view returns (Log[] memory logs); /// Returns state diffs from current `vm.startStateDiffRecording` session. function getStateDiff() external view returns (string memory diff); @@ -1163,7 +1165,7 @@ interface VmSafe { function broadcast(uint256 privateKey) external; /// Returns addresses of available unlocked wallets in the script environment. - function getWallets() external returns (address[] memory wallets); + function getWallets() external view returns (address[] memory wallets); /// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction function signAndAttachDelegation(address implementation, uint256 privateKey) @@ -1216,7 +1218,7 @@ interface VmSafe { // ======== String ======== /// Returns true if `search` is found in `subject`, false otherwise. - function contains(string calldata subject, string calldata search) external returns (bool result); + function contains(string calldata subject, string calldata search) external pure returns (bool result); /// Returns the index of the first occurrence of a `key` in an `input` string. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found. @@ -1866,6 +1868,12 @@ interface VmSafe { // ======== Utilities ======== + /// Returns an uint256 value bounded in given range and different from the current one. + function bound(uint256 current, uint256 min, uint256 max) external view returns (uint256); + + /// Returns an int256 value bounded in given range and different from the current one. + function bound(int256 current, int256 min, int256 max) external view returns (int256); + /// Compute the address of a contract created with CREATE2 using the given CREATE2 deployer. function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external @@ -2190,7 +2198,7 @@ interface Vm is VmSafe { function prevrandao(uint256 newPrevrandao) external; /// 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); + function readCallers() external view returns (CallerMode callerMode, address msgSender, address txOrigin); /// Resets the nonce of an account to 0 for EOAs and 1 for contract accounts. function resetNonce(address account) external; diff --git a/test/CommonConfig.t.sol b/test/CommonConfig.t.sol deleted file mode 100644 index 08e8cbef..00000000 --- a/test/CommonConfig.t.sol +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; -pragma experimental ABIEncoderV2; - -import {Test} from "../src/Test.sol"; - -contract CommonConfigTest is Test { - function test_loadConfig() public { - // Deploy the config contract with the test fixture. - _loadConfig("./test/fixtures/config.toml"); - - // -- MAINNET -------------------------------------------------------------- - - // Read and assert RPC URL for Mainnet (chain ID 1) - assertEq(config.getRpcUrl(1), "https://eth.llamarpc.com"); - - // Read and assert boolean values - assertTrue(config.getBool(1, "is_live")); - bool[] memory bool_array = config.getBoolArray(1, "bool_array"); - assertTrue(bool_array[0]); - assertFalse(bool_array[1]); - - // Read and assert address values - assertEq(config.getAddress(1, "weth"), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - address[] memory address_array = config.getAddressArray(1, "deps"); - assertEq(address_array[0], 0x0000000000000000000000000000000000000000); - assertEq(address_array[1], 0x1111111111111111111111111111111111111111); - - // Read and assert uint values - assertEq(config.getUint(1, "number"), 1234); - uint256[] memory uint_array = config.getUintArray(1, "number_array"); - assertEq(uint_array[0], 5678); - assertEq(uint_array[1], 9999); - - // Read and assert bytes32 values - assertEq(config.getBytes32(1, "word"), bytes32(uint256(1234))); - bytes32[] memory bytes32_array = config.getBytes32Array(1, "word_array"); - assertEq(bytes32_array[0], bytes32(uint256(5678))); - assertEq(bytes32_array[1], bytes32(uint256(9999))); - - // Read and assert bytes values - assertEq(config.getBytes(1, "b"), hex"abcd"); - bytes[] memory bytes_array = config.getBytesArray(1, "b_array"); - assertEq(bytes_array[0], hex"dead"); - assertEq(bytes_array[1], hex"beef"); - - // Read and assert string values - assertEq(config.getString(1, "str"), "foo"); - string[] memory string_array = config.getStringArray(1, "str_array"); - assertEq(string_array[0], "bar"); - assertEq(string_array[1], "baz"); - - // -- OPTIMISM ------------------------------------------------------------- - - // Read and assert RPC URL for Optimism (chain ID 10) - assertEq(config.getRpcUrl(10), "https://mainnet.optimism.io"); - - // Read and assert boolean values - assertFalse(config.getBool(10, "is_live")); - bool_array = config.getBoolArray(10, "bool_array"); - assertFalse(bool_array[0]); - assertTrue(bool_array[1]); - - // Read and assert address values - assertEq(config.getAddress(10, "weth"), 0x4200000000000000000000000000000000000006); - address_array = config.getAddressArray(10, "deps"); - assertEq(address_array[0], 0x2222222222222222222222222222222222222222); - assertEq(address_array[1], 0x3333333333333333333333333333333333333333); - - // Read and assert uint values - assertEq(config.getUint(10, "number"), 9999); - uint_array = config.getUintArray(10, "number_array"); - assertEq(uint_array[0], 1234); - assertEq(uint_array[1], 5678); - - // Read and assert bytes32 values - assertEq(config.getBytes32(10, "word"), bytes32(uint256(9999))); - bytes32_array = config.getBytes32Array(10, "word_array"); - assertEq(bytes32_array[0], bytes32(uint256(1234))); - assertEq(bytes32_array[1], bytes32(uint256(5678))); - - // Read and assert bytes values - assertEq(config.getBytes(10, "b"), hex"dcba"); - bytes_array = config.getBytesArray(10, "b_array"); - assertEq(bytes_array[0], hex"c0ffee"); - assertEq(bytes_array[1], hex"babe"); - - // Read and assert string values - assertEq(config.getString(10, "str"), "alice"); - string_array = config.getStringArray(10, "str_array"); - assertEq(string_array[0], "bob"); - assertEq(string_array[1], "charlie"); - } - - function test_loadConfigAndForks() public { - _loadConfigAndForks("./test/fixtures/config.toml"); - - // assert that the map of chain id and fork ids is created and that the chain ids actually match - assertEq(forkOf[1], 0); - vm.selectFork(forkOf[1]); - assertEq(vm.getChainId(), 1); - - assertEq(forkOf[10], 1); - vm.selectFork(forkOf[10]); - assertEq(vm.getChainId(), 10); - } - - function test_writeConfig() public { - // Create a temporary copy of the config file to avoid modifying the original. - string memory originalConfig = "./test/fixtures/config.toml"; - string memory testConfig = "./test/fixtures/config.t.toml"; - vm.copyFile(originalConfig, testConfig); - - // Deploy the config contract with the temporary fixture. - _loadConfig(testConfig); - - // Update a single boolean value and verify the change. - config.set(1, "is_live", false, true); - - assertFalse(config.getBool(1, "is_live")); - - string memory content = vm.readFile(testConfig); - assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live")); - - // Update a single address value and verify the change. - address new_addr = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; - config.set(1, "weth", new_addr, true); - - assertEq(config.getAddress(1, "weth"), new_addr); - - content = vm.readFile(testConfig); - assertEq(vm.parseTomlAddress(content, "$.mainnet.address.weth"), new_addr); - - // Update a uint array and verify the change. - uint256[] memory new_numbers = new uint256[](3); - new_numbers[0] = 1; - new_numbers[1] = 2; - new_numbers[2] = 3; - config.set(10, "number_array", new_numbers, true); - - uint256[] memory updated_numbers_mem = config.getUintArray(10, "number_array"); - assertEq(updated_numbers_mem.length, 3); - assertEq(updated_numbers_mem[0], 1); - assertEq(updated_numbers_mem[1], 2); - assertEq(updated_numbers_mem[2], 3); - - content = vm.readFile(testConfig); - uint256[] memory updated_numbers_disk = vm.parseTomlUintArray(content, "$.optimism.uint.number_array"); - assertEq(updated_numbers_disk.length, 3); - assertEq(updated_numbers_disk[0], 1); - assertEq(updated_numbers_disk[1], 2); - assertEq(updated_numbers_disk[2], 3); - - // Update a string array and verify the change. - string[] memory new_strings = new string[](2); - new_strings[0] = "hello"; - new_strings[1] = "world"; - config.set(1, "str_array", new_strings, true); - - string[] memory updated_strings_mem = config.getStringArray(1, "str_array"); - assertEq(updated_strings_mem.length, 2); - assertEq(updated_strings_mem[0], "hello"); - assertEq(updated_strings_mem[1], "world"); - - content = vm.readFile(testConfig); - string[] memory updated_strings_disk = vm.parseTomlStringArray(content, "$.mainnet.string.str_array"); - assertEq(updated_strings_disk.length, 2); - assertEq(updated_strings_disk[0], "hello"); - assertEq(updated_strings_disk[1], "world"); - - // Create a new uint variable and verify the change. - config.set(1, "new_uint", 42, true); - - assertEq(config.getUint(1, "new_uint"), 42); - - content = vm.readFile(testConfig); - assertEq(vm.parseTomlUint(content, "$.mainnet.uint.new_uint"), 42); - - // Create a new bytes32 array and verify the change. - bytes32[] memory new_words = new bytes32[](2); - new_words[0] = bytes32(uint256(0xDEAD)); - new_words[1] = bytes32(uint256(0xBEEF)); - config.set(10, "new_words", new_words, true); - - bytes32[] memory updated_words_mem = config.getBytes32Array(10, "new_words"); - assertEq(updated_words_mem.length, 2); - assertEq(updated_words_mem[0], new_words[0]); - assertEq(updated_words_mem[1], new_words[1]); - - content = vm.readFile(testConfig); - bytes32[] memory updated_words_disk = vm.parseTomlBytes32Array(content, "$.optimism.bytes32.new_words"); - assertEq(updated_words_disk.length, 2); - assertEq(vm.toString(updated_words_disk[0]), vm.toString(new_words[0])); - assertEq(vm.toString(updated_words_disk[1]), vm.toString(new_words[1])); - - // Clean up the temporary file. - vm.removeFile(testConfig); - } -} diff --git a/test/Config.t.sol b/test/Config.t.sol new file mode 100644 index 00000000..eb33ca05 --- /dev/null +++ b/test/Config.t.sol @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import {Test} from "../src/Test.sol"; +import {Config} from "../src/Config.sol"; +import {StdConfig} from "../src/StdConfig.sol"; +import {Variable, LibVariable} from "../src/LibVariable.sol"; + +contract ConfigTest is Test, Config { + function test_loadConfig() public { + // Deploy the config contract with the test fixture. + _loadConfig("./test/fixtures/config.toml"); + + // -- MAINNET -------------------------------------------------------------- + + // Read and assert RPC URL for Mainnet (chain ID 1) + assertEq(config.getRpcUrl(1), "https://eth.llamarpc.com"); + + // Read and assert boolean values + assertTrue(config.get(1, "is_live").toBool()); + bool[] memory bool_array = config.get(1, "bool_array").toBoolArray(); + assertTrue(bool_array[0]); + assertFalse(bool_array[1]); + + // Read and assert address values + assertEq(config.get(1, "weth").toAddress(), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address[] memory address_array = config.get(1, "deps").toAddressArray(); + assertEq(address_array[0], 0x0000000000000000000000000000000000000000); + assertEq(address_array[1], 0x1111111111111111111111111111111111111111); + + // Read and assert uint values + assertEq(config.get(1, "number").toUint(), 1234); + uint256[] memory uint_array = config.get(1, "number_array").toUintArray(); + assertEq(uint_array[0], 5678); + assertEq(uint_array[1], 9999); + + // Read and assert bytes32 values + assertEq(config.get(1, "word").toBytes32(), bytes32(uint256(1234))); + bytes32[] memory bytes32_array = config.get(1, "word_array").toBytes32Array(); + assertEq(bytes32_array[0], bytes32(uint256(5678))); + assertEq(bytes32_array[1], bytes32(uint256(9999))); + + // Read and assert bytes values + assertEq(config.get(1, "b").toBytes(), hex"abcd"); + bytes[] memory bytes_array = config.get(1, "b_array").toBytesArray(); + assertEq(bytes_array[0], hex"dead"); + assertEq(bytes_array[1], hex"beef"); + + // Read and assert string values + assertEq(config.get(1, "str").toString(), "foo"); + string[] memory string_array = config.get(1, "str_array").toStringArray(); + assertEq(string_array[0], "bar"); + assertEq(string_array[1], "baz"); + + // -- OPTIMISM ------------------------------------------------------------ + + // Read and assert RPC URL for Optimism (chain ID 10) + assertEq(config.getRpcUrl(10), "https://mainnet.optimism.io"); + + // Read and assert boolean values + assertFalse(config.get(10, "is_live").toBool()); + bool_array = config.get(10, "bool_array").toBoolArray(); + assertFalse(bool_array[0]); + assertTrue(bool_array[1]); + + // Read and assert address values + assertEq(config.get(10, "weth").toAddress(), 0x4200000000000000000000000000000000000006); + address_array = config.get(10, "deps").toAddressArray(); + assertEq(address_array[0], 0x2222222222222222222222222222222222222222); + assertEq(address_array[1], 0x3333333333333333333333333333333333333333); + + // Read and assert uint values + assertEq(config.get(10, "number").toUint(), 9999); + uint_array = config.get(10, "number_array").toUintArray(); + assertEq(uint_array[0], 1234); + assertEq(uint_array[1], 5678); + + // Read and assert bytes32 values + assertEq(config.get(10, "word").toBytes32(), bytes32(uint256(9999))); + bytes32_array = config.get(10, "word_array").toBytes32Array(); + assertEq(bytes32_array[0], bytes32(uint256(1234))); + assertEq(bytes32_array[1], bytes32(uint256(5678))); + + // Read and assert bytes values + assertEq(config.get(10, "b").toBytes(), hex"dcba"); + bytes_array = config.get(10, "b_array").toBytesArray(); + assertEq(bytes_array[0], hex"c0ffee"); + assertEq(bytes_array[1], hex"babe"); + + // Read and assert string values + assertEq(config.get(10, "str").toString(), "alice"); + string_array = config.get(10, "str_array").toStringArray(); + assertEq(string_array[0], "bob"); + assertEq(string_array[1], "charlie"); + } + + function test_loadConfigAndForks() public { + _loadConfigAndForks("./test/fixtures/config.toml"); + + // assert that the map of chain id and fork ids is created and that the chain ids actually match + assertEq(forkOf[1], 0); + vm.selectFork(forkOf[1]); + assertEq(vm.getChainId(), 1); + + assertEq(forkOf[10], 1); + vm.selectFork(forkOf[10]); + assertEq(vm.getChainId(), 10); + } + + function test_writeConfig() public { + // Create a temporary copy of the config file to avoid modifying the original. + string memory originalConfig = "./test/fixtures/config.toml"; + string memory testConfig = "./test/fixtures/config.t.toml"; + vm.copyFile(originalConfig, testConfig); + + // Deploy the config contract with the temporary fixture. + _loadConfig(testConfig); + + // Update a single boolean value and verify the change. + config.set(1, "is_live", false, true); + + assertFalse(config.get(1, "is_live").toBool()); + + string memory content = vm.readFile(testConfig); + assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live")); + + // Update a single address value and verify the change. + address new_addr = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; + config.set(1, "weth", new_addr, true); + + assertEq(config.get(1, "weth").toAddress(), new_addr); + + content = vm.readFile(testConfig); + assertEq(vm.parseTomlAddress(content, "$.mainnet.address.weth"), new_addr); + + // Update a uint array and verify the change. + uint256[] memory new_numbers = new uint256[](3); + new_numbers[0] = 1; + new_numbers[1] = 2; + new_numbers[2] = 3; + config.set(10, "number_array", new_numbers, true); + + uint256[] memory updated_numbers_mem = config.get(10, "number_array").toUintArray(); + assertEq(updated_numbers_mem.length, 3); + assertEq(updated_numbers_mem[0], 1); + assertEq(updated_numbers_mem[1], 2); + assertEq(updated_numbers_mem[2], 3); + + content = vm.readFile(testConfig); + uint256[] memory updated_numbers_disk = vm.parseTomlUintArray(content, "$.optimism.uint.number_array"); + assertEq(updated_numbers_disk.length, 3); + assertEq(updated_numbers_disk[0], 1); + assertEq(updated_numbers_disk[1], 2); + assertEq(updated_numbers_disk[2], 3); + + // Update a string array and verify the change. + string[] memory new_strings = new string[](2); + new_strings[0] = "hello"; + new_strings[1] = "world"; + config.set(1, "str_array", new_strings, true); + + string[] memory updated_strings_mem = config.get(1, "str_array").toStringArray(); + assertEq(updated_strings_mem.length, 2); + assertEq(updated_strings_mem[0], "hello"); + assertEq(updated_strings_mem[1], "world"); + + content = vm.readFile(testConfig); + string[] memory updated_strings_disk = vm.parseTomlStringArray(content, "$.mainnet.string.str_array"); + assertEq(updated_strings_disk.length, 2); + assertEq(updated_strings_disk[0], "hello"); + assertEq(updated_strings_disk[1], "world"); + + // Create a new uint variable and verify the change. + config.set(1, "new_uint", 42, true); + + assertEq(config.get(1, "new_uint").toUint(), 42); + + content = vm.readFile(testConfig); + assertEq(vm.parseTomlUint(content, "$.mainnet.uint.new_uint"), 42); + + // Create a new bytes32 array and verify the change. + bytes32[] memory new_words = new bytes32[](2); + new_words[0] = bytes32(uint256(0xDEAD)); + new_words[1] = bytes32(uint256(0xBEEF)); + config.set(10, "new_words", new_words, true); + + bytes32[] memory updated_words_mem = config.get(10, "new_words").toBytes32Array(); + assertEq(updated_words_mem.length, 2); + assertEq(updated_words_mem[0], new_words[0]); + assertEq(updated_words_mem[1], new_words[1]); + + content = vm.readFile(testConfig); + bytes32[] memory updated_words_disk = vm.parseTomlBytes32Array(content, "$.optimism.bytes32.new_words"); + assertEq(updated_words_disk.length, 2); + assertEq(vm.toString(updated_words_disk[0]), vm.toString(new_words[0])); + assertEq(vm.toString(updated_words_disk[1]), vm.toString(new_words[1])); + + // Clean up the temporary file. + vm.removeFile(testConfig); + } + + function testRevert_InvalidChainKey() public { + // Create a fixture with an invalid chain key + string memory invalidChainConfig = "./test/fixtures/config_invalid_chain.toml"; + vm.writeFile( + invalidChainConfig, + string( + abi.encodePacked( + "[mainnet]\n", + "endpoint_url = \"https://eth.llamarpc.com\"\n", + "\n", + "[mainnet.uint]\n", + "valid_number = 123\n", + "\n", + "# Invalid chain key (not a number and not a valid alias)\n", + "[invalid_chain]\n", + "endpoint_url = \"https://invalid.com\"\n", + "\n", + "[invalid_chain_9999.uint]\n", + "some_value = 456\n" + ) + ) + ); + + vm.expectRevert(abi.encodeWithSelector(StdConfig.InvalidChainKey.selector, "invalid_chain")); + new StdConfig(invalidChainConfig); + vm.removeFile(invalidChainConfig); + } + + function testRevert_ChainIdNotFound() public { + _loadConfig("./test/fixtures/config.toml"); + + // Try to write a value for a non-existent chain ID + vm.expectRevert(abi.encodeWithSelector(StdConfig.ChainIdNotFound.selector, uint256(999999))); + config.set(999999, "some_key", uint256(123), true); + } + + function testRevert_UnableToParseVariable() public { + // Create a fixture with an unparseable variable + string memory badParseConfig = "./test/fixtures/config_bad_parse.toml"; + vm.writeFile( + badParseConfig, + string( + abi.encodePacked( + "[mainnet]\n", + "endpoint_url = \"https://eth.llamarpc.com\"\n", + "\n", + "[mainnet.uint]\n", + "bad_value = \"not_a_number\"\n" + ) + ) + ); + + vm.expectRevert(abi.encodeWithSelector(StdConfig.UnableToParseVariable.selector, "bad_value")); + new StdConfig(badParseConfig); + vm.removeFile(badParseConfig); + } +} + +/// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, +/// as direct library calls are inlined by the compiler, causing call depth issues. +contract LibVariableTest is Test, Config { + LibVariableHelper helper; + + function setUp() public { + helper = new LibVariableHelper(); + _loadConfig("./test/fixtures/config.toml"); + } + + function testRevert_NotInitialized() public { + // Try to read a non-existent variable + Variable memory notInit = config.get(1, "non_existent_key"); + + // Test single value types - should revert with NotInitialized + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toBool(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toUint(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toAddress(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toBytes32(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toString(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toBytes(notInit); + + // Test array types - should also revert with NotInitialized + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toBoolArray(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toUintArray(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toAddressArray(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toBytes32Array(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toStringArray(notInit); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toBytesArray(notInit); + } + + function testRevert_TypeMismatch() public { + // Get a boolean variable + Variable memory boolVar = config.get(1, "is_live"); + + // Try to coerce it to wrong single value types - should revert with TypeMismatch + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool")); + helper.toUint(boolVar); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "bool")); + helper.toAddress(boolVar); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes32", "bool")); + helper.toBytes32(boolVar); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "string", "bool")); + helper.toString(boolVar); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes", "bool")); + helper.toBytes(boolVar); + + // Get a uint variable + Variable memory uintVar = config.get(1, "number"); + + // Try to coerce it to wrong types + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "uint256")); + helper.toBool(uintVar); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "uint256")); + helper.toAddress(uintVar); + + // Get an array variable + Variable memory boolArrayVar = config.get(1, "bool_array"); + + // Try to coerce array to single value - should revert with TypeMismatch + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "bool[]")); + helper.toBool(boolArrayVar); + + // Try to coerce array to wrong array type + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256[]", "bool[]")); + helper.toUintArray(boolArrayVar); + + // Get a single value and try to coerce to array + Variable memory singleBoolVar = config.get(1, "is_live"); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool[]", "bool")); + helper.toBoolArray(singleBoolVar); + } +} + +/// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, +/// as direct library calls are inlined by the compiler, causing call depth issues. +contract LibVariableHelper { + function toBool(Variable memory v) external pure returns (bool) { + return v.toBool(); + } + + function toUint(Variable memory v) external pure returns (uint256) { + return v.toUint(); + } + + function toAddress(Variable memory v) external pure returns (address) { + return v.toAddress(); + } + + function toBytes32(Variable memory v) external pure returns (bytes32) { + return v.toBytes32(); + } + + function toString(Variable memory v) external pure returns (string memory) { + return v.toString(); + } + + function toBytes(Variable memory v) external pure returns (bytes memory) { + return v.toBytes(); + } + + function toBoolArray(Variable memory v) external pure returns (bool[] memory) { + return v.toBoolArray(); + } + + function toUintArray(Variable memory v) external pure returns (uint256[] memory) { + return v.toUintArray(); + } + + function toAddressArray(Variable memory v) external pure returns (address[] memory) { + return v.toAddressArray(); + } + + function toBytes32Array(Variable memory v) external pure returns (bytes32[] memory) { + return v.toBytes32Array(); + } + + function toStringArray(Variable memory v) external pure returns (string[] memory) { + return v.toStringArray(); + } + + function toBytesArray(Variable memory v) external pure returns (bytes[] memory) { + return v.toBytesArray(); + } +} From c4122e29baaf11c271dd3f32a1060b87cecef19e Mon Sep 17 00:00:00 2001 From: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Date: Tue, 26 Aug 2025 17:27:14 +0200 Subject: [PATCH 26/54] backwards-compatibility: drop custom errors + global 'using for' (#717) Merging into https://github.com/foundry-rs/forge-std/pull/715 --- src/Config.sol | 4 ++- src/LibVariable.sol | 20 ++++++++++---- src/StdConfig.sol | 15 +++------- test/Config.t.sol | 67 +++++++++++++++++++++++++-------------------- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/Config.sol b/src/Config.sol index d06bbeaa..855ba96f 100644 --- a/src/Config.sol +++ b/src/Config.sol @@ -3,11 +3,13 @@ pragma solidity >=0.6.2 <0.9.0; pragma experimental ABIEncoderV2; import {console} from "./console.sol"; -import {StdConfig} from "./StdConfig.sol"; import {CommonBase} from "./Base.sol"; +import {StdConfig, Variable, LibVariable} from "./StdConfig.sol"; /// @notice Boilerplate to streamline the setup of multi-chain environments. abstract contract Config is CommonBase { + using LibVariable for Variable; + // -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------ /// @notice Contract instance holding the data from the TOML config file. diff --git a/src/LibVariable.sol b/src/LibVariable.sol index 646a865c..82b6d6ee 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; -using LibVariable for Variable global; - struct Variable { Type ty; bytes data; @@ -51,8 +49,6 @@ enum TypeKind { /// } /// ``` library LibVariable { - error NotInitialized(); - error TypeMismatch(string expected, string actual); // -- TYPE HELPERS ---------------------------------------------------- @@ -64,7 +60,7 @@ library LibVariable { /// @notice Compares two Type instances for equality. Reverts if they are not equal. function assertEq(Type memory self, Type memory other) internal pure { if (!isEqual(self, other)) { - revert TypeMismatch(toString(other), toString(self)); + revert(_concat("type mismatch: expected '", toString(other), _concat("', got '", toString(self), "'"))); } } @@ -112,7 +108,7 @@ library LibVariable { /// @dev Checks if a `Variable` has been initialized, reverting if not. function assertExists(Variable memory self) public pure { if (self.ty.kind == TypeKind.None) { - revert NotInitialized(); + revert("variable not initialized"); } } @@ -229,4 +225,16 @@ library LibVariable { { return abi.decode(self.data, (bytes[])); } + + // -- UTILITY HELPERS ------------------------------------------------------ + + /// @dev concatenates two strings + function _concat(string memory s1, string memory s2) private pure returns (string memory) { + return string(abi.encodePacked(s1, s2)); + } + + /// @dev concatenates three strings + function _concat(string memory s1, string memory s2, string memory s3) private pure returns (string memory) { + return string(abi.encodePacked(s1, s2, s3)); + } } diff --git a/src/StdConfig.sol b/src/StdConfig.sol index fef5158b..1592afcd 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -39,13 +39,6 @@ contract StdConfig { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); uint8 private constant NUM_TYPES = 7; - // -- ERRORS --------------------------------------------------------------- - - error AlreadyInitialized(string key); - error InvalidChainKey(string aliasOrId); - error ChainIdNotFound(uint256 chainId); - error UnableToParseVariable(string key); - // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ /// @notice Path to the loaded TOML configuration file. @@ -112,7 +105,7 @@ contract StdConfig { if (_typeOf[chainId][key].kind == TypeKind.None) { _loadAndCacheValue(content, _concat(typePath, ".", key), chainId, key, ty); } else { - revert AlreadyInitialized(key); + revert(_concat("already initialized: '", key, "'")); } } } catch {} // Section does not exist, ignore. @@ -203,7 +196,7 @@ contract StdConfig { } if (!success) { - revert UnableToParseVariable(key); + revert(_concat("unable to parse variable: '", key, "'")); } } @@ -226,7 +219,7 @@ contract StdConfig { try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) { return chainInfo.chainId; } catch { - revert InvalidChainKey(aliasOrId); + revert(_concat("invalid chain key: '", aliasOrId, "' is not a valid alias nor a number")); } } } @@ -238,7 +231,7 @@ contract StdConfig { return _chainKeys[i]; } } - revert ChainIdNotFound(chainId); + revert(_concat("chain id: '", vm.toString(chainId), "' not found in configuration")); } /// @dev Ensures type consistency when setting a value - prevents changing types unless uninitialized. diff --git a/test/Config.t.sol b/test/Config.t.sol index eb33ca05..819552eb 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -3,11 +3,11 @@ pragma solidity >=0.6.2 <0.9.0; pragma experimental ABIEncoderV2; import {Test} from "../src/Test.sol"; -import {Config} from "../src/Config.sol"; -import {StdConfig} from "../src/StdConfig.sol"; -import {Variable, LibVariable} from "../src/LibVariable.sol"; +import {Config, StdConfig, Variable, LibVariable} from "../src/Config.sol"; contract ConfigTest is Test, Config { + using LibVariable for Variable; + function test_loadConfig() public { // Deploy the config contract with the test fixture. _loadConfig("./test/fixtures/config.toml"); @@ -201,7 +201,7 @@ contract ConfigTest is Test, Config { } function testRevert_InvalidChainKey() public { - // Create a fixture with an invalid chain key + // Create a temporary fixture with an invalid chain key string memory invalidChainConfig = "./test/fixtures/config_invalid_chain.toml"; vm.writeFile( invalidChainConfig, @@ -223,8 +223,10 @@ contract ConfigTest is Test, Config { ) ); - vm.expectRevert(abi.encodeWithSelector(StdConfig.InvalidChainKey.selector, "invalid_chain")); + vm.expectRevert("invalid chain key: 'invalid_chain' is not a valid alias nor a number"); new StdConfig(invalidChainConfig); + + // Clean up the temporary file. vm.removeFile(invalidChainConfig); } @@ -232,12 +234,12 @@ contract ConfigTest is Test, Config { _loadConfig("./test/fixtures/config.toml"); // Try to write a value for a non-existent chain ID - vm.expectRevert(abi.encodeWithSelector(StdConfig.ChainIdNotFound.selector, uint256(999999))); + vm.expectRevert("chain id: '999999' not found in configuration"); config.set(999999, "some_key", uint256(123), true); } function testRevert_UnableToParseVariable() public { - // Create a fixture with an unparseable variable + // Create a temprorary fixture with an unparseable variable string memory badParseConfig = "./test/fixtures/config_bad_parse.toml"; vm.writeFile( badParseConfig, @@ -252,8 +254,10 @@ contract ConfigTest is Test, Config { ) ); - vm.expectRevert(abi.encodeWithSelector(StdConfig.UnableToParseVariable.selector, "bad_value")); + vm.expectRevert("unable to parse variable: 'bad_value'"); new StdConfig(badParseConfig); + + // Clean up the temporary file. vm.removeFile(badParseConfig); } } @@ -261,6 +265,7 @@ contract ConfigTest is Test, Config { /// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, /// as direct library calls are inlined by the compiler, causing call depth issues. contract LibVariableTest is Test, Config { + using LibVariable for Variable; LibVariableHelper helper; function setUp() public { @@ -273,41 +278,41 @@ contract LibVariableTest is Test, Config { Variable memory notInit = config.get(1, "non_existent_key"); // Test single value types - should revert with NotInitialized - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toBool(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toUint(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toAddress(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toBytes32(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toString(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toBytes(notInit); // Test array types - should also revert with NotInitialized - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toBoolArray(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toUintArray(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toAddressArray(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toBytes32Array(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toStringArray(notInit); - vm.expectRevert(LibVariable.NotInitialized.selector); + vm.expectRevert("variable not initialized"); helper.toBytesArray(notInit); } @@ -316,46 +321,46 @@ contract LibVariableTest is Test, Config { Variable memory boolVar = config.get(1, "is_live"); // Try to coerce it to wrong single value types - should revert with TypeMismatch - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool")); + vm.expectRevert("type mismatch: expected 'uint256', got 'bool'"); helper.toUint(boolVar); - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "bool")); + vm.expectRevert("type mismatch: expected 'address', got 'bool'"); helper.toAddress(boolVar); - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes32", "bool")); + vm.expectRevert("type mismatch: expected 'bytes32', got 'bool'"); helper.toBytes32(boolVar); - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "string", "bool")); + vm.expectRevert("type mismatch: expected 'string', got 'bool'"); helper.toString(boolVar); - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes", "bool")); + vm.expectRevert("type mismatch: expected 'bytes', got 'bool'"); helper.toBytes(boolVar); // Get a uint variable Variable memory uintVar = config.get(1, "number"); // Try to coerce it to wrong types - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "uint256")); + vm.expectRevert("type mismatch: expected 'bool', got 'uint256'"); helper.toBool(uintVar); - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "uint256")); + vm.expectRevert("type mismatch: expected 'address', got 'uint256'"); helper.toAddress(uintVar); // Get an array variable Variable memory boolArrayVar = config.get(1, "bool_array"); // Try to coerce array to single value - should revert with TypeMismatch - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "bool[]")); + vm.expectRevert("type mismatch: expected 'bool', got 'bool[]'"); helper.toBool(boolArrayVar); // Try to coerce array to wrong array type - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256[]", "bool[]")); + vm.expectRevert("type mismatch: expected 'uint256[]', got 'bool[]'"); helper.toUintArray(boolArrayVar); // Get a single value and try to coerce to array Variable memory singleBoolVar = config.get(1, "is_live"); - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool[]", "bool")); + vm.expectRevert("type mismatch: expected 'bool[]', got 'bool'"); helper.toBoolArray(singleBoolVar); } } @@ -363,6 +368,8 @@ contract LibVariableTest is Test, Config { /// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, /// as direct library calls are inlined by the compiler, causing call depth issues. contract LibVariableHelper { + using LibVariable for Variable; + function toBool(Variable memory v) external pure returns (bool) { return v.toBool(); } From 61431b0731973a7b5a1d9d121addfadd9ac87064 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 17:28:29 +0200 Subject: [PATCH 27/54] missing experimental abi --- src/LibVariable.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LibVariable.sol b/src/LibVariable.sol index 82b6d6ee..a13b8ad4 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; struct Variable { Type ty; From 2a386d9a61ee68016e530094f03ba419233b4f5f Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 17:30:51 +0200 Subject: [PATCH 28/54] style: fmt + typos --- src/LibVariable.sol | 1 - src/StdConfig.sol | 12 ++++++------ test/Config.t.sol | 3 ++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/LibVariable.sol b/src/LibVariable.sol index a13b8ad4..e3afdee6 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -50,7 +50,6 @@ enum TypeKind { /// } /// ``` library LibVariable { - // -- TYPE HELPERS ---------------------------------------------------- /// @notice Compares two Type instances for equality. diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 1592afcd..c9e79687 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -41,22 +41,22 @@ contract StdConfig { // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ - /// @notice Path to the loaded TOML configuration file. + /// @dev Path to the loaded TOML configuration file. string private _filePath; - /// @notice List of top-level keys found in the TOML file, assumed to be chain names/aliases. + /// @dev List of top-level keys found in the TOML file, assumed to be chain names/aliases. string[] private _chainKeys; - /// @notice Storage for the configured RPC URL for each chain. + /// @dev Storage for the configured RPC URL for each chain. mapping(uint256 => string) private _rpcOf; - /// @notice Storage for values, organized by chain ID and variable key. + /// @dev Storage for values, organized by chain ID and variable key. mapping(uint256 => mapping(string => bytes)) private _dataOf; - /// @notice Type cache for runtime checking when casting. + /// @dev Type cache for runtime checking when casting. mapping(uint256 => mapping(string => Type)) private _typeOf; - /// @notice When enabled, `set` will always write updates back to the configuration file. + /// @dev When enabled, `set` will always write updates back to the configuration file. bool private _autoWrite; // -- CONSTRUCTOR ---------------------------------------------------------- diff --git a/test/Config.t.sol b/test/Config.t.sol index 819552eb..73accdc1 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -239,7 +239,7 @@ contract ConfigTest is Test, Config { } function testRevert_UnableToParseVariable() public { - // Create a temprorary fixture with an unparseable variable + // Create a temporary fixture with an unparsable variable string memory badParseConfig = "./test/fixtures/config_bad_parse.toml"; vm.writeFile( badParseConfig, @@ -266,6 +266,7 @@ contract ConfigTest is Test, Config { /// as direct library calls are inlined by the compiler, causing call depth issues. contract LibVariableTest is Test, Config { using LibVariable for Variable; + LibVariableHelper helper; function setUp() public { From d3b49b4aca2dce51911087743b7bf392341fd41e Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 17:31:46 +0200 Subject: [PATCH 29/54] more style --- src/Config.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Config.sol b/src/Config.sol index 855ba96f..0caeb75e 100644 --- a/src/Config.sol +++ b/src/Config.sol @@ -12,13 +12,13 @@ abstract contract Config is CommonBase { // -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------ - /// @notice Contract instance holding the data from the TOML config file. + /// @dev Contract instance holding the data from the TOML config file. StdConfig internal config; - /// @notice Array of chain IDs for which forks have been created. + /// @dev Array of chain IDs for which forks have been created. uint256[] internal chainIds; - /// @notice A mapping from a chain ID to its initialized fork ID. + /// @dev A mapping from a chain ID to its initialized fork ID. mapping(uint256 => uint256) internal forkOf; // -- HELPER FUNCTIONS ----------------------------------------------------- From 3c91387385f3ac8389dfc5630c1d162fd7049164 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 17:35:16 +0200 Subject: [PATCH 30/54] ci: exclude `test/Config.t.sol` for stable --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7a547c2..6643909e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,12 @@ jobs: with: version: ${{ matrix.toolchain }} - run: forge --version - - run: forge test -vvv + - run: | + if [ "${{ matrix.toolchain }}" = "stable" ]; then + forge test -vvv --no-match-path "test/Config.t.sol" + else + forge test -vvv + fi fmt: runs-on: ubuntu-latest From 8a264ef4141e6bfb7571f7467c9af4d13a8de106 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 17:44:58 +0200 Subject: [PATCH 31/54] update Vm.t.sol with new hash --- 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 bca885ee..af17736f 100644 --- a/test/Vm.t.sol +++ b/test/Vm.t.sol @@ -13,6 +13,6 @@ contract VmTest is Test { } function test_VmSafeInterfaceId() public pure { - assertEq(type(VmSafe).interfaceId, bytes4(0xdcd933d4), "VmSafe"); + assertEq(type(VmSafe).interfaceId, bytes4(0x42e95499), "VmSafe"); } } From a5d3fc0aeb6716034aa2d9520772eef24430e7d8 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 18:24:43 +0200 Subject: [PATCH 32/54] Revert "backwards-compatibility: drop custom errors + global 'using for' (#717)" This reverts commit c4122e29baaf11c271dd3f32a1060b87cecef19e. --- src/Config.sol | 7 ++--- src/LibVariable.sol | 25 ++++++---------- src/StdConfig.sol | 18 ++++++++---- test/Config.t.sol | 69 ++++++++++++++++++++------------------------- 4 files changed, 53 insertions(+), 66 deletions(-) diff --git a/src/Config.sol b/src/Config.sol index 0caeb75e..9cbdb0b4 100644 --- a/src/Config.sol +++ b/src/Config.sol @@ -1,15 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; -pragma experimental ABIEncoderV2; +pragma solidity ^0.8.13; import {console} from "./console.sol"; +import {StdConfig} from "./StdConfig.sol"; import {CommonBase} from "./Base.sol"; -import {StdConfig, Variable, LibVariable} from "./StdConfig.sol"; /// @notice Boilerplate to streamline the setup of multi-chain environments. abstract contract Config is CommonBase { - using LibVariable for Variable; - // -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------ /// @dev Contract instance holding the data from the TOML config file. diff --git a/src/LibVariable.sol b/src/LibVariable.sol index e3afdee6..9b0fd94b 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; -pragma experimental ABIEncoderV2; +pragma solidity ^0.8.13; + +// Enable globaly. +using LibVariable for Variable global; struct Variable { Type ty; @@ -50,6 +52,9 @@ enum TypeKind { /// } /// ``` library LibVariable { + error NotInitialized(); + error TypeMismatch(string expected, string actual); + // -- TYPE HELPERS ---------------------------------------------------- /// @notice Compares two Type instances for equality. @@ -60,7 +65,7 @@ library LibVariable { /// @notice Compares two Type instances for equality. Reverts if they are not equal. function assertEq(Type memory self, Type memory other) internal pure { if (!isEqual(self, other)) { - revert(_concat("type mismatch: expected '", toString(other), _concat("', got '", toString(self), "'"))); + revert TypeMismatch(toString(other), toString(self)); } } @@ -108,7 +113,7 @@ library LibVariable { /// @dev Checks if a `Variable` has been initialized, reverting if not. function assertExists(Variable memory self) public pure { if (self.ty.kind == TypeKind.None) { - revert("variable not initialized"); + revert NotInitialized(); } } @@ -225,16 +230,4 @@ library LibVariable { { return abi.decode(self.data, (bytes[])); } - - // -- UTILITY HELPERS ------------------------------------------------------ - - /// @dev concatenates two strings - function _concat(string memory s1, string memory s2) private pure returns (string memory) { - return string(abi.encodePacked(s1, s2)); - } - - /// @dev concatenates three strings - function _concat(string memory s1, string memory s2, string memory s3) private pure returns (string memory) { - return string(abi.encodePacked(s1, s2, s3)); - } } diff --git a/src/StdConfig.sol b/src/StdConfig.sol index c9e79687..b9db1fde 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; -pragma experimental ABIEncoderV2; +pragma solidity ^0.8.13; import {VmSafe} from "./Vm.sol"; import {Variable, Type, TypeKind, LibVariable} from "./LibVariable.sol"; @@ -39,6 +38,13 @@ contract StdConfig { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); uint8 private constant NUM_TYPES = 7; + // -- ERRORS --------------------------------------------------------------- + + error AlreadyInitialized(string key); + error InvalidChainKey(string aliasOrId); + error ChainIdNotFound(uint256 chainId); + error UnableToParseVariable(string key); + // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ /// @dev Path to the loaded TOML configuration file. @@ -105,7 +111,7 @@ contract StdConfig { if (_typeOf[chainId][key].kind == TypeKind.None) { _loadAndCacheValue(content, _concat(typePath, ".", key), chainId, key, ty); } else { - revert(_concat("already initialized: '", key, "'")); + revert AlreadyInitialized(key); } } } catch {} // Section does not exist, ignore. @@ -196,7 +202,7 @@ contract StdConfig { } if (!success) { - revert(_concat("unable to parse variable: '", key, "'")); + revert UnableToParseVariable(key); } } @@ -219,7 +225,7 @@ contract StdConfig { try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) { return chainInfo.chainId; } catch { - revert(_concat("invalid chain key: '", aliasOrId, "' is not a valid alias nor a number")); + revert InvalidChainKey(aliasOrId); } } } @@ -231,7 +237,7 @@ contract StdConfig { return _chainKeys[i]; } } - revert(_concat("chain id: '", vm.toString(chainId), "' not found in configuration")); + revert ChainIdNotFound(chainId); } /// @dev Ensures type consistency when setting a value - prevents changing types unless uninitialized. diff --git a/test/Config.t.sol b/test/Config.t.sol index 73accdc1..05dce089 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; -pragma experimental ABIEncoderV2; +pragma solidity ^0.8.13; import {Test} from "../src/Test.sol"; -import {Config, StdConfig, Variable, LibVariable} from "../src/Config.sol"; +import {Config} from "../src/Config.sol"; +import {StdConfig} from "../src/StdConfig.sol"; +import {Variable, LibVariable} from "../src/LibVariable.sol"; contract ConfigTest is Test, Config { - using LibVariable for Variable; - function test_loadConfig() public { // Deploy the config contract with the test fixture. _loadConfig("./test/fixtures/config.toml"); @@ -201,7 +200,7 @@ contract ConfigTest is Test, Config { } function testRevert_InvalidChainKey() public { - // Create a temporary fixture with an invalid chain key + // Create a fixture with an invalid chain key string memory invalidChainConfig = "./test/fixtures/config_invalid_chain.toml"; vm.writeFile( invalidChainConfig, @@ -223,10 +222,8 @@ contract ConfigTest is Test, Config { ) ); - vm.expectRevert("invalid chain key: 'invalid_chain' is not a valid alias nor a number"); + vm.expectRevert(abi.encodeWithSelector(StdConfig.InvalidChainKey.selector, "invalid_chain")); new StdConfig(invalidChainConfig); - - // Clean up the temporary file. vm.removeFile(invalidChainConfig); } @@ -234,7 +231,7 @@ contract ConfigTest is Test, Config { _loadConfig("./test/fixtures/config.toml"); // Try to write a value for a non-existent chain ID - vm.expectRevert("chain id: '999999' not found in configuration"); + vm.expectRevert(abi.encodeWithSelector(StdConfig.ChainIdNotFound.selector, uint256(999999))); config.set(999999, "some_key", uint256(123), true); } @@ -254,10 +251,8 @@ contract ConfigTest is Test, Config { ) ); - vm.expectRevert("unable to parse variable: 'bad_value'"); + vm.expectRevert(abi.encodeWithSelector(StdConfig.UnableToParseVariable.selector, "bad_value")); new StdConfig(badParseConfig); - - // Clean up the temporary file. vm.removeFile(badParseConfig); } } @@ -265,8 +260,6 @@ contract ConfigTest is Test, Config { /// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, /// as direct library calls are inlined by the compiler, causing call depth issues. contract LibVariableTest is Test, Config { - using LibVariable for Variable; - LibVariableHelper helper; function setUp() public { @@ -279,41 +272,41 @@ contract LibVariableTest is Test, Config { Variable memory notInit = config.get(1, "non_existent_key"); // Test single value types - should revert with NotInitialized - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toBool(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toUint(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toAddress(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toBytes32(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toString(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toBytes(notInit); // Test array types - should also revert with NotInitialized - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toBoolArray(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toUintArray(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toAddressArray(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toBytes32Array(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toStringArray(notInit); - vm.expectRevert("variable not initialized"); + vm.expectRevert(LibVariable.NotInitialized.selector); helper.toBytesArray(notInit); } @@ -322,46 +315,46 @@ contract LibVariableTest is Test, Config { Variable memory boolVar = config.get(1, "is_live"); // Try to coerce it to wrong single value types - should revert with TypeMismatch - vm.expectRevert("type mismatch: expected 'uint256', got 'bool'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool")); helper.toUint(boolVar); - vm.expectRevert("type mismatch: expected 'address', got 'bool'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "bool")); helper.toAddress(boolVar); - vm.expectRevert("type mismatch: expected 'bytes32', got 'bool'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes32", "bool")); helper.toBytes32(boolVar); - vm.expectRevert("type mismatch: expected 'string', got 'bool'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "string", "bool")); helper.toString(boolVar); - vm.expectRevert("type mismatch: expected 'bytes', got 'bool'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes", "bool")); helper.toBytes(boolVar); // Get a uint variable Variable memory uintVar = config.get(1, "number"); // Try to coerce it to wrong types - vm.expectRevert("type mismatch: expected 'bool', got 'uint256'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "uint256")); helper.toBool(uintVar); - vm.expectRevert("type mismatch: expected 'address', got 'uint256'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "uint256")); helper.toAddress(uintVar); // Get an array variable Variable memory boolArrayVar = config.get(1, "bool_array"); // Try to coerce array to single value - should revert with TypeMismatch - vm.expectRevert("type mismatch: expected 'bool', got 'bool[]'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "bool[]")); helper.toBool(boolArrayVar); // Try to coerce array to wrong array type - vm.expectRevert("type mismatch: expected 'uint256[]', got 'bool[]'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256[]", "bool[]")); helper.toUintArray(boolArrayVar); // Get a single value and try to coerce to array Variable memory singleBoolVar = config.get(1, "is_live"); - vm.expectRevert("type mismatch: expected 'bool[]', got 'bool'"); + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool[]", "bool")); helper.toBoolArray(singleBoolVar); } } @@ -369,8 +362,6 @@ contract LibVariableTest is Test, Config { /// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, /// as direct library calls are inlined by the compiler, causing call depth issues. contract LibVariableHelper { - using LibVariable for Variable; - function toBool(Variable memory v) external pure returns (bool) { return v.toBool(); } From 21f844d2550169101c6ee4a70e1c0f8c0dfb6305 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 18:29:31 +0200 Subject: [PATCH 33/54] CI: skip config-related contracts in old versions --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6643909e..4fca26e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,12 @@ jobs: - uses: actions/checkout@v4 - uses: foundry-rs/foundry-toolchain@v1 - run: forge --version - - run: forge build --skip test --deny-warnings ${{ matrix.flags }} + - run: | + if [[ "${{ matrix.flags }}" =~ "solc:0\.(6|7|8\.([0-9]|1[0-2]))" ]]; then + forge build --skip test/Config.t.sol --skip src/StdConfig.sol --skip src/Config.sol --skip src/LibVariable.sol --deny-warnings ${{ matrix.flags }} + else + forge build --skip test --deny-warnings ${{ matrix.flags }} + fi # via-ir compilation time checks. - if: contains(matrix.flags, '--via-ir') run: forge build --skip test --deny-warnings ${{ matrix.flags }} --contracts 'test/compilation/*' From 3c23f7307430b49adc5e530e55ee9674cdb23484 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 18:29:58 +0200 Subject: [PATCH 34/54] ci: always skip test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fca26e0..b37b0493 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: - run: forge --version - run: | if [[ "${{ matrix.flags }}" =~ "solc:0\.(6|7|8\.([0-9]|1[0-2]))" ]]; then - forge build --skip test/Config.t.sol --skip src/StdConfig.sol --skip src/Config.sol --skip src/LibVariable.sol --deny-warnings ${{ matrix.flags }} + forge build --skip test --skip src/StdConfig.sol --skip src/Config.sol --skip src/LibVariable.sol --deny-warnings ${{ matrix.flags }} else forge build --skip test --deny-warnings ${{ matrix.flags }} fi From 1465e12dc5e5d1542773f7a304119c5bd2943acc Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 18:36:07 +0200 Subject: [PATCH 35/54] fix: ci --- .github/workflows/ci.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b37b0493..8a4281ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,11 +31,14 @@ jobs: - uses: foundry-rs/foundry-toolchain@v1 - run: forge --version - run: | - if [[ "${{ matrix.flags }}" =~ "solc:0\.(6|7|8\.([0-9]|1[0-2]))" ]]; then - forge build --skip test --skip src/StdConfig.sol --skip src/Config.sol --skip src/LibVariable.sol --deny-warnings ${{ matrix.flags }} - else - forge build --skip test --deny-warnings ${{ matrix.flags }} - fi + case "${{ matrix.flags }}" in + *"solc:0.8.0"* | *"solc:0.7"* | *"solc:0.6"*) + forge build --skip test --skip Config --skip StdConfig --skip LibVariable --deny-warnings ${{ matrix.flags }} + ;; + *) + forge build --skip test --deny-warnings ${{ matrix.flags }} + ;; + esac # via-ir compilation time checks. - if: contains(matrix.flags, '--via-ir') run: forge build --skip test --deny-warnings ${{ matrix.flags }} --contracts 'test/compilation/*' From 8fa3ec598330ec5a68fe52acae8fb677a596668d Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 18:37:09 +0200 Subject: [PATCH 36/54] fix: typo --- src/LibVariable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LibVariable.sol b/src/LibVariable.sol index 9b0fd94b..d08fbb3a 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -// Enable globaly. +// Enable globally. using LibVariable for Variable global; struct Variable { From e178cd01fab470fef884ae3905215f5ed08a1fd4 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 26 Aug 2025 18:58:13 +0200 Subject: [PATCH 37/54] set `writeToFile` on constructor --- src/Config.sol | 8 +-- src/StdConfig.sol | 155 ++++++++++++++++++++++--------------------- test/Config.t.sol | 35 +++++----- test/StdChains.t.sol | 2 +- 4 files changed, 102 insertions(+), 98 deletions(-) diff --git a/src/Config.sol b/src/Config.sol index 9cbdb0b4..c39f65ba 100644 --- a/src/Config.sol +++ b/src/Config.sol @@ -25,10 +25,10 @@ abstract contract Config is CommonBase { /// @dev This function instantiates a `Config` contract, caching all its config variables. /// /// @param filePath: the path to the TOML configuration file. - function _loadConfig(string memory filePath) internal { + function _loadConfig(string memory filePath, bool writeToFile) internal { console.log("----------"); console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); - config = new StdConfig(filePath); + config = new StdConfig(filePath, writeToFile); vm.makePersistent(address(config)); console.log("Config successfully loaded"); console.log("----------"); @@ -41,8 +41,8 @@ abstract contract Config is CommonBase { /// It also creates a map `forkOf[chainId] -> forkId` to easily switch between forks. /// /// @param filePath: the path to the TOML configuration file. - function _loadConfigAndForks(string memory filePath) internal { - _loadConfig(filePath); + function _loadConfigAndForks(string memory filePath, bool writeToFile) internal { + _loadConfig(filePath, writeToFile); console.log("Setting up forks for the configured chains..."); uint256[] memory chains = config.getChainIds(); diff --git a/src/StdConfig.sol b/src/StdConfig.sol index b9db1fde..e423d2f4 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -42,7 +42,7 @@ contract StdConfig { error AlreadyInitialized(string key); error InvalidChainKey(string aliasOrId); - error ChainIdNotFound(uint256 chainId); + error ChainNotInitialized(uint256 chainId); error UnableToParseVariable(string key); // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ @@ -63,7 +63,7 @@ contract StdConfig { mapping(uint256 => mapping(string => Type)) private _typeOf; /// @dev When enabled, `set` will always write updates back to the configuration file. - bool private _autoWrite; + bool private _writeToFile; // -- CONSTRUCTOR ---------------------------------------------------------- @@ -77,8 +77,9 @@ contract StdConfig { /// parsed as either, the constructor will revert with an error. /// /// @param configFilePath: The local path to the TOML configuration file. - constructor(string memory configFilePath) public { + constructor(string memory configFilePath, bool writeToFile) { _filePath = configFilePath; + _writeToFile = writeToFile; string memory content = vm.resolveEnv(vm.readFile(configFilePath)); string[] memory chain_keys = vm.parseTomlKeys(content, "$"); @@ -210,7 +211,7 @@ contract StdConfig { /// @notice Enable or disable automatic writing to the TOML file on `set`. function setAutoWrite(bool enabled) public { - _autoWrite = enabled; + _writeToFile = enabled; } /// @notice Resolves a chain alias or a chain id string to its numerical chain id. @@ -237,7 +238,7 @@ contract StdConfig { return _chainKeys[i]; } } - revert ChainIdNotFound(chainId); + revert ChainNotInitialized(chainId); } /// @dev Ensures type consistency when setting a value - prevents changing types unless uninitialized. @@ -335,102 +336,102 @@ contract StdConfig { // -- SETTER FUNCTIONS ----------------------------------------------------- /// @notice Sets a boolean value for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, bool value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, bool value) public { Type memory ty = Type(TypeKind.Bool, false); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); } /// @notice Sets a boolean value for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, bool value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, bool value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a uint256 value for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, uint256 value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, uint256 value) public { Type memory ty = Type(TypeKind.Uint256, false); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); } /// @notice Sets a uint256 value for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, uint256 value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, uint256 value) public { + set(vm.getChainId(), key, value); } /// @notice Sets an address value for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, address value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, address value) public { Type memory ty = Type(TypeKind.Address, false); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); } /// @notice Sets an address value for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, address value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, address value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a bytes32 value for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, bytes32 value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, bytes32 value) public { Type memory ty = Type(TypeKind.Bytes32, false); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); } /// @notice Sets a bytes32 value for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, bytes32 value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, bytes32 value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a string value for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, string memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, string memory value) public { Type memory ty = Type(TypeKind.String, false); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(value)); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(value)); } /// @notice Sets a string value for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, string memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, string memory value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a bytes value for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, bytes memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, bytes memory value) public { Type memory ty = Type(TypeKind.Bytes, false); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, _quote(vm.toString(value))); } /// @notice Sets a bytes value for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, bytes memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, bytes memory value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a boolean array for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, bool[] memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, bool[] memory value) public { Type memory ty = Type(TypeKind.Bool, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) { + if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, vm.toString(value[i])); @@ -442,18 +443,18 @@ contract StdConfig { } /// @notice Sets a boolean array for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, bool[] memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, bool[] memory value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a uint256 array for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, uint256[] memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, uint256[] memory value) public { Type memory ty = Type(TypeKind.Uint256, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) { + if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, vm.toString(value[i])); @@ -465,18 +466,18 @@ contract StdConfig { } /// @notice Sets a uint256 array for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, uint256[] memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, uint256[] memory value) public { + set(vm.getChainId(), key, value); } /// @notice Sets an address array for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, address[] memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, address[] memory value) public { Type memory ty = Type(TypeKind.Address, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) { + if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); @@ -488,18 +489,18 @@ contract StdConfig { } /// @notice Sets an address array for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, address[] memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, address[] memory value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a bytes32 array for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, bytes32[] memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, bytes32[] memory value) public { Type memory ty = Type(TypeKind.Bytes32, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) { + if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); @@ -511,18 +512,18 @@ contract StdConfig { } /// @notice Sets a bytes32 array for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, bytes32[] memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, bytes32[] memory value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a string array for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, string[] memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, string[] memory value) public { Type memory ty = Type(TypeKind.String, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) { + if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(value[i])); @@ -534,18 +535,18 @@ contract StdConfig { } /// @notice Sets a string array for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, string[] memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, string[] memory value) public { + set(vm.getChainId(), key, value); } /// @notice Sets a bytes array for a given key and chain ID. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(uint256 chainId, string memory key, bytes[] memory value, bool write) public { + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(uint256 chainId, string memory key, bytes[] memory value) public { Type memory ty = Type(TypeKind.Bytes, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); - if (write || _autoWrite) { + if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { json = _concat(json, _quote(vm.toString(value[i]))); @@ -557,8 +558,8 @@ contract StdConfig { } /// @notice Sets a bytes array for a given key on the current chain. - /// @dev Sets the cached value in storage and optionally writes the change back to the TOML file. - function set(string memory key, bytes[] memory value, bool write) public { - set(vm.getChainId(), key, value, write); + /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + function set(string memory key, bytes[] memory value) public { + set(vm.getChainId(), key, value); } } diff --git a/test/Config.t.sol b/test/Config.t.sol index 05dce089..839dce57 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -9,7 +9,7 @@ import {Variable, LibVariable} from "../src/LibVariable.sol"; contract ConfigTest is Test, Config { function test_loadConfig() public { // Deploy the config contract with the test fixture. - _loadConfig("./test/fixtures/config.toml"); + _loadConfig("./test/fixtures/config.toml", false); // -- MAINNET -------------------------------------------------------------- @@ -95,7 +95,7 @@ contract ConfigTest is Test, Config { } function test_loadConfigAndForks() public { - _loadConfigAndForks("./test/fixtures/config.toml"); + _loadConfigAndForks("./test/fixtures/config.toml", false); // assert that the map of chain id and fork ids is created and that the chain ids actually match assertEq(forkOf[1], 0); @@ -114,10 +114,13 @@ contract ConfigTest is Test, Config { vm.copyFile(originalConfig, testConfig); // Deploy the config contract with the temporary fixture. - _loadConfig(testConfig); + _loadConfig(testConfig, true); + + // Enable autoWrite so that changes are written to the file + config.setAutoWrite(true); // Update a single boolean value and verify the change. - config.set(1, "is_live", false, true); + config.set(1, "is_live", false); assertFalse(config.get(1, "is_live").toBool()); @@ -126,7 +129,7 @@ contract ConfigTest is Test, Config { // Update a single address value and verify the change. address new_addr = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; - config.set(1, "weth", new_addr, true); + config.set(1, "weth", new_addr); assertEq(config.get(1, "weth").toAddress(), new_addr); @@ -138,7 +141,7 @@ contract ConfigTest is Test, Config { new_numbers[0] = 1; new_numbers[1] = 2; new_numbers[2] = 3; - config.set(10, "number_array", new_numbers, true); + config.set(10, "number_array", new_numbers); uint256[] memory updated_numbers_mem = config.get(10, "number_array").toUintArray(); assertEq(updated_numbers_mem.length, 3); @@ -157,7 +160,7 @@ contract ConfigTest is Test, Config { string[] memory new_strings = new string[](2); new_strings[0] = "hello"; new_strings[1] = "world"; - config.set(1, "str_array", new_strings, true); + config.set(1, "str_array", new_strings); string[] memory updated_strings_mem = config.get(1, "str_array").toStringArray(); assertEq(updated_strings_mem.length, 2); @@ -171,7 +174,7 @@ contract ConfigTest is Test, Config { assertEq(updated_strings_disk[1], "world"); // Create a new uint variable and verify the change. - config.set(1, "new_uint", 42, true); + config.set(1, "new_uint", 42); assertEq(config.get(1, "new_uint").toUint(), 42); @@ -182,7 +185,7 @@ contract ConfigTest is Test, Config { bytes32[] memory new_words = new bytes32[](2); new_words[0] = bytes32(uint256(0xDEAD)); new_words[1] = bytes32(uint256(0xBEEF)); - config.set(10, "new_words", new_words, true); + config.set(10, "new_words", new_words); bytes32[] memory updated_words_mem = config.get(10, "new_words").toBytes32Array(); assertEq(updated_words_mem.length, 2); @@ -223,16 +226,16 @@ contract ConfigTest is Test, Config { ); vm.expectRevert(abi.encodeWithSelector(StdConfig.InvalidChainKey.selector, "invalid_chain")); - new StdConfig(invalidChainConfig); + new StdConfig(invalidChainConfig, false); vm.removeFile(invalidChainConfig); } - function testRevert_ChainIdNotFound() public { - _loadConfig("./test/fixtures/config.toml"); + function testRevert_ChainNotInitialized() public { + _loadConfig("./test/fixtures/config.toml", true); // Try to write a value for a non-existent chain ID - vm.expectRevert(abi.encodeWithSelector(StdConfig.ChainIdNotFound.selector, uint256(999999))); - config.set(999999, "some_key", uint256(123), true); + vm.expectRevert(abi.encodeWithSelector(StdConfig.ChainNotInitialized.selector, uint256(999999))); + config.set(999999, "some_key", uint256(123)); } function testRevert_UnableToParseVariable() public { @@ -252,7 +255,7 @@ contract ConfigTest is Test, Config { ); vm.expectRevert(abi.encodeWithSelector(StdConfig.UnableToParseVariable.selector, "bad_value")); - new StdConfig(badParseConfig); + new StdConfig(badParseConfig, false); vm.removeFile(badParseConfig); } } @@ -264,7 +267,7 @@ contract LibVariableTest is Test, Config { function setUp() public { helper = new LibVariableHelper(); - _loadConfig("./test/fixtures/config.toml"); + _loadConfig("./test/fixtures/config.toml", false); } function testRevert_NotInitialized() public { diff --git a/test/StdChains.t.sol b/test/StdChains.t.sol index d88069bf..9522b37d 100644 --- a/test/StdChains.t.sol +++ b/test/StdChains.t.sol @@ -179,7 +179,7 @@ contract StdChainsTest is Test { stdChainsMock.exposed_getChain(""); } - function test_RevertIf_ChainIdNotFound() public { + function test_RevertIf_ChainNotInitialized() public { // We deploy a mock to properly test the revert. StdChainsMock stdChainsMock = new StdChainsMock(); From 319ae20c5a0e0226f49770ee6bf182a57759a077 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 27 Aug 2025 08:15:09 +0200 Subject: [PATCH 38/54] downcast support for all uint types --- src/Config.sol | 2 + src/LibVariable.sol | 120 ++++++++++++++++++++++++++++++++++++++++++-- src/StdConfig.sol | 52 +++++++++---------- test/Config.t.sol | 28 +++++------ 4 files changed, 159 insertions(+), 43 deletions(-) diff --git a/src/Config.sol b/src/Config.sol index c39f65ba..f3e10db2 100644 --- a/src/Config.sol +++ b/src/Config.sol @@ -25,6 +25,7 @@ abstract contract Config is CommonBase { /// @dev This function instantiates a `Config` contract, caching all its config variables. /// /// @param filePath: the path to the TOML configuration file. + /// @param writeToFile: whether updates are written back to the TOML file. function _loadConfig(string memory filePath, bool writeToFile) internal { console.log("----------"); console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); @@ -41,6 +42,7 @@ abstract contract Config is CommonBase { /// It also creates a map `forkOf[chainId] -> forkId` to easily switch between forks. /// /// @param filePath: the path to the TOML configuration file. + /// @param writeToFile: whether updates are written back to the TOML file. function _loadConfigAndForks(string memory filePath, bool writeToFile) internal { _loadConfig(filePath, writeToFile); diff --git a/src/LibVariable.sol b/src/LibVariable.sol index d08fbb3a..8cc536d1 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -41,7 +41,7 @@ enum TypeKind { /// /// function readValues() public { /// // Retrieve a 'uint256' value from the config. -/// uint256 myNumber = config.get("important_number").toUint(); +/// uint256 myNumber = config.get("important_number").toUint256(); /// /// // Would revert with `TypeMismatch` as 'important_number' isn't a `uint256` in the config file. /// // string memory notANumber = config.get("important_number").toString(); @@ -54,6 +54,7 @@ enum TypeKind { library LibVariable { error NotInitialized(); error TypeMismatch(string expected, string actual); + error UnsafeCast(string message); // -- TYPE HELPERS ---------------------------------------------------- @@ -125,10 +126,58 @@ library LibVariable { } /// @notice Coerces a `Variable` to a `uint256` value. - function toUint(Variable memory self) internal pure check(self, Type(TypeKind.Uint256, false)) returns (uint256) { + function toUint256(Variable memory self) + internal + pure + check(self, Type(TypeKind.Uint256, false)) + returns (uint256) + { return abi.decode(self.data, (uint256)); } + /// @notice Coerces a `Variable` to a `uint128` value, checking for overflow. + function toUint128(Variable memory self) internal pure returns (uint128) { + uint256 value = self.toUint256(); + if (value > type(uint128).max) { + revert UnsafeCast("value does not fit in uint128"); + } + return uint128(value); + } + + uint256 value = self.toUint256(); + if (value > type(uint64).max) { + revert UnsafeCast("value does not fit in uint64"); + } + return uint64(value); + } + + /// @notice Coerces a `Variable` to a `uint32` value, checking for overflow. + function toUint32(Variable memory self) internal pure returns (uint32) { + uint256 value = self.toUint256(); + if (value > type(uint32).max) { + revert UnsafeCast("value does not fit in uint32"); + } + return uint32(value); + } + + /// @notice Coerces a `Variable` to a `uint16` value, checking for overflow. + function toUint16(Variable memory self) internal pure returns (uint16) { + uint256 value = self.toUint256(); + if (value > type(uint16).max) { + revert UnsafeCast("value does not fit in uint16"); + } + return uint16(value); + } + + /// @notice Coerces a `Variable` to a `uint8` value, checking for overflow. + function toUint8(Variable memory self) internal pure returns (uint8) { + uint256 value = self.toUint256(); + if (value > type(uint8).max) { + revert UnsafeCast("value does not fit in uint8"); + } + return uint8(value); + } + /// @notice Coerces a `Variable` to an `address` value. function toAddress(Variable memory self) internal @@ -182,7 +231,7 @@ library LibVariable { } /// @notice Coerces a `Variable` to a `uint256` array. - function toUintArray(Variable memory self) + function toUint256Array(Variable memory self) internal pure check(self, Type(TypeKind.Uint256, true)) @@ -191,6 +240,71 @@ library LibVariable { return abi.decode(self.data, (uint256[])); } + /// @notice Coerces a `Variable` to a `uint128` array, checking for overflow. + function toUint128Array(Variable memory self) internal pure returns (uint128[] memory) { + uint256[] memory values = self.toUint256Array(); + uint128[] memory result = new uint128[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(uint128).max) { + revert UnsafeCast("value in array does not fit in uint128"); + } + result[i] = uint128(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `uint64` array, checking for overflow. + function toUint64Array(Variable memory self) internal pure returns (uint64[] memory) { + uint256[] memory values = self.toUint256Array(); + uint64[] memory result = new uint64[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(uint64).max) { + revert UnsafeCast("value in array does not fit in uint64"); + } + result[i] = uint64(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `uint32` array, checking for overflow. + function toUint32Array(Variable memory self) internal pure returns (uint32[] memory) { + uint256[] memory values = self.toUint256Array(); + uint32[] memory result = new uint32[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(uint32).max) { + revert UnsafeCast("value in array does not fit in uint32"); + } + result[i] = uint32(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `uint16` array, checking for overflow. + function toUint16Array(Variable memory self) internal pure returns (uint16[] memory) { + uint256[] memory values = self.toUint256Array(); + uint16[] memory result = new uint16[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(uint16).max) { + revert UnsafeCast("value in array does not fit in uint16"); + } + result[i] = uint16(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `uint8` array, checking for overflow. + function toUint8Array(Variable memory self) internal pure returns (uint8[] memory) { + uint256[] memory values = self.toUint256Array(); + uint8[] memory result = new uint8[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(uint8).max) { + revert UnsafeCast("value in array does not fit in uint8"); + } + result[i] = uint8(values[i]); + } + return result; + } + /// @notice Coerces a `Variable` to an `address` array. function toAddressArray(Variable memory self) internal diff --git a/src/StdConfig.sol b/src/StdConfig.sol index e423d2f4..4e23767f 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -292,7 +292,7 @@ contract StdConfig { /// @dev Reads a variable for a given chain id and key, and returns it in a generic container. /// The caller should use `LibVariable` to safely coerce the type. - /// Example: `uint256 myVar = config.get("my_key").toUint();` + /// Example: `uint256 myVar = config.get("my_key").toUint256();` /// /// @param chain_id The chain ID to read from. /// @param key The key of the variable to retrieve. @@ -303,7 +303,7 @@ contract StdConfig { /// @dev Reads a variable for the current chain and a given key, and returns it in a generic container. /// The caller should use `LibVariable` to safely coerce the type. - /// Example: `uint256 myVar = config.get("my_key").toUint();` + /// Example: `uint256 myVar = config.get("my_key").toUint256();` /// /// @param key The key of the variable to retrieve. /// @return `Variable` struct containing the type and the ABI-encoded value. @@ -336,7 +336,7 @@ contract StdConfig { // -- SETTER FUNCTIONS ----------------------------------------------------- /// @notice Sets a boolean value for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, bool value) public { Type memory ty = Type(TypeKind.Bool, false); _ensureTypeConsistency(chainId, key, ty); @@ -345,13 +345,13 @@ contract StdConfig { } /// @notice Sets a boolean value for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, bool value) public { set(vm.getChainId(), key, value); } /// @notice Sets a uint256 value for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, uint256 value) public { Type memory ty = Type(TypeKind.Uint256, false); _ensureTypeConsistency(chainId, key, ty); @@ -360,13 +360,13 @@ contract StdConfig { } /// @notice Sets a uint256 value for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, uint256 value) public { set(vm.getChainId(), key, value); } /// @notice Sets an address value for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, address value) public { Type memory ty = Type(TypeKind.Address, false); _ensureTypeConsistency(chainId, key, ty); @@ -375,13 +375,13 @@ contract StdConfig { } /// @notice Sets an address value for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, address value) public { set(vm.getChainId(), key, value); } /// @notice Sets a bytes32 value for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, bytes32 value) public { Type memory ty = Type(TypeKind.Bytes32, false); _ensureTypeConsistency(chainId, key, ty); @@ -390,13 +390,13 @@ contract StdConfig { } /// @notice Sets a bytes32 value for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, bytes32 value) public { set(vm.getChainId(), key, value); } /// @notice Sets a string value for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, string memory value) public { Type memory ty = Type(TypeKind.String, false); _ensureTypeConsistency(chainId, key, ty); @@ -405,13 +405,13 @@ contract StdConfig { } /// @notice Sets a string value for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, string memory value) public { set(vm.getChainId(), key, value); } /// @notice Sets a bytes value for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, bytes memory value) public { Type memory ty = Type(TypeKind.Bytes, false); _ensureTypeConsistency(chainId, key, ty); @@ -420,13 +420,13 @@ contract StdConfig { } /// @notice Sets a bytes value for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, bytes memory value) public { set(vm.getChainId(), key, value); } /// @notice Sets a boolean array for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, bool[] memory value) public { Type memory ty = Type(TypeKind.Bool, true); _ensureTypeConsistency(chainId, key, ty); @@ -443,13 +443,13 @@ contract StdConfig { } /// @notice Sets a boolean array for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, bool[] memory value) public { set(vm.getChainId(), key, value); } /// @notice Sets a uint256 array for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, uint256[] memory value) public { Type memory ty = Type(TypeKind.Uint256, true); _ensureTypeConsistency(chainId, key, ty); @@ -466,13 +466,13 @@ contract StdConfig { } /// @notice Sets a uint256 array for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, uint256[] memory value) public { set(vm.getChainId(), key, value); } /// @notice Sets an address array for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, address[] memory value) public { Type memory ty = Type(TypeKind.Address, true); _ensureTypeConsistency(chainId, key, ty); @@ -489,13 +489,13 @@ contract StdConfig { } /// @notice Sets an address array for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, address[] memory value) public { set(vm.getChainId(), key, value); } /// @notice Sets a bytes32 array for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, bytes32[] memory value) public { Type memory ty = Type(TypeKind.Bytes32, true); _ensureTypeConsistency(chainId, key, ty); @@ -512,13 +512,13 @@ contract StdConfig { } /// @notice Sets a bytes32 array for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, bytes32[] memory value) public { set(vm.getChainId(), key, value); } /// @notice Sets a string array for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, string[] memory value) public { Type memory ty = Type(TypeKind.String, true); _ensureTypeConsistency(chainId, key, ty); @@ -535,13 +535,13 @@ contract StdConfig { } /// @notice Sets a string array for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, string[] memory value) public { set(vm.getChainId(), key, value); } /// @notice Sets a bytes array for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, bytes[] memory value) public { Type memory ty = Type(TypeKind.Bytes, true); _ensureTypeConsistency(chainId, key, ty); @@ -558,7 +558,7 @@ contract StdConfig { } /// @notice Sets a bytes array for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if autoWrite is enabled. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(string memory key, bytes[] memory value) public { set(vm.getChainId(), key, value); } diff --git a/test/Config.t.sol b/test/Config.t.sol index 839dce57..405a8df2 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -29,8 +29,8 @@ contract ConfigTest is Test, Config { assertEq(address_array[1], 0x1111111111111111111111111111111111111111); // Read and assert uint values - assertEq(config.get(1, "number").toUint(), 1234); - uint256[] memory uint_array = config.get(1, "number_array").toUintArray(); + assertEq(config.get(1, "number").toUint256(), 1234); + uint256[] memory uint_array = config.get(1, "number_array").toUint256Array(); assertEq(uint_array[0], 5678); assertEq(uint_array[1], 9999); @@ -70,8 +70,8 @@ contract ConfigTest is Test, Config { assertEq(address_array[1], 0x3333333333333333333333333333333333333333); // Read and assert uint values - assertEq(config.get(10, "number").toUint(), 9999); - uint_array = config.get(10, "number_array").toUintArray(); + assertEq(config.get(10, "number").toUint256(), 9999); + uint_array = config.get(10, "number_array").toUint256Array(); assertEq(uint_array[0], 1234); assertEq(uint_array[1], 5678); @@ -143,7 +143,7 @@ contract ConfigTest is Test, Config { new_numbers[2] = 3; config.set(10, "number_array", new_numbers); - uint256[] memory updated_numbers_mem = config.get(10, "number_array").toUintArray(); + uint256[] memory updated_numbers_mem = config.get(10, "number_array").toUint256Array(); assertEq(updated_numbers_mem.length, 3); assertEq(updated_numbers_mem[0], 1); assertEq(updated_numbers_mem[1], 2); @@ -176,7 +176,7 @@ contract ConfigTest is Test, Config { // Create a new uint variable and verify the change. config.set(1, "new_uint", 42); - assertEq(config.get(1, "new_uint").toUint(), 42); + assertEq(config.get(1, "new_uint").toUint256(), 42); content = vm.readFile(testConfig); assertEq(vm.parseTomlUint(content, "$.mainnet.uint.new_uint"), 42); @@ -279,7 +279,7 @@ contract LibVariableTest is Test, Config { helper.toBool(notInit); vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toUint(notInit); + helper.toUint256(notInit); vm.expectRevert(LibVariable.NotInitialized.selector); helper.toAddress(notInit); @@ -298,7 +298,7 @@ contract LibVariableTest is Test, Config { helper.toBoolArray(notInit); vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toUintArray(notInit); + helper.toUint256Array(notInit); vm.expectRevert(LibVariable.NotInitialized.selector); helper.toAddressArray(notInit); @@ -319,7 +319,7 @@ contract LibVariableTest is Test, Config { // Try to coerce it to wrong single value types - should revert with TypeMismatch vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool")); - helper.toUint(boolVar); + helper.toUint256(boolVar); vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "bool")); helper.toAddress(boolVar); @@ -352,7 +352,7 @@ contract LibVariableTest is Test, Config { // Try to coerce array to wrong array type vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256[]", "bool[]")); - helper.toUintArray(boolArrayVar); + helper.toUint256Array(boolArrayVar); // Get a single value and try to coerce to array Variable memory singleBoolVar = config.get(1, "is_live"); @@ -369,8 +369,8 @@ contract LibVariableHelper { return v.toBool(); } - function toUint(Variable memory v) external pure returns (uint256) { - return v.toUint(); + function toUint256(Variable memory v) external pure returns (uint256) { + return v.toUint256(); } function toAddress(Variable memory v) external pure returns (address) { @@ -393,8 +393,8 @@ contract LibVariableHelper { return v.toBoolArray(); } - function toUintArray(Variable memory v) external pure returns (uint256[] memory) { - return v.toUintArray(); + function toUint256Array(Variable memory v) external pure returns (uint256[] memory) { + return v.toUint256Array(); } function toAddressArray(Variable memory v) external pure returns (address[] memory) { From 3cf9c668f46dac44eb2844f5c6ed67535573813b Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 27 Aug 2025 08:20:45 +0200 Subject: [PATCH 39/54] fix: mistakenly erased fn sig --- src/LibVariable.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/LibVariable.sol b/src/LibVariable.sol index 8cc536d1..7d544834 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -144,6 +144,8 @@ library LibVariable { return uint128(value); } + /// @notice Coerces a `Variable` to a `uint64` value, checking for overflow. + function toUint64(Variable memory self) internal pure returns (uint128) { uint256 value = self.toUint256(); if (value > type(uint64).max) { revert UnsafeCast("value does not fit in uint64"); From b6128a094f51f3568f0d29e5ec2a8722ab4248bd Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 27 Aug 2025 08:46:57 +0200 Subject: [PATCH 40/54] add integer support --- src/LibVariable.sol | 212 +++++++++++++++++++++++++++++++++++--------- src/StdConfig.sol | 130 ++++++++++++++++++--------- test/Config.t.sol | 2 +- 3 files changed, 261 insertions(+), 83 deletions(-) diff --git a/src/LibVariable.sol b/src/LibVariable.sol index 7d544834..a438a8f8 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -18,8 +18,9 @@ enum TypeKind { None, Bool, Address, - Uint256, Bytes32, + Uint256, + Int256, String, Bytes } @@ -84,8 +85,9 @@ library LibVariable { function toString(TypeKind self) internal pure returns (string memory) { if (self == TypeKind.Bool) return "bool"; if (self == TypeKind.Address) return "address"; - if (self == TypeKind.Uint256) return "uint256"; if (self == TypeKind.Bytes32) return "bytes32"; + if (self == TypeKind.Uint256) return "uint256"; + if (self == TypeKind.Int256) return "int256"; if (self == TypeKind.String) return "string"; if (self == TypeKind.Bytes) return "bytes"; return "none"; @@ -95,8 +97,9 @@ library LibVariable { function toTomlKey(TypeKind self) internal pure returns (string memory) { if (self == TypeKind.Bool) return "bool"; if (self == TypeKind.Address) return "address"; - if (self == TypeKind.Uint256) return "uint"; if (self == TypeKind.Bytes32) return "bytes32"; + if (self == TypeKind.Uint256) return "uint"; + if (self == TypeKind.Int256) return "int"; if (self == TypeKind.String) return "string"; if (self == TypeKind.Bytes) return "bytes"; return "none"; @@ -125,6 +128,26 @@ library LibVariable { return abi.decode(self.data, (bool)); } + /// @notice Coerces a `Variable` to an `address` value. + function toAddress(Variable memory self) + internal + pure + check(self, Type(TypeKind.Address, false)) + returns (address) + { + return abi.decode(self.data, (address)); + } + + /// @notice Coerces a `Variable` to a `bytes32` value. + function toBytes32(Variable memory self) + internal + pure + check(self, Type(TypeKind.Bytes32, false)) + returns (bytes32) + { + return abi.decode(self.data, (bytes32)); + } + /// @notice Coerces a `Variable` to a `uint256` value. function toUint256(Variable memory self) internal @@ -139,7 +162,7 @@ library LibVariable { function toUint128(Variable memory self) internal pure returns (uint128) { uint256 value = self.toUint256(); if (value > type(uint128).max) { - revert UnsafeCast("value does not fit in uint128"); + revert UnsafeCast("value does not fit in 'uint128'"); } return uint128(value); } @@ -148,7 +171,7 @@ library LibVariable { function toUint64(Variable memory self) internal pure returns (uint128) { uint256 value = self.toUint256(); if (value > type(uint64).max) { - revert UnsafeCast("value does not fit in uint64"); + revert UnsafeCast("value does not fit in 'uint64'"); } return uint64(value); } @@ -157,7 +180,7 @@ library LibVariable { function toUint32(Variable memory self) internal pure returns (uint32) { uint256 value = self.toUint256(); if (value > type(uint32).max) { - revert UnsafeCast("value does not fit in uint32"); + revert UnsafeCast("value does not fit in 'uint32'"); } return uint32(value); } @@ -166,7 +189,7 @@ library LibVariable { function toUint16(Variable memory self) internal pure returns (uint16) { uint256 value = self.toUint256(); if (value > type(uint16).max) { - revert UnsafeCast("value does not fit in uint16"); + revert UnsafeCast("value does not fit in 'uint16'"); } return uint16(value); } @@ -175,29 +198,59 @@ library LibVariable { function toUint8(Variable memory self) internal pure returns (uint8) { uint256 value = self.toUint256(); if (value > type(uint8).max) { - revert UnsafeCast("value does not fit in uint8"); + revert UnsafeCast("value does not fit in 'uint8'"); } return uint8(value); } - /// @notice Coerces a `Variable` to an `address` value. - function toAddress(Variable memory self) - internal - pure - check(self, Type(TypeKind.Address, false)) - returns (address) - { - return abi.decode(self.data, (address)); + /// @notice Coerces a `Variable` to an `int256` value. + function toInt256(Variable memory self) internal pure check(self, Type(TypeKind.Int256, false)) returns (int256) { + return abi.decode(self.data, (int256)); } - /// @notice Coerces a `Variable` to a `bytes32` value. - function toBytes32(Variable memory self) - internal - pure - check(self, Type(TypeKind.Bytes32, false)) - returns (bytes32) - { - return abi.decode(self.data, (bytes32)); + /// @notice Coerces a `Variable` to an `int128` value, checking for overflow/underflow. + function toInt128(Variable memory self) internal pure returns (int128) { + int256 value = self.toInt256(); + if (value > type(int128).max || value < type(int128).min) { + revert UnsafeCast("value does not fit in 'int128'"); + } + return int128(value); + } + + /// @notice Coerces a `Variable` to an `int64` value, checking for overflow/underflow. + function toInt64(Variable memory self) internal pure returns (int64) { + int256 value = self.toInt256(); + if (value > type(int64).max || value < type(int64).min) { + revert UnsafeCast("value does not fit in 'int64'"); + } + return int64(value); + } + + /// @notice Coerces a `Variable` to an `int32` value, checking for overflow/underflow. + function toInt32(Variable memory self) internal pure returns (int32) { + int256 value = self.toInt256(); + if (value > type(int32).max || value < type(int32).min) { + revert UnsafeCast("value does not fit in 'int32'"); + } + return int32(value); + } + + /// @notice Coerces a `Variable` to an `int16` value, checking for overflow/underflow. + function toInt16(Variable memory self) internal pure returns (int16) { + int256 value = self.toInt256(); + if (value > type(int16).max || value < type(int16).min) { + revert UnsafeCast("value does not fit in 'int16'"); + } + return int16(value); + } + + /// @notice Coerces a `Variable` to an `int8` value, checking for overflow/underflow. + function toInt8(Variable memory self) internal pure returns (int8) { + int256 value = self.toInt256(); + if (value > type(int8).max || value < type(int8).min) { + revert UnsafeCast("value does not fit in 'int8'"); + } + return int8(value); } /// @notice Coerces a `Variable` to a `string` value. @@ -232,6 +285,26 @@ library LibVariable { return abi.decode(self.data, (bool[])); } + /// @notice Coerces a `Variable` to an `address` array. + function toAddressArray(Variable memory self) + internal + pure + check(self, Type(TypeKind.Address, true)) + returns (address[] memory) + { + return abi.decode(self.data, (address[])); + } + + /// @notice Coerces a `Variable` to a `bytes32` array. + function toBytes32Array(Variable memory self) + internal + pure + check(self, Type(TypeKind.Bytes32, true)) + returns (bytes32[] memory) + { + return abi.decode(self.data, (bytes32[])); + } + /// @notice Coerces a `Variable` to a `uint256` array. function toUint256Array(Variable memory self) internal @@ -248,7 +321,7 @@ library LibVariable { uint128[] memory result = new uint128[](values.length); for (uint256 i = 0; i < values.length; i++) { if (values[i] > type(uint128).max) { - revert UnsafeCast("value in array does not fit in uint128"); + revert UnsafeCast("value in array does not fit in 'uint128'"); } result[i] = uint128(values[i]); } @@ -261,7 +334,7 @@ library LibVariable { uint64[] memory result = new uint64[](values.length); for (uint256 i = 0; i < values.length; i++) { if (values[i] > type(uint64).max) { - revert UnsafeCast("value in array does not fit in uint64"); + revert UnsafeCast("value in array does not fit in 'uint64'"); } result[i] = uint64(values[i]); } @@ -274,7 +347,7 @@ library LibVariable { uint32[] memory result = new uint32[](values.length); for (uint256 i = 0; i < values.length; i++) { if (values[i] > type(uint32).max) { - revert UnsafeCast("value in array does not fit in uint32"); + revert UnsafeCast("value in array does not fit in 'uint32'"); } result[i] = uint32(values[i]); } @@ -287,7 +360,7 @@ library LibVariable { uint16[] memory result = new uint16[](values.length); for (uint256 i = 0; i < values.length; i++) { if (values[i] > type(uint16).max) { - revert UnsafeCast("value in array does not fit in uint16"); + revert UnsafeCast("value in array does not fit in 'uint16'"); } result[i] = uint16(values[i]); } @@ -300,31 +373,86 @@ library LibVariable { uint8[] memory result = new uint8[](values.length); for (uint256 i = 0; i < values.length; i++) { if (values[i] > type(uint8).max) { - revert UnsafeCast("value in array does not fit in uint8"); + revert UnsafeCast("value in array does not fit in 'uint8'"); } result[i] = uint8(values[i]); } return result; } - /// @notice Coerces a `Variable` to an `address` array. - function toAddressArray(Variable memory self) + /// @notice Coerces a `Variable` to an `int256` array. + function toInt256Array(Variable memory self) internal pure - check(self, Type(TypeKind.Address, true)) - returns (address[] memory) + check(self, Type(TypeKind.Int256, true)) + returns (int256[] memory) { - return abi.decode(self.data, (address[])); + return abi.decode(self.data, (int256[])); } - /// @notice Coerces a `Variable` to a `bytes32` array. - function toBytes32Array(Variable memory self) - internal - pure - check(self, Type(TypeKind.Bytes32, true)) - returns (bytes32[] memory) - { - return abi.decode(self.data, (bytes32[])); + /// @notice Coerces a `Variable` to a `int128` array, checking for overflow/underflow. + function toInt128Array(Variable memory self) internal pure returns (int128[] memory) { + int256[] memory values = self.toInt256Array(); + int128[] memory result = new int128[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(int128).max || values[i] < type(int128).min) { + revert UnsafeCast("value in array does not fit in 'int128'"); + } + result[i] = int128(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `int64` array, checking for overflow/underflow. + function toInt64Array(Variable memory self) internal pure returns (int64[] memory) { + int256[] memory values = self.toInt256Array(); + int64[] memory result = new int64[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(int64).max || values[i] < type(int64).min) { + revert UnsafeCast("value in array does not fit in 'int64'"); + } + result[i] = int64(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `int32` array, checking for overflow/underflow. + function toInt32Array(Variable memory self) internal pure returns (int32[] memory) { + int256[] memory values = self.toInt256Array(); + int32[] memory result = new int32[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(int32).max || values[i] < type(int32).min) { + revert UnsafeCast("value in array does not fit in 'int32'"); + } + result[i] = int32(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `int16` array, checking for overflow/underflow. + function toInt16Array(Variable memory self) internal pure returns (int16[] memory) { + int256[] memory values = self.toInt256Array(); + int16[] memory result = new int16[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(int16).max || values[i] < type(int16).min) { + revert UnsafeCast("value in array does not fit in 'int16'"); + } + result[i] = int16(values[i]); + } + return result; + } + + /// @notice Coerces a `Variable` to a `int8` array, checking for overflow/underflow. + function toInt8Array(Variable memory self) internal pure returns (int8[] memory) { + int256[] memory values = self.toInt256Array(); + int8[] memory result = new int8[](values.length); + for (uint256 i = 0; i < values.length; i++) { + if (values[i] > type(int8).max || values[i] < type(int8).min) { + revert UnsafeCast("value in array does not fit in 'int8'"); + } + result[i] = int8(values[i]); + } + return result; } /// @notice Coerces a `Variable` to a `string` array. diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 4e23767f..4e5e8140 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -10,7 +10,7 @@ import {Variable, Type, TypeKind, LibVariable} from "./LibVariable.sol"; /// @dev This contract assumes a toml structure where top-level keys /// represent chain ids or aliases. Under each chain key, variables are /// organized by type in separate sub-tables like `[.]`, where -/// type must be: `bool`, `address`, `uint`, `bytes32`, `string`, or `bytes`. +/// type must be: `bool`, `address`, `bytes32`, `uint`, `ìnt`, `string`, or `bytes`. /// /// Supported format: /// ``` @@ -102,7 +102,7 @@ contract StdConfig { } // Iterate through all the available `TypeKind`s (except `None`) to create the sub-section paths - for (uint8 t = 1; t < NUM_TYPES; t++) { + for (uint8 t = 1; t <= NUM_TYPES; t++) { TypeKind ty = TypeKind(t); string memory typePath = _concat("$.", chain_key, ".", ty.toTomlKey()); @@ -152,6 +152,18 @@ contract StdConfig { success = true; } catch {} } + } else if (ty == TypeKind.Bytes32) { + try vm.parseTomlBytes32(content, path) returns (bytes32 val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bytes32, false); + success = true; + } catch { + try vm.parseTomlBytes32Array(content, path) returns (bytes32[] memory val) { + _dataOf[chainId][key] = abi.encode(val); + _typeOf[chainId][key] = Type(TypeKind.Bytes32, true); + success = true; + } catch {} + } } else if (ty == TypeKind.Uint256) { try vm.parseTomlUint(content, path) returns (uint256 val) { _dataOf[chainId][key] = abi.encode(val); @@ -164,15 +176,15 @@ contract StdConfig { success = true; } catch {} } - } else if (ty == TypeKind.Bytes32) { - try vm.parseTomlBytes32(content, path) returns (bytes32 val) { + } else if (ty == TypeKind.Int256) { + try vm.parseTomlInt(content, path) returns (int256 val) { _dataOf[chainId][key] = abi.encode(val); - _typeOf[chainId][key] = Type(TypeKind.Bytes32, false); + _typeOf[chainId][key] = Type(TypeKind.Int256, false); success = true; } catch { - try vm.parseTomlBytes32Array(content, path) returns (bytes32[] memory val) { + try vm.parseTomlIntArray(content, path) returns (int256[] memory val) { _dataOf[chainId][key] = abi.encode(val); - _typeOf[chainId][key] = Type(TypeKind.Bytes32, true); + _typeOf[chainId][key] = Type(TypeKind.Int256, true); success = true; } catch {} } @@ -333,7 +345,7 @@ contract StdConfig { return _rpcOf[vm.getChainId()]; } - // -- SETTER FUNCTIONS ----------------------------------------------------- + // -- SETTER FUNCTIONS (SINGLE VALUES) ------------------------------------- /// @notice Sets a boolean value for a given key and chain ID. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. @@ -350,21 +362,6 @@ contract StdConfig { set(vm.getChainId(), key, value); } - /// @notice Sets a uint256 value for a given key and chain ID. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(uint256 chainId, string memory key, uint256 value) public { - Type memory ty = Type(TypeKind.Uint256, false); - _ensureTypeConsistency(chainId, key, ty); - _dataOf[chainId][key] = abi.encode(value); - if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); - } - - /// @notice Sets a uint256 value for a given key on the current chain. - /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(string memory key, uint256 value) public { - set(vm.getChainId(), key, value); - } - /// @notice Sets an address value for a given key and chain ID. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, address value) public { @@ -395,6 +392,34 @@ contract StdConfig { set(vm.getChainId(), key, value); } + /// @notice Sets a uint256 value for a given key and chain ID. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. + function set(uint256 chainId, string memory key, uint256 value) public { + Type memory ty = Type(TypeKind.Uint256, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); + } + + /// @notice Sets a uint256 value for a given key on the current chain. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. + function set(string memory key, uint256 value) public { + set(vm.getChainId(), key, value); + } + + /// @notice Sets an int256 value for a given key and chain ID. + function set(uint256 chainId, string memory key, int256 value) public { + Type memory ty = Type(TypeKind.Int256, false); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (_writeToFile) _writeToToml(chainId, ty.kind.toTomlKey(), key, vm.toString(value)); + } + + /// @notice Sets an int256 value for a given key on the current chain. + function set(string memory key, int256 value) public { + set(vm.getChainId(), key, value); + } + /// @notice Sets a string value for a given key and chain ID. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, string memory value) public { @@ -425,6 +450,8 @@ contract StdConfig { set(vm.getChainId(), key, value); } + // -- SETTER FUNCTIONS (ARRAYS) -------------------------------------------- + /// @notice Sets a boolean array for a given key and chain ID. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. function set(uint256 chainId, string memory key, bool[] memory value) public { @@ -448,16 +475,16 @@ contract StdConfig { set(vm.getChainId(), key, value); } - /// @notice Sets a uint256 array for a given key and chain ID. + /// @notice Sets an address array for a given key and chain ID. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(uint256 chainId, string memory key, uint256[] memory value) public { - Type memory ty = Type(TypeKind.Uint256, true); + function set(uint256 chainId, string memory key, address[] memory value) public { + Type memory ty = Type(TypeKind.Address, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, vm.toString(value[i])); + json = _concat(json, _quote(vm.toString(value[i]))); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); @@ -465,16 +492,16 @@ contract StdConfig { } } - /// @notice Sets a uint256 array for a given key on the current chain. + /// @notice Sets an address array for a given key on the current chain. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(string memory key, uint256[] memory value) public { + function set(string memory key, address[] memory value) public { set(vm.getChainId(), key, value); } - /// @notice Sets an address array for a given key and chain ID. + /// @notice Sets a bytes32 array for a given key and chain ID. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(uint256 chainId, string memory key, address[] memory value) public { - Type memory ty = Type(TypeKind.Address, true); + function set(uint256 chainId, string memory key, bytes32[] memory value) public { + Type memory ty = Type(TypeKind.Bytes32, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); if (_writeToFile) { @@ -488,22 +515,22 @@ contract StdConfig { } } - /// @notice Sets an address array for a given key on the current chain. + /// @notice Sets a bytes32 array for a given key on the current chain. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(string memory key, address[] memory value) public { + function set(string memory key, bytes32[] memory value) public { set(vm.getChainId(), key, value); } - /// @notice Sets a bytes32 array for a given key and chain ID. + /// @notice Sets a uint256 array for a given key and chain ID. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(uint256 chainId, string memory key, bytes32[] memory value) public { - Type memory ty = Type(TypeKind.Bytes32, true); + function set(uint256 chainId, string memory key, uint256[] memory value) public { + Type memory ty = Type(TypeKind.Uint256, true); _ensureTypeConsistency(chainId, key, ty); _dataOf[chainId][key] = abi.encode(value); if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, _quote(vm.toString(value[i]))); + json = _concat(json, vm.toString(value[i])); if (i < value.length - 1) json = _concat(json, ","); } json = _concat(json, "]"); @@ -511,9 +538,32 @@ contract StdConfig { } } - /// @notice Sets a bytes32 array for a given key on the current chain. + /// @notice Sets a uint256 array for a given key on the current chain. /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. - function set(string memory key, bytes32[] memory value) public { + function set(string memory key, uint256[] memory value) public { + set(vm.getChainId(), key, value); + } + + /// @notice Sets a int256 array for a given key and chain ID. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. + function set(uint256 chainId, string memory key, int256[] memory value) public { + Type memory ty = Type(TypeKind.Int256, true); + _ensureTypeConsistency(chainId, key, ty); + _dataOf[chainId][key] = abi.encode(value); + if (_writeToFile) { + string memory json = "["; + for (uint256 i = 0; i < value.length; i++) { + json = _concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = _concat(json, ","); + } + json = _concat(json, "]"); + _writeToToml(chainId, ty.kind.toTomlKey(), key, json); + } + } + + /// @notice Sets a int256 array for a given key on the current chain. + /// @dev Sets the cached value in storage and writes the change back to the TOML file if `autoWrite` is enabled. + function set(string memory key, int256[] memory value) public { set(vm.getChainId(), key, value); } diff --git a/test/Config.t.sol b/test/Config.t.sol index 405a8df2..aed189ab 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -174,7 +174,7 @@ contract ConfigTest is Test, Config { assertEq(updated_strings_disk[1], "world"); // Create a new uint variable and verify the change. - config.set(1, "new_uint", 42); + config.set(1, "new_uint", uint256(42)); assertEq(config.get(1, "new_uint").toUint256(), 42); From 8bd86d7c26328e26fca678bf0736667e53dcd49d Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 27 Aug 2025 09:32:58 +0200 Subject: [PATCH 41/54] more tests --- src/LibVariable.sol | 2 +- test/Config.t.sol | 375 +++++++++++++------------------------- test/LibVariable.t.sol | 335 ++++++++++++++++++++++++++++++++++ test/fixtures/config.toml | 8 + 4 files changed, 472 insertions(+), 248 deletions(-) create mode 100644 test/LibVariable.t.sol diff --git a/src/LibVariable.sol b/src/LibVariable.sol index a438a8f8..a2618942 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -168,7 +168,7 @@ library LibVariable { } /// @notice Coerces a `Variable` to a `uint64` value, checking for overflow. - function toUint64(Variable memory self) internal pure returns (uint128) { + function toUint64(Variable memory self) internal pure returns (uint64) { uint256 value = self.toUint256(); if (value > type(uint64).max) { revert UnsafeCast("value does not fit in 'uint64'"); diff --git a/test/Config.t.sol b/test/Config.t.sol index aed189ab..6da0e5fd 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.13; import {Test} from "../src/Test.sol"; import {Config} from "../src/Config.sol"; import {StdConfig} from "../src/StdConfig.sol"; -import {Variable, LibVariable} from "../src/LibVariable.sol"; contract ConfigTest is Test, Config { function test_loadConfig() public { @@ -28,17 +27,23 @@ contract ConfigTest is Test, Config { assertEq(address_array[0], 0x0000000000000000000000000000000000000000); assertEq(address_array[1], 0x1111111111111111111111111111111111111111); + // Read and assert bytes32 values + assertEq(config.get(1, "word").toBytes32(), bytes32(uint256(1234))); + bytes32[] memory bytes32_array = config.get(1, "word_array").toBytes32Array(); + assertEq(bytes32_array[0], bytes32(uint256(5678))); + assertEq(bytes32_array[1], bytes32(uint256(9999))); + // Read and assert uint values assertEq(config.get(1, "number").toUint256(), 1234); uint256[] memory uint_array = config.get(1, "number_array").toUint256Array(); assertEq(uint_array[0], 5678); assertEq(uint_array[1], 9999); - // Read and assert bytes32 values - assertEq(config.get(1, "word").toBytes32(), bytes32(uint256(1234))); - bytes32[] memory bytes32_array = config.get(1, "word_array").toBytes32Array(); - assertEq(bytes32_array[0], bytes32(uint256(5678))); - assertEq(bytes32_array[1], bytes32(uint256(9999))); + // Read and assert int values + assertEq(config.get(1, "signed_number").toInt256(), -1234); + int256[] memory int_array = config.get(1, "signed_number_array").toInt256Array(); + assertEq(int_array[0], -5678); + assertEq(int_array[1], 9999); // Read and assert bytes values assertEq(config.get(1, "b").toBytes(), hex"abcd"); @@ -69,17 +74,23 @@ contract ConfigTest is Test, Config { assertEq(address_array[0], 0x2222222222222222222222222222222222222222); assertEq(address_array[1], 0x3333333333333333333333333333333333333333); + // Read and assert bytes32 values + assertEq(config.get(10, "word").toBytes32(), bytes32(uint256(9999))); + bytes32_array = config.get(10, "word_array").toBytes32Array(); + assertEq(bytes32_array[0], bytes32(uint256(1234))); + assertEq(bytes32_array[1], bytes32(uint256(5678))); + // Read and assert uint values assertEq(config.get(10, "number").toUint256(), 9999); uint_array = config.get(10, "number_array").toUint256Array(); assertEq(uint_array[0], 1234); assertEq(uint_array[1], 5678); - // Read and assert bytes32 values - assertEq(config.get(10, "word").toBytes32(), bytes32(uint256(9999))); - bytes32_array = config.get(10, "word_array").toBytes32Array(); - assertEq(bytes32_array[0], bytes32(uint256(1234))); - assertEq(bytes32_array[1], bytes32(uint256(5678))); + // Read and assert int values + assertEq(config.get(10, "signed_number").toInt256(), 9999); + int_array = config.get(10, "signed_number_array").toInt256Array(); + assertEq(int_array[0], -1234); + assertEq(int_array[1], -5678); // Read and assert bytes values assertEq(config.get(10, "b").toBytes(), hex"dcba"); @@ -113,90 +124,114 @@ contract ConfigTest is Test, Config { string memory testConfig = "./test/fixtures/config.t.toml"; vm.copyFile(originalConfig, testConfig); - // Deploy the config contract with the temporary fixture. + // Deploy the config contract with the temporary fixture and `writeToFile = true`. _loadConfig(testConfig, true); - // Enable autoWrite so that changes are written to the file - config.setAutoWrite(true); - - // Update a single boolean value and verify the change. - config.set(1, "is_live", false); - - assertFalse(config.get(1, "is_live").toBool()); - - string memory content = vm.readFile(testConfig); - assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live")); - - // Update a single address value and verify the change. - address new_addr = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; - config.set(1, "weth", new_addr); - - assertEq(config.get(1, "weth").toAddress(), new_addr); - - content = vm.readFile(testConfig); - assertEq(vm.parseTomlAddress(content, "$.mainnet.address.weth"), new_addr); - - // Update a uint array and verify the change. - uint256[] memory new_numbers = new uint256[](3); - new_numbers[0] = 1; - new_numbers[1] = 2; - new_numbers[2] = 3; - config.set(10, "number_array", new_numbers); - - uint256[] memory updated_numbers_mem = config.get(10, "number_array").toUint256Array(); - assertEq(updated_numbers_mem.length, 3); - assertEq(updated_numbers_mem[0], 1); - assertEq(updated_numbers_mem[1], 2); - assertEq(updated_numbers_mem[2], 3); - - content = vm.readFile(testConfig); - uint256[] memory updated_numbers_disk = vm.parseTomlUintArray(content, "$.optimism.uint.number_array"); - assertEq(updated_numbers_disk.length, 3); - assertEq(updated_numbers_disk[0], 1); - assertEq(updated_numbers_disk[1], 2); - assertEq(updated_numbers_disk[2], 3); - - // Update a string array and verify the change. - string[] memory new_strings = new string[](2); - new_strings[0] = "hello"; - new_strings[1] = "world"; - config.set(1, "str_array", new_strings); - - string[] memory updated_strings_mem = config.get(1, "str_array").toStringArray(); - assertEq(updated_strings_mem.length, 2); - assertEq(updated_strings_mem[0], "hello"); - assertEq(updated_strings_mem[1], "world"); - - content = vm.readFile(testConfig); - string[] memory updated_strings_disk = vm.parseTomlStringArray(content, "$.mainnet.string.str_array"); - assertEq(updated_strings_disk.length, 2); - assertEq(updated_strings_disk[0], "hello"); - assertEq(updated_strings_disk[1], "world"); - - // Create a new uint variable and verify the change. - config.set(1, "new_uint", uint256(42)); - - assertEq(config.get(1, "new_uint").toUint256(), 42); - - content = vm.readFile(testConfig); - assertEq(vm.parseTomlUint(content, "$.mainnet.uint.new_uint"), 42); - - // Create a new bytes32 array and verify the change. - bytes32[] memory new_words = new bytes32[](2); - new_words[0] = bytes32(uint256(0xDEAD)); - new_words[1] = bytes32(uint256(0xBEEF)); - config.set(10, "new_words", new_words); - - bytes32[] memory updated_words_mem = config.get(10, "new_words").toBytes32Array(); - assertEq(updated_words_mem.length, 2); - assertEq(updated_words_mem[0], new_words[0]); - assertEq(updated_words_mem[1], new_words[1]); - - content = vm.readFile(testConfig); - bytes32[] memory updated_words_disk = vm.parseTomlBytes32Array(content, "$.optimism.bytes32.new_words"); - assertEq(updated_words_disk.length, 2); - assertEq(vm.toString(updated_words_disk[0]), vm.toString(new_words[0])); - assertEq(vm.toString(updated_words_disk[1]), vm.toString(new_words[1])); + { + // Update a single boolean value and verify the change. + config.set(1, "is_live", false); + + assertFalse(config.get(1, "is_live").toBool()); + + string memory content = vm.readFile(testConfig); + assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live")); + + // Update a single address value and verify the change. + address new_addr = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; + config.set(1, "weth", new_addr); + + assertEq(config.get(1, "weth").toAddress(), new_addr); + + content = vm.readFile(testConfig); + assertEq(vm.parseTomlAddress(content, "$.mainnet.address.weth"), new_addr); + + // Update a uint array and verify the change. + uint256[] memory new_numbers = new uint256[](3); + new_numbers[0] = 1; + new_numbers[1] = 2; + new_numbers[2] = 3; + config.set(10, "number_array", new_numbers); + + uint256[] memory updated_numbers_mem = config.get(10, "number_array").toUint256Array(); + assertEq(updated_numbers_mem.length, 3); + assertEq(updated_numbers_mem[0], 1); + assertEq(updated_numbers_mem[1], 2); + assertEq(updated_numbers_mem[2], 3); + + content = vm.readFile(testConfig); + uint256[] memory updated_numbers_disk = vm.parseTomlUintArray(content, "$.optimism.uint.number_array"); + assertEq(updated_numbers_disk.length, 3); + assertEq(updated_numbers_disk[0], 1); + assertEq(updated_numbers_disk[1], 2); + assertEq(updated_numbers_disk[2], 3); + + // Update a string array and verify the change. + string[] memory new_strings = new string[](2); + new_strings[0] = "hello"; + new_strings[1] = "world"; + config.set(1, "str_array", new_strings); + + string[] memory updated_strings_mem = config.get(1, "str_array").toStringArray(); + assertEq(updated_strings_mem.length, 2); + assertEq(updated_strings_mem[0], "hello"); + assertEq(updated_strings_mem[1], "world"); + + content = vm.readFile(testConfig); + string[] memory updated_strings_disk = vm.parseTomlStringArray(content, "$.mainnet.string.str_array"); + assertEq(updated_strings_disk.length, 2); + assertEq(updated_strings_disk[0], "hello"); + assertEq(updated_strings_disk[1], "world"); + + // Create a new uint variable and verify the change. + config.set(1, "new_uint", uint256(42)); + + assertEq(config.get(1, "new_uint").toUint256(), 42); + + content = vm.readFile(testConfig); + assertEq(vm.parseTomlUint(content, "$.mainnet.uint.new_uint"), 42); + + // Create a new int variable and verify the change. + config.set(1, "new_int", int256(-42)); + + assertEq(config.get(1, "new_int").toInt256(), -42); + + content = vm.readFile(testConfig); + assertEq(vm.parseTomlInt(content, "$.mainnet.int.new_int"), -42); + + // Create a new int array and verify the change. + int256[] memory new_ints = new int256[](2); + new_ints[0] = -100; + new_ints[1] = 200; + config.set(10, "new_ints", new_ints); + + int256[] memory updated_ints_mem = config.get(10, "new_ints").toInt256Array(); + assertEq(updated_ints_mem.length, 2); + assertEq(updated_ints_mem[0], -100); + assertEq(updated_ints_mem[1], 200); + + content = vm.readFile(testConfig); + int256[] memory updated_ints_disk = vm.parseTomlIntArray(content, "$.optimism.int.new_ints"); + assertEq(updated_ints_disk.length, 2); + assertEq(updated_ints_disk[0], -100); + assertEq(updated_ints_disk[1], 200); + + // Create a new bytes32 array and verify the change. + bytes32[] memory new_words = new bytes32[](2); + new_words[0] = bytes32(uint256(0xDEAD)); + new_words[1] = bytes32(uint256(0xBEEF)); + config.set(10, "new_words", new_words); + + bytes32[] memory updated_words_mem = config.get(10, "new_words").toBytes32Array(); + assertEq(updated_words_mem.length, 2); + assertEq(updated_words_mem[0], new_words[0]); + assertEq(updated_words_mem[1], new_words[1]); + + content = vm.readFile(testConfig); + bytes32[] memory updated_words_disk = vm.parseTomlBytes32Array(content, "$.optimism.bytes32.new_words"); + assertEq(updated_words_disk.length, 2); + assertEq(vm.toString(updated_words_disk[0]), vm.toString(new_words[0])); + assertEq(vm.toString(updated_words_disk[1]), vm.toString(new_words[1])); + } // Clean up the temporary file. vm.removeFile(testConfig); @@ -259,157 +294,3 @@ contract ConfigTest is Test, Config { vm.removeFile(badParseConfig); } } - -/// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, -/// as direct library calls are inlined by the compiler, causing call depth issues. -contract LibVariableTest is Test, Config { - LibVariableHelper helper; - - function setUp() public { - helper = new LibVariableHelper(); - _loadConfig("./test/fixtures/config.toml", false); - } - - function testRevert_NotInitialized() public { - // Try to read a non-existent variable - Variable memory notInit = config.get(1, "non_existent_key"); - - // Test single value types - should revert with NotInitialized - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toBool(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toUint256(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toAddress(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toBytes32(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toString(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toBytes(notInit); - - // Test array types - should also revert with NotInitialized - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toBoolArray(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toUint256Array(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toAddressArray(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toBytes32Array(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toStringArray(notInit); - - vm.expectRevert(LibVariable.NotInitialized.selector); - helper.toBytesArray(notInit); - } - - function testRevert_TypeMismatch() public { - // Get a boolean variable - Variable memory boolVar = config.get(1, "is_live"); - - // Try to coerce it to wrong single value types - should revert with TypeMismatch - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool")); - helper.toUint256(boolVar); - - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "bool")); - helper.toAddress(boolVar); - - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes32", "bool")); - helper.toBytes32(boolVar); - - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "string", "bool")); - helper.toString(boolVar); - - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes", "bool")); - helper.toBytes(boolVar); - - // Get a uint variable - Variable memory uintVar = config.get(1, "number"); - - // Try to coerce it to wrong types - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "uint256")); - helper.toBool(uintVar); - - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "uint256")); - helper.toAddress(uintVar); - - // Get an array variable - Variable memory boolArrayVar = config.get(1, "bool_array"); - - // Try to coerce array to single value - should revert with TypeMismatch - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "bool[]")); - helper.toBool(boolArrayVar); - - // Try to coerce array to wrong array type - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256[]", "bool[]")); - helper.toUint256Array(boolArrayVar); - - // Get a single value and try to coerce to array - Variable memory singleBoolVar = config.get(1, "is_live"); - - vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool[]", "bool")); - helper.toBoolArray(singleBoolVar); - } -} - -/// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, -/// as direct library calls are inlined by the compiler, causing call depth issues. -contract LibVariableHelper { - function toBool(Variable memory v) external pure returns (bool) { - return v.toBool(); - } - - function toUint256(Variable memory v) external pure returns (uint256) { - return v.toUint256(); - } - - function toAddress(Variable memory v) external pure returns (address) { - return v.toAddress(); - } - - function toBytes32(Variable memory v) external pure returns (bytes32) { - return v.toBytes32(); - } - - function toString(Variable memory v) external pure returns (string memory) { - return v.toString(); - } - - function toBytes(Variable memory v) external pure returns (bytes memory) { - return v.toBytes(); - } - - function toBoolArray(Variable memory v) external pure returns (bool[] memory) { - return v.toBoolArray(); - } - - function toUint256Array(Variable memory v) external pure returns (uint256[] memory) { - return v.toUint256Array(); - } - - function toAddressArray(Variable memory v) external pure returns (address[] memory) { - return v.toAddressArray(); - } - - function toBytes32Array(Variable memory v) external pure returns (bytes32[] memory) { - return v.toBytes32Array(); - } - - function toStringArray(Variable memory v) external pure returns (string[] memory) { - return v.toStringArray(); - } - - function toBytesArray(Variable memory v) external pure returns (bytes[] memory) { - return v.toBytesArray(); - } -} diff --git a/test/LibVariable.t.sol b/test/LibVariable.t.sol new file mode 100644 index 00000000..50b67c34 --- /dev/null +++ b/test/LibVariable.t.sol @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test} from "../src/Test.sol"; +import {Variable, Type, TypeKind, LibVariable} from "../src/LibVariable.sol"; + +contract LibVariableTest is Test { + using LibVariable for Type; + using LibVariable for TypeKind; + + LibVariableHelper internal helper; + + bytes internal expectedErr; + Variable internal uninitVar; + Variable internal boolVar; + Variable internal addressVar; + Variable internal bytes32Var; + Variable internal uintVar; + Variable internal intVar; + Variable internal stringVar; + Variable internal bytesVar; + Variable internal boolArrayVar; + Variable internal addressArrayVar; + Variable internal bytes32ArrayVar; + Variable internal uintArrayVar; + Variable internal intArrayVar; + Variable internal stringArrayVar; + Variable internal bytesArrayVar; + + function setUp() public { + helper = new LibVariableHelper(); + + // UNINITIALIZED + uninitVar = Variable(Type(TypeKind.None, false), ""); + + // SINGLE VALUES + boolVar = Variable(Type(TypeKind.Bool, false), abi.encode(true)); + addressVar = Variable(Type(TypeKind.Address, false), abi.encode(address(0xdeadbeef))); + bytes32Var = Variable(Type(TypeKind.Bytes32, false), abi.encode(bytes32(uint256(42)))); + uintVar = Variable(Type(TypeKind.Uint256, false), abi.encode(uint256(123))); + intVar = Variable(Type(TypeKind.Int256, false), abi.encode(int256(-123))); + stringVar = Variable(Type(TypeKind.String, false), abi.encode("hello world")); + bytesVar = Variable(Type(TypeKind.Bytes, false), abi.encode(hex"c0ffee")); + + // ARRAY VALUES + bool[] memory bools = new bool[](2); + bools[0] = true; + bools[1] = false; + boolArrayVar = Variable(Type(TypeKind.Bool, true), abi.encode(bools)); + + address[] memory addrs = new address[](2); + addrs[0] = address(0x1); + addrs[1] = address(0x2); + addressArrayVar = Variable(Type(TypeKind.Address, true), abi.encode(addrs)); + + bytes32[] memory b32s = new bytes32[](2); + b32s[0] = bytes32(uint256(1)); + b32s[1] = bytes32(uint256(2)); + bytes32ArrayVar = Variable(Type(TypeKind.Bytes32, true), abi.encode(b32s)); + + uint256[] memory uints = new uint256[](2); + uints[0] = 1; + uints[1] = 2; + uintArrayVar = Variable(Type(TypeKind.Uint256, true), abi.encode(uints)); + + int256[] memory ints = new int256[](2); + ints[0] = -1; + ints[1] = 2; + intArrayVar = Variable(Type(TypeKind.Int256, true), abi.encode(ints)); + + string[] memory strings = new string[](2); + strings[0] = "one"; + strings[1] = "two"; + stringArrayVar = Variable(Type(TypeKind.String, true), abi.encode(strings)); + + bytes[] memory b = new bytes[](2); + b[0] = hex"01"; + b[1] = hex"02"; + bytesArrayVar = Variable(Type(TypeKind.Bytes, true), abi.encode(b)); + } + + // -- SUCCESS CASES -------------------------------------------------------- + + function test_TypeHelpers() public view { + // TypeKind.toString() + assertEq(TypeKind.None.toString(), "none"); + assertEq(TypeKind.Bool.toString(), "bool"); + assertEq(TypeKind.Address.toString(), "address"); + assertEq(TypeKind.Bytes32.toString(), "bytes32"); + assertEq(TypeKind.Uint256.toString(), "uint256"); + assertEq(TypeKind.Int256.toString(), "int256"); + assertEq(TypeKind.String.toString(), "string"); + assertEq(TypeKind.Bytes.toString(), "bytes"); + + // TypeKind.toTomlKey() + assertEq(TypeKind.Uint256.toTomlKey(), "uint"); + assertEq(TypeKind.Int256.toTomlKey(), "int"); + assertEq(TypeKind.Bytes32.toTomlKey(), "bytes32"); + + // Type.toString() + assertEq(boolVar.ty.toString(), "bool"); + assertEq(boolArrayVar.ty.toString(), "bool[]"); + assertEq(uintVar.ty.toString(), "uint256"); + assertEq(uintArrayVar.ty.toString(), "uint256[]"); + assertEq(uninitVar.ty.toString(), "none"); + + // Type.isEqual() + assertTrue(boolVar.ty.isEqual(Type(TypeKind.Bool, false))); + assertFalse(boolVar.ty.isEqual(Type(TypeKind.Bool, true))); + assertFalse(boolVar.ty.isEqual(Type(TypeKind.Address, false))); + + // Type.assertEq() + boolVar.ty.assertEq(Type(TypeKind.Bool, false)); + uintArrayVar.ty.assertEq(Type(TypeKind.Uint256, true)); + } + + function test_Coercion() public view { + // Single values + assertTrue(helper.toBool(boolVar)); + assertEq(helper.toAddress(addressVar), address(0xdeadbeef)); + assertEq(helper.toBytes32(bytes32Var), bytes32(uint256(42))); + assertEq(helper.toUint256(uintVar), 123); + assertEq(helper.toInt256(intVar), -123); + assertEq(helper.toString(stringVar), "hello world"); + assertEq(helper.toBytes(bytesVar), hex"c0ffee"); + + // Bool array + bool[] memory bools = helper.toBoolArray(boolArrayVar); + assertEq(bools.length, 2); + assertTrue(bools[0]); + assertFalse(bools[1]); + + // Address array + address[] memory addrs = helper.toAddressArray(addressArrayVar); + assertEq(addrs.length, 2); + assertEq(addrs[0], address(0x1)); + assertEq(addrs[1], address(0x2)); + + // String array + string[] memory strings = helper.toStringArray(stringArrayVar); + assertEq(strings.length, 2); + assertEq(strings[0], "one"); + assertEq(strings[1], "two"); + } + + function test_Downcasting() public view { + // Uint downcasting + Variable memory v_uint_small = Variable(Type(TypeKind.Uint256, false), abi.encode(uint256(100))); + assertEq(helper.toUint128(v_uint_small), 100); + assertEq(helper.toUint64(v_uint_small), 100); + assertEq(helper.toUint32(v_uint_small), 100); + assertEq(helper.toUint16(v_uint_small), 100); + assertEq(helper.toUint8(v_uint_small), 100); + + // Uint array downcasting + uint256[] memory small_uints = new uint256[](2); + small_uints[0] = 10; + small_uints[1] = 20; + Variable memory v_uint_array_small = Variable(Type(TypeKind.Uint256, true), abi.encode(small_uints)); + uint8[] memory u8_array = helper.toUint8Array(v_uint_array_small); + assertEq(u8_array[0], 10); + assertEq(u8_array[1], 20); + + // Int downcasting + Variable memory v_int_small_pos = Variable(Type(TypeKind.Int256, false), abi.encode(int256(100))); + Variable memory v_int_small_neg = Variable(Type(TypeKind.Int256, false), abi.encode(int256(-100))); + assertEq(helper.toInt128(v_int_small_pos), 100); + assertEq(helper.toInt64(v_int_small_neg), -100); + assertEq(helper.toInt32(v_int_small_pos), 100); + assertEq(helper.toInt16(v_int_small_neg), -100); + assertEq(helper.toInt8(v_int_small_pos), 100); + + // Int array downcasting + int256[] memory small_ints = new int256[](2); + small_ints[0] = -10; + small_ints[1] = 20; + Variable memory intArraySmall = Variable(Type(TypeKind.Int256, true), abi.encode(small_ints)); + int8[] memory i8_array = helper.toInt8Array(intArraySmall); + assertEq(i8_array[0], -10); + assertEq(i8_array[1], 20); + } + + + // -- REVERT CASES --------------------------------------------------------- + + function testRevert_NotInitialized() public { + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toBool(uninitVar); + + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.toAddressArray(uninitVar); + } + + function testRevert_assertExists() public { + vm.expectRevert(LibVariable.NotInitialized.selector); + helper.assertExists(uninitVar); + } + + function testRevert_TypeMismatch() public { + // Single values + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool")); + helper.toUint256(boolVar); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "string")); + helper.toAddress(stringVar); + + // Arrays + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256[]", "bool[]")); + helper.toUint256Array(boolArrayVar); + + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address[]", "string[]")); + helper.toAddressArray(stringArrayVar); + + // Single value to array + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool[]", "bool")); + helper.toBoolArray(boolVar); + + // Array to single value + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "bool[]")); + helper.toBool(boolArrayVar); + + // assertEq reverts + vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool")); + helper.assertEq(boolVar.ty, Type(TypeKind.Uint256, false)); + } + + function testRevert_UnsafeCast() public { + // uint overflow + Variable memory uintLarge = Variable(Type(TypeKind.Uint256, false), abi.encode(uint256(type(uint128).max) + 1)); + expectedErr = abi.encodeWithSelector(LibVariable.UnsafeCast.selector, "value does not fit in 'uint128'"); + vm.expectRevert(expectedErr); + helper.toUint128(uintLarge); + + // int overflow + Variable memory intLarge = Variable(Type(TypeKind.Int256, false), abi.encode(int256(type(int128).max) + 1)); + expectedErr = abi.encodeWithSelector(LibVariable.UnsafeCast.selector, "value does not fit in 'int128'"); + + vm.expectRevert(expectedErr); + helper.toInt128(intLarge); + + // int underflow + Variable memory intSmall = Variable(Type(TypeKind.Int256, false), abi.encode(int256(type(int128).min) - 1)); + expectedErr = abi.encodeWithSelector(LibVariable.UnsafeCast.selector, "value does not fit in 'int128'"); + + vm.expectRevert(expectedErr); + helper.toInt128(intSmall); + + // uint array overflow + uint256[] memory uintArray = new uint256[](2); + uintArray[0] = 10; + uintArray[1] = uint256(type(uint64).max) + 1; + Variable memory uintArrayLarge = Variable(Type(TypeKind.Uint256, true), abi.encode(uintArray)); + expectedErr = abi.encodeWithSelector(LibVariable.UnsafeCast.selector, "value in array does not fit in 'uint64'"); + + vm.expectRevert(expectedErr); + helper.toUint64Array(uintArrayLarge); + + // int array overflow + int256[] memory intArray = new int256[](2); + intArray[0] = 10; + intArray[1] = int256(type(int64).max) + 1; + Variable memory intArrayLarge = Variable(Type(TypeKind.Int256, true), abi.encode(intArray)); + expectedErr = abi.encodeWithSelector(LibVariable.UnsafeCast.selector, "value in array does not fit in 'int64'"); + + vm.expectRevert(expectedErr); + helper.toInt64Array(intArrayLarge); + + // int array underflow + intArray[0] = 10; + intArray[1] = int256(type(int64).min) - 1; + Variable memory intArraySmall = Variable(Type(TypeKind.Int256, true), abi.encode(intArray)); + expectedErr = abi.encodeWithSelector(LibVariable.UnsafeCast.selector, "value in array does not fit in 'int64'"); + + vm.expectRevert(expectedErr); + helper.toInt64Array(intArraySmall); + } +} + + +/// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, +/// as direct library calls are inlined by the compiler, causing call depth issues. +contract LibVariableHelper { + using LibVariable for Type; + using LibVariable for TypeKind; + + // Assertions + function assertExists(Variable memory v) external pure { v.assertExists(); } + function assertEq(Type memory t1, Type memory t2) external pure { t1.assertEq(t2); } + + // Single Value Coercion + function toBool(Variable memory v) external pure returns (bool) { return v.toBool(); } + function toAddress(Variable memory v) external pure returns (address) { return v.toAddress(); } + function toBytes32(Variable memory v) external pure returns (bytes32) { return v.toBytes32(); } + function toUint256(Variable memory v) external pure returns (uint256) { return v.toUint256(); } + function toInt256(Variable memory v) external pure returns (int256) { return v.toInt256(); } + function toString(Variable memory v) external pure returns (string memory) { return v.toString(); } + function toBytes(Variable memory v) external pure returns (bytes memory) { return v.toBytes(); } + + // Array Coercion + function toBoolArray(Variable memory v) external pure returns (bool[] memory) { return v.toBoolArray(); } + function toAddressArray(Variable memory v) external pure returns (address[] memory) { return v.toAddressArray(); } + function toBytes32Array(Variable memory v) external pure returns (bytes32[] memory) { return v.toBytes32Array(); } + function toUint256Array(Variable memory v) external pure returns (uint256[] memory) { return v.toUint256Array(); } + function toInt256Array(Variable memory v) external pure returns (int256[] memory) { return v.toInt256Array(); } + function toStringArray(Variable memory v) external pure returns (string[] memory) { return v.toStringArray(); } + function toBytesArray(Variable memory v) external pure returns (bytes[] memory) { return v.toBytesArray(); } + + // Uint Downcasting + function toUint128(Variable memory v) external pure returns (uint128) { return v.toUint128(); } + function toUint64(Variable memory v) external pure returns (uint64) { return v.toUint64(); } + function toUint32(Variable memory v) external pure returns (uint32) { return v.toUint32(); } + function toUint16(Variable memory v) external pure returns (uint16) { return v.toUint16(); } + function toUint8(Variable memory v) external pure returns (uint8) { return v.toUint8(); } + + // Int Downcasting + function toInt128(Variable memory v) external pure returns (int128) { return v.toInt128(); } + function toInt64(Variable memory v) external pure returns (int64) { return v.toInt64(); } + function toInt32(Variable memory v) external pure returns (int32) { return v.toInt32(); } + function toInt16(Variable memory v) external pure returns (int16) { return v.toInt16(); } + function toInt8(Variable memory v) external pure returns (int8) { return v.toInt8(); } + + // Uint Array Downcasting + function toUint128Array(Variable memory v) external pure returns (uint128[] memory) { return v.toUint128Array(); } + function toUint64Array(Variable memory v) external pure returns (uint64[] memory) { return v.toUint64Array(); } + function toUint32Array(Variable memory v) external pure returns (uint32[] memory) { return v.toUint32Array(); } + function toUint16Array(Variable memory v) external pure returns (uint16[] memory) { return v.toUint16Array(); } + function toUint8Array(Variable memory v) external pure returns (uint8[] memory) { return v.toUint8Array(); } + + // Int Array Downcasting + function toInt128Array(Variable memory v) external pure returns (int128[] memory) { return v.toInt128Array(); } + function toInt64Array(Variable memory v) external pure returns (int64[] memory) { return v.toInt64Array(); } + function toInt32Array(Variable memory v) external pure returns (int32[] memory) { return v.toInt32Array(); } + function toInt16Array(Variable memory v) external pure returns (int16[] memory) { return v.toInt16Array(); } + function toInt8Array(Variable memory v) external pure returns (int8[] memory) { return v.toInt8Array(); } +} diff --git a/test/fixtures/config.toml b/test/fixtures/config.toml index 3597f90c..e6dcccca 100644 --- a/test/fixtures/config.toml +++ b/test/fixtures/config.toml @@ -22,6 +22,10 @@ deps = [ number = 1234 number_array = [5678, 9999] +[mainnet.int] +signed_number = -1234 +signed_number_array = [-5678, 9999] + [mainnet.bytes32] word = "0x00000000000000000000000000000000000000000000000000000000000004d2" # 1234 word_array = [ @@ -57,6 +61,10 @@ deps = [ number = 9999 number_array = [1234, 5678] +[optimism.int] +signed_number = 9999 +signed_number_array = [-1234, -5678] + [optimism.bytes32] word = "0x000000000000000000000000000000000000000000000000000000000000270f" # 9999 word_array = [ From a686262939453444a051e1da3debc251a0dbe489 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 27 Aug 2025 09:33:47 +0200 Subject: [PATCH 42/54] style: fmt --- test/LibVariable.t.sol | 175 ++++++++++++++++++++++++++++++++--------- 1 file changed, 137 insertions(+), 38 deletions(-) diff --git a/test/LibVariable.t.sol b/test/LibVariable.t.sol index 50b67c34..2fc00a91 100644 --- a/test/LibVariable.t.sol +++ b/test/LibVariable.t.sol @@ -180,7 +180,6 @@ contract LibVariableTest is Test { assertEq(i8_array[1], 20); } - // -- REVERT CASES --------------------------------------------------------- function testRevert_NotInitialized() public { @@ -276,7 +275,6 @@ contract LibVariableTest is Test { } } - /// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`, /// as direct library calls are inlined by the compiler, causing call depth issues. contract LibVariableHelper { @@ -284,52 +282,153 @@ contract LibVariableHelper { using LibVariable for TypeKind; // Assertions - function assertExists(Variable memory v) external pure { v.assertExists(); } - function assertEq(Type memory t1, Type memory t2) external pure { t1.assertEq(t2); } + function assertExists(Variable memory v) external pure { + v.assertExists(); + } + + function assertEq(Type memory t1, Type memory t2) external pure { + t1.assertEq(t2); + } // Single Value Coercion - function toBool(Variable memory v) external pure returns (bool) { return v.toBool(); } - function toAddress(Variable memory v) external pure returns (address) { return v.toAddress(); } - function toBytes32(Variable memory v) external pure returns (bytes32) { return v.toBytes32(); } - function toUint256(Variable memory v) external pure returns (uint256) { return v.toUint256(); } - function toInt256(Variable memory v) external pure returns (int256) { return v.toInt256(); } - function toString(Variable memory v) external pure returns (string memory) { return v.toString(); } - function toBytes(Variable memory v) external pure returns (bytes memory) { return v.toBytes(); } + function toBool(Variable memory v) external pure returns (bool) { + return v.toBool(); + } + + function toAddress(Variable memory v) external pure returns (address) { + return v.toAddress(); + } + + function toBytes32(Variable memory v) external pure returns (bytes32) { + return v.toBytes32(); + } + + function toUint256(Variable memory v) external pure returns (uint256) { + return v.toUint256(); + } + + function toInt256(Variable memory v) external pure returns (int256) { + return v.toInt256(); + } + + function toString(Variable memory v) external pure returns (string memory) { + return v.toString(); + } + + function toBytes(Variable memory v) external pure returns (bytes memory) { + return v.toBytes(); + } // Array Coercion - function toBoolArray(Variable memory v) external pure returns (bool[] memory) { return v.toBoolArray(); } - function toAddressArray(Variable memory v) external pure returns (address[] memory) { return v.toAddressArray(); } - function toBytes32Array(Variable memory v) external pure returns (bytes32[] memory) { return v.toBytes32Array(); } - function toUint256Array(Variable memory v) external pure returns (uint256[] memory) { return v.toUint256Array(); } - function toInt256Array(Variable memory v) external pure returns (int256[] memory) { return v.toInt256Array(); } - function toStringArray(Variable memory v) external pure returns (string[] memory) { return v.toStringArray(); } - function toBytesArray(Variable memory v) external pure returns (bytes[] memory) { return v.toBytesArray(); } + function toBoolArray(Variable memory v) external pure returns (bool[] memory) { + return v.toBoolArray(); + } + + function toAddressArray(Variable memory v) external pure returns (address[] memory) { + return v.toAddressArray(); + } + + function toBytes32Array(Variable memory v) external pure returns (bytes32[] memory) { + return v.toBytes32Array(); + } + + function toUint256Array(Variable memory v) external pure returns (uint256[] memory) { + return v.toUint256Array(); + } + + function toInt256Array(Variable memory v) external pure returns (int256[] memory) { + return v.toInt256Array(); + } + + function toStringArray(Variable memory v) external pure returns (string[] memory) { + return v.toStringArray(); + } + + function toBytesArray(Variable memory v) external pure returns (bytes[] memory) { + return v.toBytesArray(); + } // Uint Downcasting - function toUint128(Variable memory v) external pure returns (uint128) { return v.toUint128(); } - function toUint64(Variable memory v) external pure returns (uint64) { return v.toUint64(); } - function toUint32(Variable memory v) external pure returns (uint32) { return v.toUint32(); } - function toUint16(Variable memory v) external pure returns (uint16) { return v.toUint16(); } - function toUint8(Variable memory v) external pure returns (uint8) { return v.toUint8(); } + function toUint128(Variable memory v) external pure returns (uint128) { + return v.toUint128(); + } + + function toUint64(Variable memory v) external pure returns (uint64) { + return v.toUint64(); + } + + function toUint32(Variable memory v) external pure returns (uint32) { + return v.toUint32(); + } + + function toUint16(Variable memory v) external pure returns (uint16) { + return v.toUint16(); + } + + function toUint8(Variable memory v) external pure returns (uint8) { + return v.toUint8(); + } // Int Downcasting - function toInt128(Variable memory v) external pure returns (int128) { return v.toInt128(); } - function toInt64(Variable memory v) external pure returns (int64) { return v.toInt64(); } - function toInt32(Variable memory v) external pure returns (int32) { return v.toInt32(); } - function toInt16(Variable memory v) external pure returns (int16) { return v.toInt16(); } - function toInt8(Variable memory v) external pure returns (int8) { return v.toInt8(); } + function toInt128(Variable memory v) external pure returns (int128) { + return v.toInt128(); + } + + function toInt64(Variable memory v) external pure returns (int64) { + return v.toInt64(); + } + + function toInt32(Variable memory v) external pure returns (int32) { + return v.toInt32(); + } + + function toInt16(Variable memory v) external pure returns (int16) { + return v.toInt16(); + } + + function toInt8(Variable memory v) external pure returns (int8) { + return v.toInt8(); + } // Uint Array Downcasting - function toUint128Array(Variable memory v) external pure returns (uint128[] memory) { return v.toUint128Array(); } - function toUint64Array(Variable memory v) external pure returns (uint64[] memory) { return v.toUint64Array(); } - function toUint32Array(Variable memory v) external pure returns (uint32[] memory) { return v.toUint32Array(); } - function toUint16Array(Variable memory v) external pure returns (uint16[] memory) { return v.toUint16Array(); } - function toUint8Array(Variable memory v) external pure returns (uint8[] memory) { return v.toUint8Array(); } + function toUint128Array(Variable memory v) external pure returns (uint128[] memory) { + return v.toUint128Array(); + } + + function toUint64Array(Variable memory v) external pure returns (uint64[] memory) { + return v.toUint64Array(); + } + + function toUint32Array(Variable memory v) external pure returns (uint32[] memory) { + return v.toUint32Array(); + } + + function toUint16Array(Variable memory v) external pure returns (uint16[] memory) { + return v.toUint16Array(); + } + + function toUint8Array(Variable memory v) external pure returns (uint8[] memory) { + return v.toUint8Array(); + } // Int Array Downcasting - function toInt128Array(Variable memory v) external pure returns (int128[] memory) { return v.toInt128Array(); } - function toInt64Array(Variable memory v) external pure returns (int64[] memory) { return v.toInt64Array(); } - function toInt32Array(Variable memory v) external pure returns (int32[] memory) { return v.toInt32Array(); } - function toInt16Array(Variable memory v) external pure returns (int16[] memory) { return v.toInt16Array(); } - function toInt8Array(Variable memory v) external pure returns (int8[] memory) { return v.toInt8Array(); } + function toInt128Array(Variable memory v) external pure returns (int128[] memory) { + return v.toInt128Array(); + } + + function toInt64Array(Variable memory v) external pure returns (int64[] memory) { + return v.toInt64Array(); + } + + function toInt32Array(Variable memory v) external pure returns (int32[] memory) { + return v.toInt32Array(); + } + + function toInt16Array(Variable memory v) external pure returns (int16[] memory) { + return v.toInt16Array(); + } + + function toInt8Array(Variable memory v) external pure returns (int8[] memory) { + return v.toInt8Array(); + } } From e764436a4dca1bae3e25294cb4ea515f1fca0bec Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 27 Aug 2025 10:15:11 +0200 Subject: [PATCH 43/54] tests for fn `writeUpdatesBackToFile()` --- src/StdConfig.sol | 3 ++- test/Config.t.sol | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 4e5e8140..f7060a4a 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -77,6 +77,7 @@ contract StdConfig { /// parsed as either, the constructor will revert with an error. /// /// @param configFilePath: The local path to the TOML configuration file. + /// @param writeToFile: Whether to write updates back to the TOML file. constructor(string memory configFilePath, bool writeToFile) { _filePath = configFilePath; _writeToFile = writeToFile; @@ -222,7 +223,7 @@ contract StdConfig { // -- HELPER FUNCTIONS ----------------------------------------------------- /// @notice Enable or disable automatic writing to the TOML file on `set`. - function setAutoWrite(bool enabled) public { + function writeUpdatesBackToFile(bool enabled) public { _writeToFile = enabled; } diff --git a/test/Config.t.sol b/test/Config.t.sol index 6da0e5fd..2dd8a37f 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -237,6 +237,40 @@ contract ConfigTest is Test, Config { vm.removeFile(testConfig); } + function test_writeUpdatesBackToFile() public { + // Create a temporary copy of the config file to avoid modifying the original. + string memory originalConfig = "./test/fixtures/config.toml"; + string memory testConfig = "./test/fixtures/write_config.t.toml"; + vm.copyFile(originalConfig, testConfig); + + // Deploy the config contract with `writeToFile = false` (disabled). + _loadConfig(testConfig, false); + + // Update a single boolean value and verify the file is NOT changed. + config.set(1, "is_live", false); + string memory content = vm.readFile(testConfig); + assertTrue(vm.parseTomlBool(content, "$.mainnet.bool.is_live"), "File should not be updated yet"); + + // Enable writing to file. + config.writeUpdatesBackToFile(true); + + // Update the value again and verify the file IS changed. + config.set(1, "is_live", false); + content = vm.readFile(testConfig); + assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live"), "File should be updated now"); + + // Disable writing to file. + config.writeUpdatesBackToFile(false); + + // Update the value again and verify the file is NOT changed. + config.set(1, "is_live", true); + content = vm.readFile(testConfig); + assertFalse(vm.parseTomlBool(content, "$.mainnet.bool.is_live"), "File should not be updated again"); + + // Clean up the temporary file. + vm.removeFile(testConfig); + } + function testRevert_InvalidChainKey() public { // Create a fixture with an invalid chain key string memory invalidChainConfig = "./test/fixtures/config_invalid_chain.toml"; From 8afa8792985dfeaad7d567f518afa69624bb8508 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 27 Aug 2025 18:53:42 +0200 Subject: [PATCH 44/54] fix: rmv .env file --- .env | 7 ------- test/Config.t.sol | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 7d78d054..00000000 --- a/.env +++ /dev/null @@ -1,7 +0,0 @@ -# ETHEREUM MAINNET -MAINNET_RPC="https://eth.llamarpc.com" -WETH_MAINNET="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" - -# OPTIMISM -OPTIMISM_RPC="https://mainnet.optimism.io" -WETH_OPTIMISM="0x4200000000000000000000000000000000000006" diff --git a/test/Config.t.sol b/test/Config.t.sol index 2dd8a37f..d8abaa5e 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -6,6 +6,13 @@ import {Config} from "../src/Config.sol"; import {StdConfig} from "../src/StdConfig.sol"; contract ConfigTest is Test, Config { + function setUp() public { + vm.setEnv("MAINNET_RPC", "https://eth.llamarpc.com"); + vm.setEnv("WETH_MAINNET", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + vm.setEnv("OPTIMISM_RPC", "https://mainnet.optimism.io"); + vm.setEnv("WETH_OPTIMISM", "0x4200000000000000000000000000000000000006"); + } + function test_loadConfig() public { // Deploy the config contract with the test fixture. _loadConfig("./test/fixtures/config.toml", false); From 0f97a2f7b00e672818bc9b42a0801f7d15a0a531 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 3 Sep 2025 11:18:06 +0200 Subject: [PATCH 45/54] fix: use `string.concat()` again --- src/StdConfig.sol | 73 ++++++++++++++++++----------------------------- test/Config.t.sol | 42 ++++++++++++--------------- 2 files changed, 46 insertions(+), 69 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index f7060a4a..7446c2db 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -88,7 +88,7 @@ contract StdConfig { for (uint256 i = 0; i < chain_keys.length; i++) { string memory chain_key = chain_keys[i]; // Ignore top-level keys that are not tables - if (vm.parseTomlKeys(content, _concat("$.", chain_key)).length == 0) { + if (vm.parseTomlKeys(content, string.concat("$.", chain_key)).length == 0) { continue; } uint256 chainId = resolveChainId(chain_key); @@ -96,7 +96,7 @@ contract StdConfig { // Cache the configure rpc endpoint for that chain. // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured. - try vm.parseTomlString(content, _concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { + try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { _rpcOf[chainId] = vm.resolveEnv(url); } catch { _rpcOf[chainId] = vm.resolveEnv(vm.rpcUrl(chain_key)); @@ -105,13 +105,13 @@ contract StdConfig { // Iterate through all the available `TypeKind`s (except `None`) to create the sub-section paths for (uint8 t = 1; t <= NUM_TYPES; t++) { TypeKind ty = TypeKind(t); - string memory typePath = _concat("$.", chain_key, ".", ty.toTomlKey()); + string memory typePath = string.concat("$.", chain_key, ".", ty.toTomlKey()); try vm.parseTomlKeys(content, typePath) returns (string[] memory keys) { for (uint256 j = 0; j < keys.length; j++) { string memory key = keys[j]; if (_typeOf[chainId][key].kind == TypeKind.None) { - _loadAndCacheValue(content, _concat(typePath, ".", key), chainId, key, ty); + _loadAndCacheValue(content, string.concat(typePath, ".", key), chainId, key, ty); } else { revert AlreadyInitialized(key); } @@ -266,28 +266,9 @@ contract StdConfig { } } - /// @dev concatenates two strings - function _concat(string memory s1, string memory s2) private pure returns (string memory) { - return string(abi.encodePacked(s1, s2)); - } - - /// @dev concatenates three strings - function _concat(string memory s1, string memory s2, string memory s3) private pure returns (string memory) { - return string(abi.encodePacked(s1, s2, s3)); - } - - /// @dev concatenates four strings - function _concat(string memory s1, string memory s2, string memory s3, string memory s4) - private - pure - returns (string memory) - { - return string(abi.encodePacked(s1, s2, s3, s4)); - } - /// @dev Wraps a string in double quotes for JSON compatibility. function _quote(string memory s) private pure returns (string memory) { - return _concat('"', s, '"'); + return string.concat('"', s, '"'); } /// @dev Writes a JSON-formatted value to a specific key in the TOML file. @@ -297,7 +278,7 @@ contract StdConfig { /// @param jsonValue The JSON-formatted value to write. function _writeToToml(uint256 chainId, string memory ty, string memory key, string memory jsonValue) private { string memory chainKey = _getChainKeyFromId(chainId); - string memory valueKey = _concat("$.", chainKey, ".", _concat(ty, ".", key)); + string memory valueKey = string.concat("$.", chainKey, ".", ty, ".", key); vm.writeTomlUpsert(jsonValue, _filePath, valueKey); } @@ -462,10 +443,10 @@ contract StdConfig { if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, vm.toString(value[i])); - if (i < value.length - 1) json = _concat(json, ","); + json = string.concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); } - json = _concat(json, "]"); + json = string.concat(json, "]"); _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -485,10 +466,10 @@ contract StdConfig { if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = _concat(json, ","); + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); } - json = _concat(json, "]"); + json = string.concat(json, "]"); _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -508,10 +489,10 @@ contract StdConfig { if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = _concat(json, ","); + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); } - json = _concat(json, "]"); + json = string.concat(json, "]"); _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -531,10 +512,10 @@ contract StdConfig { if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, vm.toString(value[i])); - if (i < value.length - 1) json = _concat(json, ","); + json = string.concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); } - json = _concat(json, "]"); + json = string.concat(json, "]"); _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -554,10 +535,10 @@ contract StdConfig { if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, vm.toString(value[i])); - if (i < value.length - 1) json = _concat(json, ","); + json = string.concat(json, vm.toString(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); } - json = _concat(json, "]"); + json = string.concat(json, "]"); _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -577,10 +558,10 @@ contract StdConfig { if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, _quote(value[i])); - if (i < value.length - 1) json = _concat(json, ","); + json = string.concat(json, _quote(value[i])); + if (i < value.length - 1) json = string.concat(json, ","); } - json = _concat(json, "]"); + json = string.concat(json, "]"); _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } @@ -600,10 +581,10 @@ contract StdConfig { if (_writeToFile) { string memory json = "["; for (uint256 i = 0; i < value.length; i++) { - json = _concat(json, _quote(vm.toString(value[i]))); - if (i < value.length - 1) json = _concat(json, ","); + json = string.concat(json, _quote(vm.toString(value[i]))); + if (i < value.length - 1) json = string.concat(json, ","); } - json = _concat(json, "]"); + json = string.concat(json, "]"); _writeToToml(chainId, ty.kind.toTomlKey(), key, json); } } diff --git a/test/Config.t.sol b/test/Config.t.sol index d8abaa5e..1977cc50 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -283,21 +283,19 @@ contract ConfigTest is Test, Config { string memory invalidChainConfig = "./test/fixtures/config_invalid_chain.toml"; vm.writeFile( invalidChainConfig, - string( - abi.encodePacked( - "[mainnet]\n", - "endpoint_url = \"https://eth.llamarpc.com\"\n", - "\n", - "[mainnet.uint]\n", - "valid_number = 123\n", - "\n", - "# Invalid chain key (not a number and not a valid alias)\n", - "[invalid_chain]\n", - "endpoint_url = \"https://invalid.com\"\n", - "\n", - "[invalid_chain_9999.uint]\n", - "some_value = 456\n" - ) + string.concat( + "[mainnet]\n", + "endpoint_url = \"https://eth.llamarpc.com\"\n", + "\n", + "[mainnet.uint]\n", + "valid_number = 123\n", + "\n", + "# Invalid chain key (not a number and not a valid alias)\n", + "[invalid_chain]\n", + "endpoint_url = \"https://invalid.com\"\n", + "\n", + "[invalid_chain_9999.uint]\n", + "some_value = 456\n" ) ); @@ -319,14 +317,12 @@ contract ConfigTest is Test, Config { string memory badParseConfig = "./test/fixtures/config_bad_parse.toml"; vm.writeFile( badParseConfig, - string( - abi.encodePacked( - "[mainnet]\n", - "endpoint_url = \"https://eth.llamarpc.com\"\n", - "\n", - "[mainnet.uint]\n", - "bad_value = \"not_a_number\"\n" - ) + string.concat( + "[mainnet]\n", + "endpoint_url = \"https://eth.llamarpc.com\"\n", + "\n", + "[mainnet.uint]\n", + "bad_value = \"not_a_number\"\n" ) ); From a59383aaf5f1962dabce11804af5db6041eedf53 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 3 Sep 2025 11:18:51 +0200 Subject: [PATCH 46/54] style: fmt --- src/StdConfig.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 7446c2db..2765d874 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -96,7 +96,8 @@ contract StdConfig { // Cache the configure rpc endpoint for that chain. // Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured. - try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (string memory url) { + try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (string memory url) + { _rpcOf[chainId] = vm.resolveEnv(url); } catch { _rpcOf[chainId] = vm.resolveEnv(vm.rpcUrl(chain_key)); From 93c3d258b4ea293ac64f5a087cb5d4c38b0d0863 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 3 Sep 2025 11:25:54 +0200 Subject: [PATCH 47/54] fix: more `string.concat()` --- src/Config.sol | 2 +- src/LibVariable.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Config.sol b/src/Config.sol index f3e10db2..1c63c872 100644 --- a/src/Config.sol +++ b/src/Config.sol @@ -28,7 +28,7 @@ abstract contract Config is CommonBase { /// @param writeToFile: whether updates are written back to the TOML file. function _loadConfig(string memory filePath, bool writeToFile) internal { console.log("----------"); - console.log(string(abi.encodePacked("Loading config from '", filePath, "'"))); + console.log(string.concat("Loading config from '", filePath, "'")); config = new StdConfig(filePath, writeToFile); vm.makePersistent(address(config)); console.log("Config successfully loaded"); diff --git a/src/LibVariable.sol b/src/LibVariable.sol index a2618942..c46b1532 100644 --- a/src/LibVariable.sol +++ b/src/LibVariable.sol @@ -77,7 +77,7 @@ library LibVariable { if (!self.isArray || self.kind == TypeKind.None) { return tyStr; } else { - return string(abi.encodePacked(tyStr, "[]")); + return string.concat(tyStr, "[]"); } } From b4ce8ee1ff16c9e7a71d9c92d3eb2216f4d6cabb Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 3 Sep 2025 11:58:01 +0200 Subject: [PATCH 48/54] chore: run `vm.py` --- src/StdConfig.sol | 2 +- src/Vm.sol | 12 ++---------- test/Vm.t.sol | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 2765d874..172f5a43 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -280,7 +280,7 @@ contract StdConfig { function _writeToToml(uint256 chainId, string memory ty, string memory key, string memory jsonValue) private { string memory chainKey = _getChainKeyFromId(chainId); string memory valueKey = string.concat("$.", chainKey, ".", ty, ".", key); - vm.writeTomlUpsert(jsonValue, _filePath, valueKey); + vm.writeToml(jsonValue, _filePath, valueKey); } // -- GETTER FUNCTIONS ----------------------------------------------------- diff --git a/src/Vm.sol b/src/Vm.sol index 0f8f9b68..cd883706 100644 --- a/src/Vm.sol +++ b/src/Vm.sol @@ -1129,6 +1129,7 @@ interface VmSafe { /// 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. + /// This cheatcode will create new keys if they didn't previously exist. function writeJson(string calldata json, string calldata path, string calldata valueKey) external; /// Checks if `key` exists in a JSON object @@ -1849,21 +1850,12 @@ interface VmSafe { /// ABI-encodes a TOML table at `key`. function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); - /// 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. - /// Unlike `writeJson`, this cheatcode will create new keys if they didn't previously exist. - function writeJsonUpsert(string calldata json, string calldata path, string calldata valueKey) external; - - /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = - /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. - /// Unlike `writeToml`, this cheatcode will create new keys if they didn't previously exist. - function writeTomlUpsert(string calldata json, string calldata path, string calldata valueKey) external; - /// Takes serialized JSON, converts to TOML and write a serialized TOML to a file. function writeToml(string calldata json, string calldata path) external; /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. + /// This cheatcode will create new keys if they didn't previously exist. function writeToml(string calldata json, string calldata path, string calldata valueKey) external; // ======== Utilities ======== diff --git a/test/Vm.t.sol b/test/Vm.t.sol index af17736f..1b99e3db 100644 --- a/test/Vm.t.sol +++ b/test/Vm.t.sol @@ -13,6 +13,6 @@ contract VmTest is Test { } function test_VmSafeInterfaceId() public pure { - assertEq(type(VmSafe).interfaceId, bytes4(0x42e95499), "VmSafe"); + assertEq(type(VmSafe).interfaceId, bytes4(0xe02727c3), "VmSafe"); } } From c49c8ab79e3b9e9b12796ee1f8e57987655f1f3c Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 3 Sep 2025 15:26:38 +0200 Subject: [PATCH 49/54] update solc ignored error codes --- foundry.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foundry.toml b/foundry.toml index d9322989..b68bd546 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,8 +4,8 @@ optimizer = true optimizer_runs = 200 # A list of solidity error codes to ignore. -# 2462 = "Visibility for constructor is ignored." -ignored_error_codes = [2462] +# 3860 = init-code-size +ignored_error_codes = [3860] [rpc_endpoints] # The RPC URLs are modified versions of the default for testing initialization. From 75ac3e34ea1d1c10f160048d329668a2c601148d Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 3 Sep 2025 16:53:57 +0200 Subject: [PATCH 50/54] fix: forbid writting to file unless scripting --- src/StdConfig.sol | 13 ++++++++++++- test/Config.t.sol | 29 ++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 172f5a43..b50c3c4e 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -44,6 +44,7 @@ contract StdConfig { error InvalidChainKey(string aliasOrId); error ChainNotInitialized(uint256 chainId); error UnableToParseVariable(string key); + error WriteToFileInForbiddenCtxt(); // -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------ @@ -63,6 +64,7 @@ contract StdConfig { mapping(uint256 => mapping(string => Type)) private _typeOf; /// @dev When enabled, `set` will always write updates back to the configuration file. + /// Can only be enabled when scripting. bool private _writeToFile; // -- CONSTRUCTOR ---------------------------------------------------------- @@ -77,8 +79,12 @@ contract StdConfig { /// parsed as either, the constructor will revert with an error. /// /// @param configFilePath: The local path to the TOML configuration file. - /// @param writeToFile: Whether to write updates back to the TOML file. + /// @param writeToFile: Whether to write updates back to the TOML file. Only for scrips. constructor(string memory configFilePath, bool writeToFile) { + if (writeToFile && !vm.isContext(VmSafe.ForgeContext.ScriptGroup)) { + revert WriteToFileInForbiddenCtxt(); + } + _filePath = configFilePath; _writeToFile = writeToFile; string memory content = vm.resolveEnv(vm.readFile(configFilePath)); @@ -224,7 +230,12 @@ contract StdConfig { // -- HELPER FUNCTIONS ----------------------------------------------------- /// @notice Enable or disable automatic writing to the TOML file on `set`. + /// Can only be enabled when scripting. function writeUpdatesBackToFile(bool enabled) public { + if (enabled && !vm.isContext(VmSafe.ForgeContext.ScriptGroup)) { + revert WriteToFileInForbiddenCtxt(); + } + _writeToFile = enabled; } diff --git a/test/Config.t.sol b/test/Config.t.sol index 1977cc50..8e2342ca 100644 --- a/test/Config.t.sol +++ b/test/Config.t.sol @@ -131,8 +131,11 @@ contract ConfigTest is Test, Config { string memory testConfig = "./test/fixtures/config.t.toml"; vm.copyFile(originalConfig, testConfig); - // Deploy the config contract with the temporary fixture and `writeToFile = true`. - _loadConfig(testConfig, true); + // Deploy the config contract with the temporary fixture. + _loadConfig(testConfig, false); + + // Enable writing to file bypassing the context check. + vm.store(address(config), bytes32(uint256(5)), bytes32(uint256(1))); { // Update a single boolean value and verify the change. @@ -258,8 +261,8 @@ contract ConfigTest is Test, Config { string memory content = vm.readFile(testConfig); assertTrue(vm.parseTomlBool(content, "$.mainnet.bool.is_live"), "File should not be updated yet"); - // Enable writing to file. - config.writeUpdatesBackToFile(true); + // Enable writing to file bypassing the context check. + vm.store(address(config), bytes32(uint256(5)), bytes32(uint256(1))); // Update the value again and verify the file IS changed. config.set(1, "is_live", false); @@ -278,6 +281,19 @@ contract ConfigTest is Test, Config { vm.removeFile(testConfig); } + function testRevert_WriteToFileInForbiddenCtxt() public { + // Cannot initialize enabling writing to file unless we are in SCRIPT mode. + vm.expectRevert(StdConfig.WriteToFileInForbiddenCtxt.selector); + _loadConfig("./test/fixtures/config.toml", true); + + // Initialize with `writeToFile = false`. + _loadConfig("./test/fixtures/config.toml", false); + + // Cannot enable writing to file unless we are in SCRIPT mode. + vm.expectRevert(StdConfig.WriteToFileInForbiddenCtxt.selector); + config.writeUpdatesBackToFile(true); + } + function testRevert_InvalidChainKey() public { // Create a fixture with an invalid chain key string memory invalidChainConfig = "./test/fixtures/config_invalid_chain.toml"; @@ -305,7 +321,10 @@ contract ConfigTest is Test, Config { } function testRevert_ChainNotInitialized() public { - _loadConfig("./test/fixtures/config.toml", true); + _loadConfig("./test/fixtures/config.toml", false); + + // Enable writing to file bypassing the context check. + vm.store(address(config), bytes32(uint256(5)), bytes32(uint256(1))); // Try to write a value for a non-existent chain ID vm.expectRevert(abi.encodeWithSelector(StdConfig.ChainNotInitialized.selector, uint256(999999))); From c086c2ebb8bc9fa4402f6387575b169f115ddccb Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 3 Sep 2025 16:59:55 +0200 Subject: [PATCH 51/54] improve docs --- src/StdConfig.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index b50c3c4e..98315e43 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -64,7 +64,8 @@ contract StdConfig { mapping(uint256 => mapping(string => Type)) private _typeOf; /// @dev When enabled, `set` will always write updates back to the configuration file. - /// Can only be enabled when scripting. + /// Can only be enabled in a scripting context to prevent file corruption from + /// concurrent I/O access, as tests run in parallel. bool private _writeToFile; // -- CONSTRUCTOR ---------------------------------------------------------- From 9802415ed332df81fa369cd05f27106e83da0e94 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:28:28 +0200 Subject: [PATCH 52/54] Update StdConfig.sol Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- src/StdConfig.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 98315e43..288fc388 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -36,6 +36,8 @@ contract StdConfig { using LibVariable for TypeKind; VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + + /// @dev Types: `bool`, `address`, `bytes32`, `uint`, `ìnt`, `string`, `bytes`. uint8 private constant NUM_TYPES = 7; // -- ERRORS --------------------------------------------------------------- From 2788a6cc754615c3ba4cc08c43b73cfab6da3e74 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:29:11 +0200 Subject: [PATCH 53/54] more docs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- src/StdConfig.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 288fc388..12e4c7ad 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -37,6 +37,8 @@ contract StdConfig { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + /// @dev Types: `bool`, `address`, `bytes32`, `uint`, `ìnt`, `string`, `bytes`. + /// @dev Types: `bool`, `address`, `bytes32`, `uint`, `ìnt`, `string`, `bytes`. uint8 private constant NUM_TYPES = 7; From 16d10874b394e7ae9241b9db017bb763ba0774bc Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:47:42 +0200 Subject: [PATCH 54/54] Update src/StdConfig.sol --- src/StdConfig.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/StdConfig.sol b/src/StdConfig.sol index 12e4c7ad..288fc388 100644 --- a/src/StdConfig.sol +++ b/src/StdConfig.sol @@ -37,8 +37,6 @@ contract StdConfig { VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); - /// @dev Types: `bool`, `address`, `bytes32`, `uint`, `ìnt`, `string`, `bytes`. - /// @dev Types: `bool`, `address`, `bytes32`, `uint`, `ìnt`, `string`, `bytes`. uint8 private constant NUM_TYPES = 7;