Skip to main content

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, Bytes, U256};
6use alloy_sol_types::SolValue;
7use foundry_common::version::SEMVER_VERSION;
8use foundry_evm_core::{constants::MAGIC_SKIP, evm::FoundryEvmNetwork};
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<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> 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<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> 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<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
32        let Self {} = self;
33        Ok(SEMVER_VERSION.abi_encode())
34    }
35}
36
37impl Cheatcode for rpcUrlCall {
38    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> 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<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> 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<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> 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<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> 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<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
70        let Self { skipTest } = *self;
71        skip(ccx, skipTest, "")
72    }
73}
74
75impl Cheatcode for skip_1Call {
76    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
77        let Self { skipTest, reason } = self;
78        skip(ccx, *skipTest, reason)
79    }
80}
81
82impl Cheatcode for getChain_0Call {
83    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
84        let Self { chainAlias } = self;
85        get_chain(state, chainAlias)
86    }
87}
88
89impl Cheatcode for getChain_1Call {
90    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
91        let Self { chainId } = self;
92        // Convert the chainId to a string and use the existing get_chain function
93        let chain_id_str = chainId.to_string();
94        get_chain(state, &chain_id_str)
95    }
96}
97
98/// Reverts with the magic skip payload and records it in the state, so that the executor can
99/// distinguish this genuine skip from user-crafted revert data carrying the same prefix.
100fn skip<FEN: FoundryEvmNetwork>(
101    ccx: &mut CheatsCtxt<'_, '_, FEN>,
102    skip_test: bool,
103    reason: &str,
104) -> Result {
105    if !skip_test {
106        return Ok(Default::default());
107    }
108    // Skip should not work if called deeper than at test level.
109    // Since we're not returning the magic skip bytes, this will cause a test failure.
110    ensure!(ccx.depth() <= 1, "`skip` can only be used at test level");
111    let payload = Bytes::from([MAGIC_SKIP, reason.as_bytes()].concat());
112    ccx.state.skip_payloads.push(payload.clone());
113    Err(payload.into())
114}
115
116/// Adds or removes the given breakpoint to the state.
117fn breakpoint<FEN: FoundryEvmNetwork>(
118    state: &mut Cheatcodes<FEN>,
119    caller: &Address,
120    s: &str,
121    add: bool,
122) -> Result {
123    let mut chars = s.chars();
124    let (Some(point), None) = (chars.next(), chars.next()) else {
125        bail!("breakpoints must be exactly one character");
126    };
127    ensure!(point.is_alphabetic(), "only alphabetic characters are accepted as breakpoints");
128
129    if add {
130        state.breakpoints.insert(point, (*caller, state.pc));
131    } else {
132        state.breakpoints.remove(&point);
133    }
134
135    Ok(Default::default())
136}
137
138/// Gets chain information for the given alias.
139fn get_chain<FEN: FoundryEvmNetwork>(state: &mut Cheatcodes<FEN>, chain_alias: &str) -> Result {
140    // Parse the chain alias - works for both chain names and IDs
141    let alloy_chain = AlloyChain::from_str(chain_alias)
142        .map_err(|_| fmt_err!("invalid chain alias: {chain_alias}"))?;
143    let chain_name = alloy_chain.to_string();
144    let chain_id = alloy_chain.id();
145
146    // Check if this is an unknown chain ID by comparing the name to the chain ID
147    // When a numeric ID is passed for an unknown chain, alloy_chain.to_string() will return the ID
148    // So if they match, it's likely an unknown chain ID
149    if chain_name == chain_id.to_string() {
150        return Err(fmt_err!("invalid chain alias: {chain_alias}"));
151    }
152
153    // Try to retrieve RPC URL and chain alias from user's config in foundry.toml.
154    let (rpc_url, chain_alias) = if let Some(rpc_url) =
155        state.config.rpc_endpoint(&chain_name).ok().and_then(|e| e.url().ok())
156    {
157        (rpc_url, chain_name.clone())
158    } else {
159        (String::new(), chain_alias.to_string())
160    };
161
162    let chain_struct = Chain {
163        name: chain_name,
164        chainId: U256::from(chain_id),
165        chainAlias: chain_alias,
166        rpcUrl: rpc_url,
167    };
168
169    Ok(chain_struct.abi_encode())
170}