foundry_config/
cache.rs

1//! Support types for configuring storage caching
2
3use crate::Chain;
4use number_prefix::NumberPrefix;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::{fmt, fmt::Formatter, str::FromStr};
7
8/// Settings to configure caching of remote.
9#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
10pub struct StorageCachingConfig {
11    /// Chains to cache.
12    pub chains: CachedChains,
13    /// Endpoints to cache.
14    pub endpoints: CachedEndpoints,
15}
16
17impl StorageCachingConfig {
18    /// Whether caching should be enabled for the endpoint
19    pub fn enable_for_endpoint(&self, endpoint: impl AsRef<str>) -> bool {
20        self.endpoints.is_match(endpoint)
21    }
22
23    /// Whether caching should be enabled for the chain id
24    pub fn enable_for_chain_id(&self, chain_id: u64) -> bool {
25        // ignore dev chains
26        if [99, 1337, 31337].contains(&chain_id) {
27            return false
28        }
29        self.chains.is_match(chain_id)
30    }
31}
32
33/// What chains to cache
34#[derive(Clone, Debug, Default, PartialEq, Eq)]
35pub enum CachedChains {
36    /// Cache all chains
37    #[default]
38    All,
39    /// Don't cache anything
40    None,
41    /// Only cache these chains
42    Chains(Vec<Chain>),
43}
44impl CachedChains {
45    /// Whether the `endpoint` matches
46    pub fn is_match(&self, chain: u64) -> bool {
47        match self {
48            Self::All => true,
49            Self::None => false,
50            Self::Chains(chains) => chains.iter().any(|c| c.id() == chain),
51        }
52    }
53}
54
55impl Serialize for CachedChains {
56    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
57    where
58        S: Serializer,
59    {
60        match self {
61            Self::All => serializer.serialize_str("all"),
62            Self::None => serializer.serialize_str("none"),
63            Self::Chains(chains) => chains.serialize(serializer),
64        }
65    }
66}
67
68impl<'de> Deserialize<'de> for CachedChains {
69    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70    where
71        D: Deserializer<'de>,
72    {
73        #[derive(Deserialize)]
74        #[serde(untagged)]
75        enum Chains {
76            All(String),
77            Chains(Vec<Chain>),
78        }
79
80        match Chains::deserialize(deserializer)? {
81            Chains::All(s) => match s.as_str() {
82                "all" => Ok(Self::All),
83                "none" => Ok(Self::None),
84                s => Err(serde::de::Error::unknown_variant(s, &["all", "none"])),
85            },
86            Chains::Chains(chains) => Ok(Self::Chains(chains)),
87        }
88    }
89}
90
91/// What endpoints to enable caching for
92#[derive(Clone, Debug, Default)]
93pub enum CachedEndpoints {
94    /// Cache all endpoints
95    #[default]
96    All,
97    /// Only cache non-local host endpoints
98    Remote,
99    /// Only cache these chains
100    Pattern(regex::Regex),
101}
102
103impl CachedEndpoints {
104    /// Whether the `endpoint` matches
105    pub fn is_match(&self, endpoint: impl AsRef<str>) -> bool {
106        let endpoint = endpoint.as_ref();
107        match self {
108            Self::All => true,
109            Self::Remote => !endpoint.contains("localhost:") && !endpoint.contains("127.0.0.1:"),
110            Self::Pattern(re) => re.is_match(endpoint),
111        }
112    }
113}
114
115impl PartialEq for CachedEndpoints {
116    fn eq(&self, other: &Self) -> bool {
117        match (self, other) {
118            (Self::Pattern(a), Self::Pattern(b)) => a.as_str() == b.as_str(),
119            (&Self::All, &Self::All) => true,
120            (&Self::Remote, &Self::Remote) => true,
121            _ => false,
122        }
123    }
124}
125
126impl Eq for CachedEndpoints {}
127
128impl fmt::Display for CachedEndpoints {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Self::All => f.write_str("all"),
132            Self::Remote => f.write_str("remote"),
133            Self::Pattern(s) => s.fmt(f),
134        }
135    }
136}
137
138impl FromStr for CachedEndpoints {
139    type Err = regex::Error;
140
141    fn from_str(s: &str) -> Result<Self, Self::Err> {
142        match s {
143            "all" => Ok(Self::All),
144            "remote" => Ok(Self::Remote),
145            _ => Ok(Self::Pattern(s.parse()?)),
146        }
147    }
148}
149
150impl<'de> Deserialize<'de> for CachedEndpoints {
151    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152    where
153        D: Deserializer<'de>,
154    {
155        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
156    }
157}
158
159impl Serialize for CachedEndpoints {
160    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161    where
162        S: Serializer,
163    {
164        match self {
165            Self::All => serializer.serialize_str("all"),
166            Self::Remote => serializer.serialize_str("remote"),
167            Self::Pattern(pattern) => serializer.serialize_str(pattern.as_str()),
168        }
169    }
170}
171
172/// Content of the foundry cache folder
173#[derive(Debug, Default)]
174pub struct Cache {
175    /// The list of chains in the cache
176    pub chains: Vec<ChainCache>,
177}
178
179impl fmt::Display for Cache {
180    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
181        for chain in &self.chains {
182            match NumberPrefix::decimal(
183                chain.block_explorer as f32 + chain.blocks.iter().map(|x| x.1).sum::<u64>() as f32,
184            ) {
185                NumberPrefix::Standalone(size) => {
186                    writeln!(f, "- {} ({size:.1} B)", chain.name)?;
187                }
188                NumberPrefix::Prefixed(prefix, size) => {
189                    writeln!(f, "- {} ({size:.1} {prefix}B)", chain.name)?;
190                }
191            }
192            match NumberPrefix::decimal(chain.block_explorer as f32) {
193                NumberPrefix::Standalone(size) => {
194                    writeln!(f, "\t- Block Explorer ({size:.1} B)\n")?;
195                }
196                NumberPrefix::Prefixed(prefix, size) => {
197                    writeln!(f, "\t- Block Explorer ({size:.1} {prefix}B)\n")?;
198                }
199            }
200            for block in &chain.blocks {
201                match NumberPrefix::decimal(block.1 as f32) {
202                    NumberPrefix::Standalone(size) => {
203                        writeln!(f, "\t- Block {} ({size:.1} B)", block.0)?;
204                    }
205                    NumberPrefix::Prefixed(prefix, size) => {
206                        writeln!(f, "\t- Block {} ({size:.1} {prefix}B)", block.0)?;
207                    }
208                }
209            }
210        }
211        Ok(())
212    }
213}
214
215/// A representation of data for a given chain in the foundry cache
216#[derive(Debug)]
217pub struct ChainCache {
218    /// The name of the chain
219    pub name: String,
220
221    /// A tuple containing block number and the block directory size in bytes
222    pub blocks: Vec<(String, u64)>,
223
224    /// The size of the block explorer directory in bytes
225    pub block_explorer: u64,
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use similar_asserts::assert_eq;
232
233    #[test]
234    fn can_parse_storage_config() {
235        #[derive(Serialize, Deserialize)]
236        pub struct Wrapper {
237            pub rpc_storage_caching: StorageCachingConfig,
238        }
239
240        let s = r#"rpc_storage_caching = { chains = "all", endpoints = "remote"}"#;
241        let w: Wrapper = toml::from_str(s).unwrap();
242
243        assert_eq!(
244            w.rpc_storage_caching,
245            StorageCachingConfig { chains: CachedChains::All, endpoints: CachedEndpoints::Remote }
246        );
247
248        let s = r#"rpc_storage_caching = { chains = [1, "optimism", 999999], endpoints = "all"}"#;
249        let w: Wrapper = toml::from_str(s).unwrap();
250
251        assert_eq!(
252            w.rpc_storage_caching,
253            StorageCachingConfig {
254                chains: CachedChains::Chains(vec![
255                    Chain::mainnet(),
256                    Chain::optimism_mainnet(),
257                    Chain::from_id(999999)
258                ]),
259                endpoints: CachedEndpoints::All,
260            }
261        )
262    }
263
264    #[test]
265    fn cache_to_string() {
266        let cache = Cache {
267            chains: vec![
268                ChainCache {
269                    name: "mainnet".to_string(),
270                    blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)],
271                    block_explorer: 500,
272                },
273                ChainCache {
274                    name: "ropsten".to_string(),
275                    blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)],
276                    block_explorer: 4567,
277                },
278                ChainCache {
279                    name: "rinkeby".to_string(),
280                    blocks: vec![("1".to_string(), 1032), ("2".to_string(), 2000000)],
281                    block_explorer: 4230000,
282                },
283                ChainCache {
284                    name: "mumbai".to_string(),
285                    blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)],
286                    block_explorer: 0,
287                },
288            ],
289        };
290
291        let expected = "\
292            - mainnet (503.0 B)\n\t\
293                - Block Explorer (500.0 B)\n\n\t\
294                - Block 1 (1.0 B)\n\t\
295                - Block 2 (2.0 B)\n\
296            - ropsten (4.6 kB)\n\t\
297                - Block Explorer (4.6 kB)\n\n\t\
298                - Block 1 (1.0 B)\n\t\
299                - Block 2 (2.0 B)\n\
300            - rinkeby (6.2 MB)\n\t\
301                - Block Explorer (4.2 MB)\n\n\t\
302                - Block 1 (1.0 kB)\n\t\
303                - Block 2 (2.0 MB)\n\
304            - mumbai (3.0 B)\n\t\
305                - Block Explorer (0.0 B)\n\n\t\
306                - Block 1 (1.0 B)\n\t\
307                - Block 2 (2.0 B)\n";
308        assert_eq!(format!("{cache}"), expected);
309    }
310}