diff --git a/Cargo.lock b/Cargo.lock index 6053ca5c01566..fac4311005343 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3707,6 +3707,7 @@ dependencies = [ name = "foundry-cheatcodes" version = "1.1.0" dependencies = [ + "alloy-chains", "alloy-consensus", "alloy-dyn-abi", "alloy-genesis", diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 6965ebe0b646c..645426ea8bf29 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -40,6 +40,7 @@ parking_lot.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } alloy-network.workspace = true alloy-rlp.workspace = true +alloy-chains.workspace = true base64.workspace = true dialoguer = "0.11" diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index e6e14c349406f..7cce924b5f60c 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -369,6 +369,32 @@ } ] }, + { + "name": "Chain", + "description": "Information about a blockchain.", + "fields": [ + { + "name": "name", + "ty": "string", + "description": "The chain name." + }, + { + "name": "chainId", + "ty": "uint256", + "description": "The chain's Chain ID." + }, + { + "name": "chainAlias", + "ty": "string", + "description": "The chain's alias. (i.e. what gets specified in `foundry.toml`)." + }, + { + "name": "rpcUrl", + "ty": "string", + "description": "A default RPC endpoint for this chain." + } + ] + }, { "name": "AccountAccess", "description": "The result of a `stopAndReturnStateDiff` call.", @@ -5928,6 +5954,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getChain_0", + "description": "Returns a Chain struct for specific alias", + "declaration": "function getChain(string calldata chainAlias) external view returns (Chain memory chain);", + "visibility": "external", + "mutability": "view", + "signature": "getChain(string)", + "selector": "0x4cc1c2bb", + "selectorBytes": [ + 76, + 193, + 194, + 187 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getChain_1", + "description": "Returns a Chain struct for specific chainId", + "declaration": "function getChain(uint256 chainId) external view returns (Chain memory chain);", + "visibility": "external", + "mutability": "view", + "signature": "getChain(uint256)", + "selector": "0xb6791ad4", + "selectorBytes": [ + 182, + 121, + 26, + 212 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getCode", diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index b7c44f3284b47..7d1dbe33ac9e6 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -83,6 +83,7 @@ impl Cheatcodes<'static> { Vm::Wallet::STRUCT.clone(), Vm::FfiResult::STRUCT.clone(), Vm::ChainInfo::STRUCT.clone(), + Vm::Chain::STRUCT.clone(), Vm::AccountAccess::STRUCT.clone(), Vm::StorageAccess::STRUCT.clone(), Vm::Gas::STRUCT.clone(), diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 1aa5f7890833a..c0481569af296 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -207,6 +207,18 @@ interface Vm { uint256 chainId; } + /// Information about a blockchain. + struct Chain { + /// The chain name. + string name; + /// The chain's Chain ID. + uint256 chainId; + /// The chain's alias. (i.e. what gets specified in `foundry.toml`). + string chainAlias; + /// A default RPC endpoint for this chain. + string rpcUrl; + } + /// The storage accessed during an `AccountAccess`. struct StorageAccess { /// The account whose storage was accessed. @@ -967,6 +979,14 @@ interface Vm { #[cheatcode(group = Testing, safety = Safe)] function rpcUrlStructs() external view returns (Rpc[] memory urls); + /// Returns a Chain struct for specific alias + #[cheatcode(group = Testing, safety = Safe)] + function getChain(string calldata chainAlias) external view returns (Chain memory chain); + + /// Returns a Chain struct for specific chainId + #[cheatcode(group = Testing, safety = Safe)] + function getChain(uint256 chainId) external view returns (Chain memory chain); + /// Suspends execution of the main thread for `duration` milliseconds. #[cheatcode(group = Testing, safety = Safe)] function sleep(uint256 duration) external; diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 3ab70f6e053fb..210c76553cba7 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -9,6 +9,7 @@ use foundry_config::{ }; use foundry_evm_core::opts::EvmOpts; use std::{ + collections::HashMap, path::{Path, PathBuf}, time::Duration, }; @@ -56,6 +57,18 @@ pub struct CheatsConfig { pub seed: Option, /// Whether to allow `expectRevert` to work for internal calls. pub internal_expect_revert: bool, + /// Mapping of chain aliases to chain data + pub chains: HashMap, + /// Mapping of chain IDs to their aliases + pub chain_id_to_alias: HashMap, +} + +/// Chain data for getChain cheatcodes +#[derive(Clone, Debug)] +pub struct ChainData { + pub name: String, + pub chain_id: u64, + pub default_rpc_url: String, // Store default RPC URL } impl CheatsConfig { @@ -96,6 +109,8 @@ impl CheatsConfig { assertions_revert: config.assertions_revert, seed: config.fuzz.seed, internal_expect_revert: config.allow_internal_expect_revert, + chains: HashMap::new(), + chain_id_to_alias: HashMap::new(), } } @@ -203,6 +218,77 @@ impl CheatsConfig { } Ok(urls) } + + /// Initialize default chain data (similar to initializeStdChains in Solidity) + pub fn initialize_chain_data(&mut self) { + if !self.chains.is_empty() { + return; // Already initialized + } + + // Use the same function to create chains + let chains = create_default_chains(); + + // Add all chains to the config + for (alias, data) in chains { + self.set_chain_with_default_rpc_url(&alias, data); + } + } + + /// Set chain with default RPC URL (similar to setChainWithDefaultRpcUrl in Solidity) + pub fn set_chain_with_default_rpc_url(&mut self, alias: &str, data: ChainData) { + // Store the default RPC URL is already stored in the data + // No need to clone it separately + + // Add chain data + self.set_chain_data(alias, data); + } + + /// Set chain data for a specific alias + pub fn set_chain_data(&mut self, alias: &str, data: ChainData) { + // Remove old chain ID mapping if it exists + if let Some(old_data) = self.chains.get(alias) { + self.chain_id_to_alias.remove(&old_data.chain_id); + } + + // Add new mappings + self.chain_id_to_alias.insert(data.chain_id, alias.to_string()); + self.chains.insert(alias.to_string(), data); + } + + /// Get chain data by alias + pub fn get_chain_data_by_alias_non_mut(&self, alias: &str) -> Result { + // Initialize chains if not already done + if self.chains.is_empty() { + // Create a temporary copy with initialized chains + // This is inefficient but handles the edge case + let temp_chains = create_default_chains(); + + if let Some(data) = temp_chains.get(alias) { + return Ok(data.clone()); + } + } else { + // Normal path - chains are initialized + if let Some(data) = self.chains.get(alias) { + return Ok(data.clone()); + } + } + + // Chain not found in either case + Err(fmt_err!("vm.getChain: Chain with alias \"{}\" not found", alias)) + } + + /// Get RPC URL for an alias + pub fn get_rpc_url_non_mut(&self, alias: &str) -> Result { + // Try to get from config first + match self.rpc_endpoint(alias) { + Ok(endpoint) => Ok(endpoint.url()?), + Err(_) => { + // If not in config, try to get default URL + let chain_data = self.get_chain_data_by_alias_non_mut(alias)?; + Ok(chain_data.default_rpc_url) + } + } + } } impl Default for CheatsConfig { @@ -226,10 +312,399 @@ impl Default for CheatsConfig { assertions_revert: true, seed: None, internal_expect_revert: false, + chains: HashMap::new(), + chain_id_to_alias: HashMap::new(), } } } +// Helper function to set default chains +fn create_default_chains() -> HashMap { + let mut chains = HashMap::new(); + + // Define all chains in one place + chains.insert( + "anvil".to_string(), + ChainData { + name: "Anvil".to_string(), + chain_id: 31337, + default_rpc_url: "http://127.0.0.1:8545".to_string(), + }, + ); + + chains.insert( + "mainnet".to_string(), + ChainData { + name: "Mainnet".to_string(), + chain_id: 1, + default_rpc_url: "https://eth.llamarpc.com".to_string(), + }, + ); + + chains.insert( + "sepolia".to_string(), + ChainData { + name: "Sepolia".to_string(), + chain_id: 11155111, + default_rpc_url: "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001" + .to_string(), + }, + ); + + chains.insert( + "holesky".to_string(), + ChainData { + name: "Holesky".to_string(), + chain_id: 17000, + default_rpc_url: "https://rpc.holesky.ethpandaops.io".to_string(), + }, + ); + + chains.insert( + "optimism".to_string(), + ChainData { + name: "Optimism".to_string(), + chain_id: 10, + default_rpc_url: "https://mainnet.optimism.io".to_string(), + }, + ); + + chains.insert( + "optimism_sepolia".to_string(), + ChainData { + name: "Optimism Sepolia".to_string(), + chain_id: 11155420, + default_rpc_url: "https://sepolia.optimism.io".to_string(), + }, + ); + + chains.insert( + "arbitrum_one".to_string(), + ChainData { + name: "Arbitrum One".to_string(), + chain_id: 42161, + default_rpc_url: "https://arb1.arbitrum.io/rpc".to_string(), + }, + ); + + chains.insert( + "arbitrum_one_sepolia".to_string(), + ChainData { + name: "Arbitrum One Sepolia".to_string(), + chain_id: 421614, + default_rpc_url: "https://sepolia-rollup.arbitrum.io/rpc".to_string(), + }, + ); + + chains.insert( + "arbitrum_nova".to_string(), + ChainData { + name: "Arbitrum Nova".to_string(), + chain_id: 42170, + default_rpc_url: "https://nova.arbitrum.io/rpc".to_string(), + }, + ); + + chains.insert( + "polygon".to_string(), + ChainData { + name: "Polygon".to_string(), + chain_id: 137, + default_rpc_url: "https://polygon-rpc.com".to_string(), + }, + ); + + chains.insert( + "polygon_amoy".to_string(), + ChainData { + name: "Polygon Amoy".to_string(), + chain_id: 80002, + default_rpc_url: "https://rpc-amoy.polygon.technology".to_string(), + }, + ); + + chains.insert( + "avalanche".to_string(), + ChainData { + name: "Avalanche".to_string(), + chain_id: 43114, + default_rpc_url: "https://api.avax.network/ext/bc/C/rpc".to_string(), + }, + ); + + chains.insert( + "avalanche_fuji".to_string(), + ChainData { + name: "Avalanche Fuji".to_string(), + chain_id: 43113, + default_rpc_url: "https://api.avax-test.network/ext/bc/C/rpc".to_string(), + }, + ); + + chains.insert( + "bnb_smart_chain".to_string(), + ChainData { + name: "BNB Smart Chain".to_string(), + chain_id: 56, + default_rpc_url: "https://bsc-dataseed1.binance.org".to_string(), + }, + ); + + chains.insert( + "bnb_smart_chain_testnet".to_string(), + ChainData { + name: "BNB Smart Chain Testnet".to_string(), + chain_id: 97, + default_rpc_url: "https://rpc.ankr.com/bsc_testnet_chapel".to_string(), + }, + ); + + chains.insert( + "gnosis_chain".to_string(), + ChainData { + name: "Gnosis Chain".to_string(), + chain_id: 100, + default_rpc_url: "https://rpc.gnosischain.com".to_string(), + }, + ); + + chains.insert( + "moonbeam".to_string(), + ChainData { + name: "Moonbeam".to_string(), + chain_id: 1284, + default_rpc_url: "https://rpc.api.moonbeam.network".to_string(), + }, + ); + + chains.insert( + "moonriver".to_string(), + ChainData { + name: "Moonriver".to_string(), + chain_id: 1285, + default_rpc_url: "https://rpc.api.moonriver.moonbeam.network".to_string(), + }, + ); + + chains.insert( + "moonbase".to_string(), + ChainData { + name: "Moonbase".to_string(), + chain_id: 1287, + default_rpc_url: "https://rpc.testnet.moonbeam.network".to_string(), + }, + ); + + chains.insert( + "base_sepolia".to_string(), + ChainData { + name: "Base Sepolia".to_string(), + chain_id: 84532, + default_rpc_url: "https://sepolia.base.org".to_string(), + }, + ); + + chains.insert( + "base".to_string(), + ChainData { + name: "Base".to_string(), + chain_id: 8453, + default_rpc_url: "https://mainnet.base.org".to_string(), + }, + ); + + chains.insert( + "blast_sepolia".to_string(), + ChainData { + name: "Blast Sepolia".to_string(), + chain_id: 168587773, + default_rpc_url: "https://sepolia.blast.io".to_string(), + }, + ); + + chains.insert( + "blast".to_string(), + ChainData { + name: "Blast".to_string(), + chain_id: 81457, + default_rpc_url: "https://rpc.blast.io".to_string(), + }, + ); + + chains.insert( + "fantom_opera".to_string(), + ChainData { + name: "Fantom Opera".to_string(), + chain_id: 250, + default_rpc_url: "https://rpc.ankr.com/fantom/".to_string(), + }, + ); + + chains.insert( + "fantom_opera_testnet".to_string(), + ChainData { + name: "Fantom Opera Testnet".to_string(), + chain_id: 4002, + default_rpc_url: "https://rpc.ankr.com/fantom_testnet/".to_string(), + }, + ); + + chains.insert( + "fraxtal".to_string(), + ChainData { + name: "Fraxtal".to_string(), + chain_id: 252, + default_rpc_url: "https://rpc.frax.com".to_string(), + }, + ); + + chains.insert( + "fraxtal_testnet".to_string(), + ChainData { + name: "Fraxtal Testnet".to_string(), + chain_id: 2522, + default_rpc_url: "https://rpc.testnet.frax.com".to_string(), + }, + ); + + chains.insert( + "berachain_bartio_testnet".to_string(), + ChainData { + name: "Berachain bArtio Testnet".to_string(), + chain_id: 80084, + default_rpc_url: "https://bartio.rpc.berachain.com".to_string(), + }, + ); + + chains.insert( + "flare".to_string(), + ChainData { + name: "Flare".to_string(), + chain_id: 14, + default_rpc_url: "https://flare-api.flare.network/ext/C/rpc".to_string(), + }, + ); + + chains.insert( + "flare_coston2".to_string(), + ChainData { + name: "Flare Coston2".to_string(), + chain_id: 114, + default_rpc_url: "https://coston2-api.flare.network/ext/C/rpc".to_string(), + }, + ); + + chains.insert( + "mode".to_string(), + ChainData { + name: "Mode".to_string(), + chain_id: 34443, + default_rpc_url: "https://mode.drpc.org".to_string(), + }, + ); + + chains.insert( + "mode_sepolia".to_string(), + ChainData { + name: "Mode Sepolia".to_string(), + chain_id: 919, + default_rpc_url: "https://sepolia.mode.network".to_string(), + }, + ); + + chains.insert( + "zora".to_string(), + ChainData { + name: "Zora".to_string(), + chain_id: 7777777, + default_rpc_url: "https://zora.drpc.org".to_string(), + }, + ); + + chains.insert( + "zora_sepolia".to_string(), + ChainData { + name: "Zora Sepolia".to_string(), + chain_id: 999999999, + default_rpc_url: "https://sepolia.rpc.zora.energy".to_string(), + }, + ); + + chains.insert( + "race".to_string(), + ChainData { + name: "Race".to_string(), + chain_id: 6805, + default_rpc_url: "https://racemainnet.io".to_string(), + }, + ); + + chains.insert( + "race_sepolia".to_string(), + ChainData { + name: "Race Sepolia".to_string(), + chain_id: 6806, + default_rpc_url: "https://racemainnet.io".to_string(), + }, + ); + + chains.insert( + "metal".to_string(), + ChainData { + name: "Metal".to_string(), + chain_id: 1750, + default_rpc_url: "https://metall2.drpc.org".to_string(), + }, + ); + + chains.insert( + "metal_sepolia".to_string(), + ChainData { + name: "Metal Sepolia".to_string(), + chain_id: 1740, + default_rpc_url: "https://testnet.rpc.metall2.com".to_string(), + }, + ); + + chains.insert( + "binary".to_string(), + ChainData { + name: "Binary".to_string(), + chain_id: 624, + default_rpc_url: "https://rpc.zero.thebinaryholdings.com".to_string(), + }, + ); + + chains.insert( + "binary_sepolia".to_string(), + ChainData { + name: "Binary Sepolia".to_string(), + chain_id: 625, + default_rpc_url: "https://rpc.zero.thebinaryholdings.com".to_string(), + }, + ); + + chains.insert( + "orderly".to_string(), + ChainData { + name: "Orderly".to_string(), + chain_id: 291, + default_rpc_url: "https://rpc.orderly.network".to_string(), + }, + ); + + chains.insert( + "orderly_sepolia".to_string(), + ChainData { + name: "Orderly Sepolia".to_string(), + chain_id: 4460, + default_rpc_url: "https://testnet-rpc.orderly.org".to_string(), + }, + ); + + chains +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index c12f4609bdef2..9a59430a9264c 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -1,10 +1,12 @@ //! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; -use alloy_primitives::Address; +use alloy_chains::Chain as AlloyChain; +use alloy_primitives::{Address, U256}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; use foundry_evm_core::constants::MAGIC_SKIP; +use std::str::FromStr; pub(crate) mod assert; pub(crate) mod assume; @@ -84,6 +86,22 @@ impl Cheatcode for skip_1Call { } } +impl Cheatcode for getChain_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { chainAlias } = self; + get_chain(state, chainAlias) + } +} + +impl Cheatcode for getChain_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { chainId } = self; + // Convert the chainId to a string and use the existing get_chain function + let chain_id_str = chainId.to_string(); + get_chain(state, &chain_id_str) + } +} + /// Adds or removes the given breakpoint to the state. fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> Result { let mut chars = s.chars(); @@ -100,3 +118,32 @@ fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> R Ok(Default::default()) } + +/// Gets chain information for the given alias. +fn get_chain(state: &mut Cheatcodes, chain_alias: &str) -> Result { + // Parse the chain alias - works for both chain names and IDs + let alloy_chain = AlloyChain::from_str(chain_alias) + .map_err(|_| fmt_err!("invalid chain alias: {chain_alias}"))?; + + // Check if this is an unknown chain ID by comparing the name to the chain ID + // When a numeric ID is passed for an unknown chain, alloy_chain.to_string() will return the ID + // So if they match, it's likely an unknown chain ID + if alloy_chain.to_string() == alloy_chain.id().to_string() { + return Err(fmt_err!("invalid chain alias: {chain_alias}")); + } + + // First, try to get RPC URL from the user's config in foundry.toml + let rpc_url = state.config.rpc_endpoint(chain_alias).ok().and_then(|e| e.url().ok()); + + // If we couldn't get a URL from config, return an empty string + let rpc_url = rpc_url.unwrap_or_default(); + + let chain_struct = Chain { + name: alloy_chain.to_string(), + chainId: U256::from(alloy_chain.id()), + chainAlias: chain_alias.to_string(), + rpcUrl: rpc_url, + }; + + Ok(chain_struct.abi_encode()) +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 7128dae391ed1..b3a447cc5e1dc 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -18,6 +18,7 @@ interface Vm { struct Wallet { address addr; uint256 publicKeyX; uint256 publicKeyY; uint256 privateKey; } struct FfiResult { int32 exitCode; bytes stdout; bytes stderr; } struct ChainInfo { uint256 forkId; uint256 chainId; } + struct Chain { string name; uint256 chainId; string chainAlias; string rpcUrl; } struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; } struct StorageAccess { address account; bytes32 slot; bool isWrite; bytes32 previousValue; bytes32 newValue; bool reverted; } struct Gas { uint64 gasLimit; uint64 gasTotalUsed; uint64 gasMemoryUsed; int64 gasRefunded; uint64 gasRemaining; } @@ -290,6 +291,8 @@ interface Vm { function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); + function getChain(string calldata chainAlias) external view returns (Chain memory chain); + function getChain(uint256 chainId) external view returns (Chain memory chain); function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); function getDeployment(string calldata contractName) external view returns (address deployedAddress); diff --git a/testdata/default/cheats/GetChain.t.sol b/testdata/default/cheats/GetChain.t.sol new file mode 100644 index 0000000000000..856a6b8a75712 --- /dev/null +++ b/testdata/default/cheats/GetChain.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GetChainTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetMainnet() public { + // Test mainnet + Vm.Chain memory mainnet = vm.getChain("mainnet"); + assertEq(mainnet.name, "mainnet"); + assertEq(mainnet.chainId, 1); + assertEq(mainnet.chainAlias, "mainnet"); + } + + function testGetSepolia() public { + // Test Sepolia + Vm.Chain memory sepolia = vm.getChain("sepolia"); + assertEq(sepolia.name, "sepolia"); + assertEq(sepolia.chainId, 11155111); + assertEq(sepolia.chainAlias, "sepolia"); + } + + function testGetOptimism() public { + // Test Optimism + Vm.Chain memory optimism = vm.getChain("optimism"); + assertEq(optimism.name, "optimism"); + assertEq(optimism.chainId, 10); + assertEq(optimism.chainAlias, "optimism"); + } + + function testGetByChainId() public { + // Test getting a chain by its ID + vm._expectCheatcodeRevert("invalid chain alias:"); + Vm.Chain memory arbitrum = vm.getChain("42161222"); + } + + function testEmptyAlias() public { + // Test empty string + vm._expectCheatcodeRevert("invalid chain alias:"); + vm.getChain(""); + } + + function testInvalidAlias() public { + // Test invalid alias + vm._expectCheatcodeRevert("invalid chain alias: nonexistent_chain"); + vm.getChain("nonexistent_chain"); + } + + // Tests for the numeric chainId version of getChain + + function testGetMainnetById() public { + // Test mainnet using chain ID + Vm.Chain memory mainnet = vm.getChain(1); + assertEq(mainnet.name, "mainnet"); + assertEq(mainnet.chainId, 1); + assertEq(mainnet.chainAlias, "1"); + } + + function testGetSepoliaById() public { + // Test Sepolia using chain ID + Vm.Chain memory sepolia = vm.getChain(11155111); + assertEq(sepolia.name, "sepolia"); + assertEq(sepolia.chainId, 11155111); + assertEq(sepolia.chainAlias, "11155111"); + } + + function testGetOptimismById() public { + // Test Optimism using chain ID + Vm.Chain memory optimism = vm.getChain(10); + assertEq(optimism.name, "optimism"); + assertEq(optimism.chainId, 10); + assertEq(optimism.chainAlias, "10"); + } + + function testGetArbitrumById() public { + // Test Arbitrum using chain ID + Vm.Chain memory arbitrum = vm.getChain(42161); + assertEq(arbitrum.name, "arbitrum"); + assertEq(arbitrum.chainId, 42161); + assertEq(arbitrum.chainAlias, "42161"); + } + + function testInvalidChainId() public { + // Test invalid chain ID (using a value that's unlikely to be a valid chain) + vm._expectCheatcodeRevert("invalid chain alias: 12345678"); + vm.getChain(12345678); + } +}