foundry_cheatcodes/
test.rs

1//! Implementations of [`Testing`](spec::Group::Testing) cheatcodes.
2
3use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*};
4use alloy_chains::Chain as AlloyChain;
5use alloy_primitives::{Address, U256};
6use alloy_sol_types::SolValue;
7use foundry_common::version::SEMVER_VERSION;
8use foundry_evm_core::constants::MAGIC_SKIP;
9use std::str::FromStr;
10
11pub(crate) mod assert;
12pub(crate) mod assume;
13pub(crate) mod expect;
14pub(crate) mod revert_handlers;
15
16impl Cheatcode for breakpoint_0Call {
17    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
18        let Self { char } = self;
19        breakpoint(ccx.state, &ccx.caller, char, true)
20    }
21}
22
23impl Cheatcode for breakpoint_1Call {
24    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
25        let Self { char, value } = self;
26        breakpoint(ccx.state, &ccx.caller, char, *value)
27    }
28}
29
30impl Cheatcode for getFoundryVersionCall {
31    fn apply(&self, _state: &mut Cheatcodes) -> Result {
32        let Self {} = self;
33        Ok(SEMVER_VERSION.abi_encode())
34    }
35}
36
37impl Cheatcode for rpcUrlCall {
38    fn apply(&self, state: &mut Cheatcodes) -> Result {
39        let Self { rpcAlias } = self;
40        let url = state.config.rpc_endpoint(rpcAlias)?.url()?.abi_encode();
41        Ok(url)
42    }
43}
44
45impl Cheatcode for rpcUrlsCall {
46    fn apply(&self, state: &mut Cheatcodes) -> Result {
47        let Self {} = self;
48        state.config.rpc_urls().map(|urls| urls.abi_encode())
49    }
50}
51
52impl Cheatcode for rpcUrlStructsCall {
53    fn apply(&self, state: &mut Cheatcodes) -> Result {
54        let Self {} = self;
55        state.config.rpc_urls().map(|urls| urls.abi_encode())
56    }
57}
58
59impl Cheatcode for sleepCall {
60    fn apply(&self, _state: &mut Cheatcodes) -> Result {
61        let Self { duration } = self;
62        let sleep_duration = std::time::Duration::from_millis(duration.saturating_to());
63        std::thread::sleep(sleep_duration);
64        Ok(Default::default())
65    }
66}
67
68impl Cheatcode for skip_0Call {
69    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
70        let Self { skipTest } = *self;
71        skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx)
72    }
73}
74
75impl Cheatcode for skip_1Call {
76    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
77        let Self { skipTest, reason } = self;
78        if *skipTest {
79            // Skip should not work if called deeper than at test level.
80            // Since we're not returning the magic skip bytes, this will cause a test failure.
81            ensure!(ccx.ecx.journaled_state.depth() <= 1, "`skip` can only be used at test level");
82            Err([MAGIC_SKIP, reason.as_bytes()].concat().into())
83        } else {
84            Ok(Default::default())
85        }
86    }
87}
88
89impl Cheatcode for getChain_0Call {
90    fn apply(&self, state: &mut Cheatcodes) -> Result {
91        let Self { chainAlias } = self;
92        get_chain(state, chainAlias)
93    }
94}
95
96impl Cheatcode for getChain_1Call {
97    fn apply(&self, state: &mut Cheatcodes) -> Result {
98        let Self { chainId } = self;
99        // Convert the chainId to a string and use the existing get_chain function
100        let chain_id_str = chainId.to_string();
101        get_chain(state, &chain_id_str)
102    }
103}
104
105/// Adds or removes the given breakpoint to the state.
106fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> Result {
107    let mut chars = s.chars();
108    let (Some(point), None) = (chars.next(), chars.next()) else {
109        bail!("breakpoints must be exactly one character");
110    };
111    ensure!(point.is_alphabetic(), "only alphabetic characters are accepted as breakpoints");
112
113    if add {
114        state.breakpoints.insert(point, (*caller, state.pc));
115    } else {
116        state.breakpoints.remove(&point);
117    }
118
119    Ok(Default::default())
120}
121
122/// Gets chain information for the given alias.
123fn get_chain(state: &mut Cheatcodes, chain_alias: &str) -> Result {
124    // Parse the chain alias - works for both chain names and IDs
125    let alloy_chain = AlloyChain::from_str(chain_alias)
126        .map_err(|_| fmt_err!("invalid chain alias: {chain_alias}"))?;
127
128    // Check if this is an unknown chain ID by comparing the name to the chain ID
129    // When a numeric ID is passed for an unknown chain, alloy_chain.to_string() will return the ID
130    // So if they match, it's likely an unknown chain ID
131    if alloy_chain.to_string() == alloy_chain.id().to_string() {
132        return Err(fmt_err!("invalid chain alias: {chain_alias}"));
133    }
134
135    // First, try to get RPC URL from the user's config in foundry.toml
136    let rpc_url = state.config.rpc_endpoint(chain_alias).ok().and_then(|e| e.url().ok());
137
138    // If we couldn't get a URL from config, return an empty string
139    let rpc_url = rpc_url.unwrap_or_default();
140
141    let chain_struct = Chain {
142        name: alloy_chain.to_string(),
143        chainId: U256::from(alloy_chain.id()),
144        chainAlias: chain_alias.to_string(),
145        rpcUrl: rpc_url,
146    };
147
148    Ok(chain_struct.abi_encode())
149}