Skip to content

Commit bdbe379

Browse files
authored
feat(config): support numeric keys in [fork.<chain>] section (#11340)
1 parent ece0eba commit bdbe379

File tree

4 files changed

+200
-103
lines changed

4 files changed

+200
-103
lines changed

crates/cheatcodes/src/fork.rs

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,35 @@ use crate::{
44
Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*, json::parse_json_as,
55
toml::toml_to_json_value,
66
};
7+
use alloy_chains::Chain;
78
use alloy_dyn_abi::{DynSolType, DynSolValue};
89
use alloy_sol_types::SolValue;
9-
use eyre::OptionExt;
1010
use foundry_evm_core::ContextExt;
11+
use std::borrow::Cow;
1112

1213
impl Cheatcode for readForkChainIdsCall {
1314
fn apply(&self, state: &mut crate::Cheatcodes) -> Result {
1415
let Self {} = self;
15-
Ok(state
16-
.config
17-
.forks
18-
.keys()
19-
.map(|name| alloy_chains::Chain::from_named(name.parse().unwrap()).id())
20-
.collect::<Vec<_>>()
21-
.abi_encode())
16+
Ok(state.config.forks.keys().map(|chain| chain.id()).collect::<Vec<_>>().abi_encode())
2217
}
2318
}
2419

2520
impl Cheatcode for readForkChainsCall {
2621
fn apply(&self, state: &mut crate::Cheatcodes) -> Result {
2722
let Self {} = self;
28-
Ok(state.config.forks.keys().collect::<Vec<_>>().abi_encode())
23+
Ok(state
24+
.config
25+
.forks
26+
.keys()
27+
.map(|chain| get_chain_name(chain).to_string())
28+
.collect::<Vec<_>>()
29+
.abi_encode())
2930
}
3031
}
3132

3233
impl Cheatcode for readForkChainCall {
3334
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
34-
Ok(get_active_fork_chain_name(ccx)?.abi_encode())
35+
Ok(get_chain_name(&get_active_fork_chain(ccx)?).to_string().abi_encode())
3536
}
3637
}
3738

@@ -41,42 +42,38 @@ impl Cheatcode for readForkChainIdCall {
4142
}
4243
}
4344

44-
fn resolve_rpc_url(name: &'static str, state: &mut crate::Cheatcodes) -> Result {
45-
// Get the chain ID from the chain_configs
46-
if let Some(config) = state.config.forks.get(name) {
45+
fn resolve_rpc_url(chain: Chain, state: &mut crate::Cheatcodes) -> Result {
46+
if let Some(config) = state.config.forks.get(&chain) {
4747
let rpc = match config.rpc_endpoint {
4848
Some(ref url) => url.clone().resolve(),
49-
None => state.config.rpc_endpoint(name)?,
49+
None => state.config.rpc_endpoint(&get_chain_name(&chain))?,
5050
};
5151

5252
return Ok(rpc.url()?.abi_encode());
5353
}
5454

55-
bail!("[fork.{name}] subsection not found in [fork] of 'foundry.toml'")
55+
bail!(
56+
"'rpc_endpoint' not found in '[fork.<chain_id: {chain}>]' subsection of 'foundry.toml'",
57+
chain = chain.id()
58+
)
5659
}
5760

5861
impl Cheatcode for readForkChainRpcUrlCall {
5962
fn apply(&self, state: &mut crate::Cheatcodes) -> Result {
6063
let Self { id } = self;
61-
let name = get_chain_name(id.to::<u64>())?;
62-
resolve_rpc_url(name, state)
64+
resolve_rpc_url(Chain::from_id(id.to::<u64>()), state)
6365
}
6466
}
6567

6668
impl Cheatcode for readForkRpcUrlCall {
6769
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
68-
let name = get_active_fork_chain_name(ccx)?;
69-
resolve_rpc_url(name, ccx.state)
70+
resolve_rpc_url(get_active_fork_chain(ccx)?, ccx.state)
7071
}
7172
}
7273

7374
/// Gets the alloy chain name for a given chain id.
74-
fn get_chain_name(id: u64) -> Result<&'static str> {
75-
let chain = alloy_chains::Chain::from_id(id)
76-
.named()
77-
.ok_or_eyre("unknown name for active forked chain")?;
78-
79-
Ok(chain.as_str())
75+
fn get_chain_name(chain: &Chain) -> Cow<'static, str> {
76+
chain.named().map_or(Cow::Owned(chain.id().to_string()), |name| Cow::Borrowed(name.as_str()))
8077
}
8178

8279
/// Gets the chain id of the active fork. Panics if no fork is selected.
@@ -88,9 +85,9 @@ fn get_active_fork_chain_id(ccx: &mut CheatsCtxt) -> Result<u64> {
8885
Ok(env.cfg.chain_id)
8986
}
9087

91-
/// Gets the alloy chain name for the active fork. Panics if no fork is selected.
92-
fn get_active_fork_chain_name(ccx: &mut CheatsCtxt) -> Result<&'static str> {
93-
get_chain_name(get_active_fork_chain_id(ccx)?)
88+
/// Gets the alloy chain for the active fork. Panics if no fork is selected.
89+
fn get_active_fork_chain(ccx: &mut CheatsCtxt) -> Result<Chain> {
90+
get_active_fork_chain_id(ccx).map(Chain::from_id)
9491
}
9592

9693
// Helper macros to generate cheatcode implementations
@@ -99,7 +96,7 @@ macro_rules! impl_get_value_cheatcode {
9996
impl Cheatcode for $struct {
10097
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
10198
let Self { key } = self;
102-
let chain = get_active_fork_chain_id(ccx)?;
99+
let chain = get_active_fork_chain(ccx)?;
103100
get_value(chain, key, $sol_type, ccx.state)
104101
}
105102
}
@@ -108,7 +105,7 @@ macro_rules! impl_get_value_cheatcode {
108105
impl Cheatcode for $struct {
109106
fn apply(&self, state: &mut crate::Cheatcodes) -> Result {
110107
let Self { chain, key } = self;
111-
get_value(chain.to::<u64>(), key, $sol_type, state)
108+
get_value(Chain::from_id(chain.to::<u64>()), key, $sol_type, state)
112109
}
113110
}
114111
};
@@ -119,16 +116,15 @@ macro_rules! impl_get_array_cheatcode {
119116
impl Cheatcode for $struct {
120117
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
121118
let Self { key } = self;
122-
let chain = get_active_fork_chain_id(ccx)?;
123-
get_array(chain, key, $sol_type, ccx.state)
119+
get_array(get_active_fork_chain(ccx)?, key, $sol_type, ccx.state)
124120
}
125121
}
126122
};
127123
($struct:ident, $sol_type:expr) => {
128124
impl Cheatcode for $struct {
129125
fn apply(&self, state: &mut crate::Cheatcodes) -> Result {
130126
let Self { chain, key } = self;
131-
get_array(chain.to::<u64>(), key, $sol_type, state)
127+
get_array(Chain::from(chain.to::<u64>()), key, $sol_type, state)
132128
}
133129
}
134130
};
@@ -178,44 +174,49 @@ impl_get_array_cheatcode!(readForkStringArrayCall, &DynSolType::String, stateful
178174

179175
/// Generic helper to get any value from the TOML config.
180176
fn get_toml_value<'a>(
181-
name: &'a str,
177+
chain: Chain,
182178
key: &'a str,
183179
state: &'a crate::Cheatcodes,
184180
) -> Result<&'a toml::Value> {
185-
let config = state.config.forks.get(name).ok_or_else(|| {
186-
fmt_err!("[fork.{name}] subsection not found in [fork] of 'foundry.toml'")
181+
let config = state.config.forks.get(&chain).ok_or_else(|| {
182+
fmt_err!(
183+
"'[fork.<chain_id: {chain}>]' subsection not found in 'foundry.toml'",
184+
chain = chain.id()
185+
)
186+
})?;
187+
let value = config.vars.get(key).ok_or_else(|| {
188+
fmt_err!("variable '{key}' not found in '[fork.<chain_id: {chain}>]'", chain = chain.id())
187189
})?;
188-
let value = config
189-
.vars
190-
.get(key)
191-
.ok_or_else(|| fmt_err!("Variable '{key}' not found in [fork.{name}] configuration"))?;
192190

193191
Ok(value)
194192
}
195193

196194
/// Generic helper to get any single value from the TOML config.
197-
fn get_value(chain: u64, key: &str, ty: &DynSolType, state: &crate::Cheatcodes) -> Result {
198-
let name = get_chain_name(chain)?;
199-
let value = get_toml_value(name, key, state)?;
200-
let sol_value = parse_toml_element(value, ty, key, name)?;
195+
fn get_value(chain: Chain, key: &str, ty: &DynSolType, state: &crate::Cheatcodes) -> Result {
196+
let value = get_toml_value(chain, key, state)?;
197+
let sol_value = parse_toml_element(value, ty, key, chain)?;
201198
Ok(sol_value.abi_encode())
202199
}
203200

204201
/// Generic helper to get an array of values from the TOML config.
205-
fn get_array(chain: u64, key: &str, element_ty: &DynSolType, state: &crate::Cheatcodes) -> Result {
206-
let name = get_chain_name(chain)?;
207-
let value = get_toml_value(name, key, state)?;
208-
209-
let arr = value
210-
.as_array()
211-
.ok_or_else(|| fmt_err!("Variable '{key}' in [fork.{name}] must be an array"))?;
212-
202+
fn get_array(
203+
chain: Chain,
204+
key: &str,
205+
element_ty: &DynSolType,
206+
state: &crate::Cheatcodes,
207+
) -> Result {
208+
let arr = get_toml_value(chain, key, state)?.as_array().ok_or_else(|| {
209+
fmt_err!(
210+
"variable '{key}' in '[fork.<chain_id: {chain}>]' must be an array",
211+
chain = chain.id()
212+
)
213+
})?;
213214
let result: Result<Vec<_>> = arr
214215
.iter()
215216
.enumerate()
216217
.map(|(i, elem)| {
217218
let context = format!("{key}[{i}]");
218-
parse_toml_element(elem, element_ty, &context, name)
219+
parse_toml_element(elem, element_ty, &context, chain)
219220
})
220221
.collect();
221222

@@ -226,10 +227,11 @@ fn get_array(chain: u64, key: &str, element_ty: &DynSolType, state: &crate::Chea
226227
fn parse_toml_element(
227228
elem: &toml::Value,
228229
element_ty: &DynSolType,
229-
context: &str,
230-
fork_name: &str,
230+
key: &str,
231+
chain: Chain,
231232
) -> Result<DynSolValue> {
232233
// Convert TOML value to JSON value and use existing JSON parsing logic
233-
parse_json_as(&toml_to_json_value(elem.to_owned()), element_ty)
234-
.map_err(|e| fmt_err!("Failed to parse '{context}' in [fork.{fork_name}]: {e}"))
234+
parse_json_as(&toml_to_json_value(elem.to_owned()), element_ty).map_err(|e| {
235+
fmt_err!("failed to parse '{key}' in '[fork.<chain_id: {chain}>]': {e}", chain = chain.id())
236+
})
235237
}

crates/config/src/fork_config.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use crate::{
44
resolve::{RE_PLACEHOLDER, UnresolvedEnvVarError, interpolate},
55
};
66

7+
use alloy_chains::Chain;
78
use serde::{Deserialize, Serialize};
89
use std::{collections::HashMap, ops::Deref};
910

1011
/// Fork-scoped config for tests and scripts.
1112
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
12-
pub struct ForkConfigs(pub HashMap<String, ForkChainConfig>);
13+
pub struct ForkConfigs(pub HashMap<Chain, ForkChainConfig>);
1314

1415
impl ForkConfigs {
1516
/// Resolve environment variables in all fork config fields
@@ -36,7 +37,7 @@ impl ForkConfigs {
3637
}
3738

3839
impl Deref for ForkConfigs {
39-
type Target = HashMap<String, ForkChainConfig>;
40+
type Target = HashMap<Chain, ForkChainConfig>;
4041

4142
fn deref(&self) -> &Self::Target {
4243
&self.0

0 commit comments

Comments
 (0)