foundry_evm_traces/identifier/
signatures.rs1use alloy_json_abi::{Error, Event, Function, JsonAbi};
2use alloy_primitives::{B256, Selector, map::HashMap};
3use eyre::Result;
4use foundry_common::{
5 abi::{get_error, get_event, get_func},
6 fs,
7 selectors::{OpenChainClient, SelectorKind},
8};
9use foundry_config::Config;
10use serde::{Deserialize, Serialize};
11use std::{
12 collections::BTreeMap,
13 path::{Path, PathBuf},
14 sync::Arc,
15};
16use tokio::sync::RwLock;
17
18#[derive(Debug, Default, Deserialize)]
20#[serde(try_from = "SignaturesDiskCache")]
21pub struct SignaturesCache {
22 signatures: HashMap<SelectorKind, Option<String>>,
23}
24
25#[derive(Serialize, Deserialize)]
27struct SignaturesDiskCache {
28 functions: BTreeMap<Selector, String>,
29 errors: BTreeMap<Selector, String>,
30 events: BTreeMap<B256, String>,
31}
32
33impl From<SignaturesDiskCache> for SignaturesCache {
34 fn from(value: SignaturesDiskCache) -> Self {
35 let functions = value
36 .functions
37 .into_iter()
38 .map(|(selector, signature)| (SelectorKind::Function(selector), signature));
39 let errors = value
40 .errors
41 .into_iter()
42 .map(|(selector, signature)| (SelectorKind::Error(selector), signature));
43 let events = value
44 .events
45 .into_iter()
46 .map(|(selector, signature)| (SelectorKind::Event(selector), signature));
47 Self {
48 signatures: functions
49 .chain(errors)
50 .chain(events)
51 .map(|(sel, sig)| (sel, (!sig.is_empty()).then_some(sig)))
52 .collect(),
53 }
54 }
55}
56
57impl From<&SignaturesCache> for SignaturesDiskCache {
58 fn from(value: &SignaturesCache) -> Self {
59 let (functions, errors, events) = value.signatures.iter().fold(
60 (BTreeMap::new(), BTreeMap::new(), BTreeMap::new()),
61 |mut acc, (kind, signature)| {
62 if let Some(value) = signature.clone() {
66 match *kind {
67 SelectorKind::Function(selector) => _ = acc.0.insert(selector, value),
68 SelectorKind::Error(selector) => _ = acc.1.insert(selector, value),
69 SelectorKind::Event(selector) => _ = acc.2.insert(selector, value),
70 }
71 }
72 acc
73 },
74 );
75 Self { functions, errors, events }
76 }
77}
78
79impl Serialize for SignaturesCache {
80 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where
82 S: serde::Serializer,
83 {
84 SignaturesDiskCache::from(self).serialize(serializer)
85 }
86}
87
88impl SignaturesCache {
89 #[instrument(target = "evm::traces", name = "SignaturesCache::load")]
91 pub fn load(path: &Path) -> Self {
92 trace!(target: "evm::traces", ?path, "reading signature cache");
93 fs::read_json_file(path)
94 .inspect_err(
95 |err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file"),
96 )
97 .unwrap_or_default()
98 }
99
100 #[instrument(target = "evm::traces", name = "SignaturesCache::save", skip(self))]
102 pub fn save(&self, path: &Path) {
103 if let Some(parent) = path.parent()
104 && let Err(err) = std::fs::create_dir_all(parent)
105 {
106 warn!(target: "evm::traces", ?parent, %err, "failed to create cache");
107 }
108 if let Err(err) = fs::write_json_file(path, self) {
109 warn!(target: "evm::traces", %err, "failed to flush signature cache");
110 } else {
111 trace!(target: "evm::traces", "flushed signature cache")
112 }
113 }
114
115 pub fn extend_from_abi(&mut self, abi: &JsonAbi) {
117 self.extend(abi.items().filter_map(|item| match item {
118 alloy_json_abi::AbiItem::Function(f) => {
119 Some((SelectorKind::Function(f.selector()), f.signature()))
120 }
121 alloy_json_abi::AbiItem::Error(e) => {
122 Some((SelectorKind::Error(e.selector()), e.signature()))
123 }
124 alloy_json_abi::AbiItem::Event(e) => {
125 Some((SelectorKind::Event(e.selector()), e.full_signature()))
126 }
127 _ => None,
128 }));
129 }
130
131 pub fn insert(&mut self, key: SelectorKind, value: String) {
133 self.extend(std::iter::once((key, value)));
134 }
135
136 pub fn extend(&mut self, signatures: impl IntoIterator<Item = (SelectorKind, String)>) {
138 self.signatures
139 .extend(signatures.into_iter().map(|(k, v)| (k, (!v.is_empty()).then_some(v))));
140 }
141
142 pub fn get(&self, key: &SelectorKind) -> Option<Option<String>> {
144 self.signatures.get(key).cloned()
145 }
146
147 pub fn contains_key(&self, key: &SelectorKind) -> bool {
149 self.signatures.contains_key(key)
150 }
151}
152
153#[derive(Clone, Debug)]
156pub struct SignaturesIdentifier(Arc<SignaturesIdentifierInner>);
157
158#[derive(Debug)]
159struct SignaturesIdentifierInner {
160 cache: RwLock<SignaturesCache>,
162 cache_path: Option<PathBuf>,
164 client: Option<OpenChainClient>,
166}
167
168impl SignaturesIdentifier {
169 pub fn new(offline: bool) -> Result<Self> {
171 Self::new_with(Config::foundry_cache_dir().as_deref(), offline)
172 }
173
174 pub fn from_config(config: &Config) -> Result<Self> {
176 Self::new(config.offline)
177 }
178
179 pub fn new_with(cache_dir: Option<&Path>, offline: bool) -> Result<Self> {
184 let client = if offline { None } else { Some(OpenChainClient::new()?) };
185 let (cache, cache_path) = if let Some(cache_dir) = cache_dir {
186 let path = cache_dir.join("signatures");
187 let cache = SignaturesCache::load(&path);
188 (cache, Some(path))
189 } else {
190 Default::default()
191 };
192 Ok(Self(Arc::new(SignaturesIdentifierInner {
193 cache: RwLock::new(cache),
194 cache_path,
195 client,
196 })))
197 }
198
199 pub fn save(&self) {
201 self.0.save();
202 }
203
204 pub async fn identify_functions(
206 &self,
207 identifiers: impl IntoIterator<Item = Selector>,
208 ) -> Vec<Option<Function>> {
209 self.identify_map(identifiers.into_iter().map(SelectorKind::Function), get_func).await
210 }
211
212 pub async fn identify_function(&self, identifier: Selector) -> Option<Function> {
214 self.identify_functions([identifier]).await.pop().unwrap()
215 }
216
217 pub async fn identify_events(
219 &self,
220 identifiers: impl IntoIterator<Item = B256>,
221 ) -> Vec<Option<Event>> {
222 self.identify_map(identifiers.into_iter().map(SelectorKind::Event), get_event).await
223 }
224
225 pub async fn identify_event(&self, identifier: B256) -> Option<Event> {
227 self.identify_events([identifier]).await.pop().unwrap()
228 }
229
230 pub async fn identify_errors(
232 &self,
233 identifiers: impl IntoIterator<Item = Selector>,
234 ) -> Vec<Option<Error>> {
235 self.identify_map(identifiers.into_iter().map(SelectorKind::Error), get_error).await
236 }
237
238 pub async fn identify_error(&self, identifier: Selector) -> Option<Error> {
240 self.identify_errors([identifier]).await.pop().unwrap()
241 }
242
243 pub async fn identify(&self, selectors: &[SelectorKind]) -> Vec<Option<String>> {
245 if selectors.is_empty() {
246 return vec![];
247 }
248 trace!(target: "evm::traces", ?selectors, "identifying selectors");
249
250 let mut cache_r = self.0.cache.read().await;
251 if let Some(client) = &self.0.client {
252 let query =
253 selectors.iter().copied().filter(|v| !cache_r.contains_key(v)).collect::<Vec<_>>();
254 if !query.is_empty() {
255 drop(cache_r);
256 let mut cache_w = self.0.cache.write().await;
257 if let Ok(res) = client.decode_selectors(&query).await {
258 for (selector, signatures) in std::iter::zip(query, res) {
259 cache_w.signatures.insert(selector, signatures.into_iter().next());
260 }
261 }
262 drop(cache_w);
263 cache_r = self.0.cache.read().await;
264 }
265 }
266 selectors.iter().map(|selector| cache_r.get(selector).unwrap_or_default()).collect()
267 }
268
269 async fn identify_map<T>(
270 &self,
271 selectors: impl IntoIterator<Item = SelectorKind>,
272 get_type: impl Fn(&str) -> Result<T>,
273 ) -> Vec<Option<T>> {
274 let results = self.identify(&Vec::from_iter(selectors)).await;
275 results.into_iter().map(|r| r.and_then(|r| get_type(&r).ok())).collect()
276 }
277}
278
279impl SignaturesIdentifierInner {
280 fn save(&self) {
281 if let Some(path) = &self.cache_path
283 && self.client.is_some()
284 {
285 self.cache
286 .try_read()
287 .expect("SignaturesIdentifier cache is locked while attempting to save")
288 .save(path);
289 }
290 }
291}
292
293impl Drop for SignaturesIdentifierInner {
294 fn drop(&mut self) {
295 self.save();
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn unknown_signatures_not_persisted_to_disk() {
305 let known_selector = SelectorKind::Function(Selector::from([0xaa, 0xbb, 0xcc, 0xdd]));
306 let unknown_selector = SelectorKind::Error(Selector::from([0x11, 0x22, 0x33, 0x44]));
307
308 let mut cache = SignaturesCache::default();
309 cache.signatures.insert(known_selector, Some("transfer(address,uint256)".into()));
310 cache.signatures.insert(unknown_selector, None);
311
312 assert!(cache.contains_key(&known_selector));
314 assert!(cache.contains_key(&unknown_selector));
315
316 let disk: SignaturesDiskCache = (&cache).into();
318 let reloaded = SignaturesCache::from(disk);
319
320 assert_eq!(reloaded.get(&known_selector), Some(Some("transfer(address,uint256)".into())));
322 assert_eq!(reloaded.get(&unknown_selector), None);
324 assert!(!reloaded.contains_key(&unknown_selector));
325 }
326}