foundry_evm_traces/identifier/
signatures.rs1use alloy_json_abi::{Error, Event, Function, JsonAbi};
2use alloy_primitives::{
3 B256, Selector,
4 map::{HashMap, HashSet},
5};
6use eyre::Result;
7use foundry_common::{
8 abi::{get_error, get_event, get_func},
9 fs,
10 selectors::{OpenChainClient, SelectorKind},
11};
12use foundry_config::Config;
13use serde::{Deserialize, Serialize};
14use std::{
15 collections::BTreeMap,
16 path::{Path, PathBuf},
17 sync::Arc,
18};
19use tokio::sync::RwLock;
20
21#[derive(Debug, Default, Deserialize)]
23#[serde(try_from = "SignaturesDiskCache")]
24pub struct SignaturesCache {
25 signatures: HashMap<SelectorKind, Option<String>>,
26}
27
28#[derive(Serialize, Deserialize)]
30struct SignaturesDiskCache {
31 functions: BTreeMap<Selector, String>,
32 errors: BTreeMap<Selector, String>,
33 events: BTreeMap<B256, String>,
34}
35
36impl From<SignaturesDiskCache> for SignaturesCache {
37 fn from(value: SignaturesDiskCache) -> Self {
38 let functions = value
39 .functions
40 .into_iter()
41 .map(|(selector, signature)| (SelectorKind::Function(selector), signature));
42 let errors = value
43 .errors
44 .into_iter()
45 .map(|(selector, signature)| (SelectorKind::Error(selector), signature));
46 let events = value
47 .events
48 .into_iter()
49 .map(|(selector, signature)| (SelectorKind::Event(selector), signature));
50 Self {
51 signatures: functions
52 .chain(errors)
53 .chain(events)
54 .map(|(sel, sig)| (sel, (!sig.is_empty()).then_some(sig)))
55 .collect(),
56 }
57 }
58}
59
60impl From<&SignaturesCache> for SignaturesDiskCache {
61 fn from(value: &SignaturesCache) -> Self {
62 let (functions, errors, events) = value.signatures.iter().fold(
63 (BTreeMap::new(), BTreeMap::new(), BTreeMap::new()),
64 |mut acc, (kind, signature)| {
65 if let Some(value) = signature.clone() {
69 match *kind {
70 SelectorKind::Function(selector) => _ = acc.0.insert(selector, value),
71 SelectorKind::Error(selector) => _ = acc.1.insert(selector, value),
72 SelectorKind::Event(selector) => _ = acc.2.insert(selector, value),
73 }
74 }
75 acc
76 },
77 );
78 Self { functions, errors, events }
79 }
80}
81
82impl Serialize for SignaturesCache {
83 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84 where
85 S: serde::Serializer,
86 {
87 SignaturesDiskCache::from(self).serialize(serializer)
88 }
89}
90
91impl SignaturesCache {
92 #[instrument(target = "evm::traces", name = "SignaturesCache::load")]
94 pub fn load(path: &Path) -> Self {
95 trace!(target: "evm::traces", ?path, "reading signature cache");
96 fs::read_json_file(path)
97 .inspect_err(
98 |err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file"),
99 )
100 .unwrap_or_default()
101 }
102
103 #[instrument(target = "evm::traces", name = "SignaturesCache::save", skip(self))]
105 pub fn save(&self, path: &Path) {
106 if let Some(parent) = path.parent()
107 && let Err(err) = std::fs::create_dir_all(parent)
108 {
109 warn!(target: "evm::traces", ?parent, %err, "failed to create cache");
110 }
111 if let Err(err) = fs::write_json_file(path, self) {
112 warn!(target: "evm::traces", %err, "failed to flush signature cache");
113 } else {
114 trace!(target: "evm::traces", "flushed signature cache")
115 }
116 }
117
118 pub fn extend_from_abi(&mut self, abi: &JsonAbi) {
120 self.extend(Self::signatures_from_abi(abi));
121 }
122
123 fn extend_from_abis_without_collisions<'a>(
125 &mut self,
126 abis: impl IntoIterator<Item = &'a JsonAbi>,
127 ) {
128 let mut seeded: HashSet<SelectorKind> = HashSet::default();
129 for abi in abis {
130 for (selector, signature) in Self::signatures_from_abi(abi) {
131 if seeded.insert(selector) {
132 self.insert(selector, signature);
133 } else {
134 trace!(target: "evm::traces", ?selector, %signature, "skipping duplicate ABI signature");
135 }
136 }
137 }
138 }
139
140 fn signatures_from_abi(abi: &JsonAbi) -> impl Iterator<Item = (SelectorKind, String)> + '_ {
141 abi.items().filter_map(|item| match item {
142 alloy_json_abi::AbiItem::Function(f) => {
143 Some((SelectorKind::Function(f.selector()), f.signature()))
144 }
145 alloy_json_abi::AbiItem::Error(e) => {
146 Some((SelectorKind::Error(e.selector()), e.signature()))
147 }
148 alloy_json_abi::AbiItem::Event(e) => {
149 Some((SelectorKind::Event(e.selector()), e.full_signature()))
150 }
151 _ => None,
152 })
153 }
154
155 pub fn insert(&mut self, key: SelectorKind, value: String) {
157 self.extend(std::iter::once((key, value)));
158 }
159
160 pub fn extend(&mut self, signatures: impl IntoIterator<Item = (SelectorKind, String)>) {
162 self.signatures
163 .extend(signatures.into_iter().map(|(k, v)| (k, (!v.is_empty()).then_some(v))));
164 }
165
166 pub fn get(&self, key: &SelectorKind) -> Option<Option<String>> {
168 self.signatures.get(key).cloned()
169 }
170
171 pub fn contains_key(&self, key: &SelectorKind) -> bool {
173 self.signatures.contains_key(key)
174 }
175}
176
177#[derive(Clone, Debug)]
180pub struct SignaturesIdentifier(Arc<SignaturesIdentifierInner>);
181
182#[derive(Debug)]
183struct SignaturesIdentifierInner {
184 cache: RwLock<SignaturesCache>,
186 local_events: HashMap<(B256, usize), Vec<Event>>,
188 cache_path: Option<PathBuf>,
190 client: Option<OpenChainClient>,
192}
193
194impl SignaturesIdentifier {
195 pub fn new(offline: bool) -> Result<Self> {
197 Self::new_with(Config::foundry_cache_dir().as_deref(), offline)
198 }
199
200 pub fn from_config(config: &Config) -> Result<Self> {
202 Self::new(config.offline || config.tracing.external_identification_timeout == 0)
203 }
204
205 pub fn new_offline_with_abis<'a>(abis: impl IntoIterator<Item = &'a JsonAbi>) -> Result<Self> {
207 Ok(Self::new_offline_with_abis_from_cache(Config::foundry_cache_dir().as_deref(), abis))
208 }
209
210 pub fn new_with(cache_dir: Option<&Path>, offline: bool) -> Result<Self> {
215 let client = if offline { None } else { Some(OpenChainClient::new()?) };
216 Ok(Self::from_cache(Self::load_cache(cache_dir), client))
217 }
218
219 fn new_offline_with_abis_from_cache<'a>(
220 cache_dir: Option<&Path>,
221 abis: impl IntoIterator<Item = &'a JsonAbi>,
222 ) -> Self {
223 let abis = abis.into_iter().collect::<Vec<_>>();
224 let (mut cache, cache_path) = Self::load_cache(cache_dir);
225 cache.extend_from_abis_without_collisions(abis.iter().copied());
226 let local_events = Self::local_events_from_abis(abis);
227 Self::from_cache_and_events((cache, cache_path), None, local_events)
228 }
229
230 fn load_cache(cache_dir: Option<&Path>) -> (SignaturesCache, Option<PathBuf>) {
231 if let Some(cache_dir) = cache_dir {
232 let path = cache_dir.join("signatures");
233 let cache = SignaturesCache::load(&path);
234 (cache, Some(path))
235 } else {
236 Default::default()
237 }
238 }
239
240 fn from_cache(
241 (cache, cache_path): (SignaturesCache, Option<PathBuf>),
242 client: Option<OpenChainClient>,
243 ) -> Self {
244 Self::from_cache_and_events((cache, cache_path), client, Default::default())
245 }
246
247 fn from_cache_and_events(
248 (cache, cache_path): (SignaturesCache, Option<PathBuf>),
249 client: Option<OpenChainClient>,
250 local_events: HashMap<(B256, usize), Vec<Event>>,
251 ) -> Self {
252 Self(Arc::new(SignaturesIdentifierInner {
253 cache: RwLock::new(cache),
254 local_events,
255 cache_path,
256 client,
257 }))
258 }
259
260 fn local_events_from_abis<'a>(
261 abis: impl IntoIterator<Item = &'a JsonAbi>,
262 ) -> HashMap<(B256, usize), Vec<Event>> {
263 let mut local_events: HashMap<(B256, usize), Vec<Event>> = HashMap::default();
264 for abi in abis {
265 for event in abi.events() {
266 local_events
267 .entry((
268 event.selector(),
269 event.inputs.iter().filter(|input| input.indexed).count(),
270 ))
271 .or_default()
272 .push(event.clone());
273 }
274 }
275 local_events
276 }
277
278 pub fn save(&self) {
280 self.0.save();
281 }
282
283 pub async fn identify_functions(
285 &self,
286 identifiers: impl IntoIterator<Item = Selector>,
287 ) -> Vec<Option<Function>> {
288 self.identify_map(identifiers.into_iter().map(SelectorKind::Function), get_func).await
289 }
290
291 pub async fn identify_function(&self, identifier: Selector) -> Option<Function> {
293 self.identify_functions([identifier]).await.pop().unwrap()
294 }
295
296 pub async fn identify_events(
298 &self,
299 identifiers: impl IntoIterator<Item = B256>,
300 ) -> Vec<Option<Event>> {
301 self.identify_map(identifiers.into_iter().map(SelectorKind::Event), get_event).await
302 }
303
304 pub async fn identify_event(&self, identifier: B256) -> Option<Event> {
306 self.identify_events([identifier]).await.pop().unwrap()
307 }
308
309 pub async fn identify_event_with_indexed_count(
311 &self,
312 identifier: B256,
313 indexed_count: usize,
314 ) -> Option<Event> {
315 if let Some(events) = self.0.local_events.get(&(identifier, indexed_count))
316 && let Some(event) = events.first()
317 {
318 return Some(event.clone());
319 }
320 self.identify_event(identifier).await
321 }
322
323 pub async fn identify_errors(
325 &self,
326 identifiers: impl IntoIterator<Item = Selector>,
327 ) -> Vec<Option<Error>> {
328 self.identify_map(identifiers.into_iter().map(SelectorKind::Error), get_error).await
329 }
330
331 pub async fn identify_error(&self, identifier: Selector) -> Option<Error> {
333 self.identify_errors([identifier]).await.pop().unwrap()
334 }
335
336 pub async fn identify(&self, selectors: &[SelectorKind]) -> Vec<Option<String>> {
338 if selectors.is_empty() {
339 return vec![];
340 }
341 trace!(target: "evm::traces", ?selectors, "identifying selectors");
342
343 let mut cache_r = self.0.cache.read().await;
344 if let Some(client) = &self.0.client
345 && selectors.iter().any(|selector| !cache_r.contains_key(selector))
346 {
347 drop(cache_r);
348 let mut cache_w = self.0.cache.write().await;
349 let query = selectors
350 .iter()
351 .copied()
352 .filter(|selector| !cache_w.contains_key(selector))
353 .collect::<Vec<_>>();
354 if !query.is_empty()
355 && let Ok(res) = client.decode_selectors(&query).await
356 {
357 for (selector, signatures) in std::iter::zip(query, res) {
358 cache_w.signatures.insert(selector, signatures.into_iter().next());
359 }
360 }
361 drop(cache_w);
362 cache_r = self.0.cache.read().await;
363 }
364 selectors.iter().map(|selector| cache_r.get(selector).unwrap_or_default()).collect()
365 }
366
367 async fn identify_map<T>(
368 &self,
369 selectors: impl IntoIterator<Item = SelectorKind>,
370 get_type: impl Fn(&str) -> Result<T>,
371 ) -> Vec<Option<T>> {
372 let results = self.identify(&Vec::from_iter(selectors)).await;
373 results.into_iter().map(|r| r.and_then(|r| get_type(&r).ok())).collect()
374 }
375}
376
377impl SignaturesIdentifierInner {
378 fn save(&self) {
379 if let Some(path) = &self.cache_path
381 && self.client.is_some()
382 {
383 self.cache
384 .try_read()
385 .expect("SignaturesIdentifier cache is locked while attempting to save")
386 .save(path);
387 }
388 }
389}
390
391impl Drop for SignaturesIdentifierInner {
392 fn drop(&mut self) {
393 self.save();
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn zero_external_identification_timeout_disables_client() {
403 let mut config = Config::default();
404 config.tracing.external_identification_timeout = 0;
405
406 let identifier = SignaturesIdentifier::from_config(&config).unwrap();
407
408 assert!(identifier.0.client.is_none());
409 }
410
411 #[test]
412 fn unknown_signatures_not_persisted_to_disk() {
413 let known_selector = SelectorKind::Function(Selector::from([0xaa, 0xbb, 0xcc, 0xdd]));
414 let unknown_selector = SelectorKind::Error(Selector::from([0x11, 0x22, 0x33, 0x44]));
415
416 let mut cache = SignaturesCache::default();
417 cache.signatures.insert(known_selector, Some("transfer(address,uint256)".into()));
418 cache.signatures.insert(unknown_selector, None);
419
420 assert!(cache.contains_key(&known_selector));
422 assert!(cache.contains_key(&unknown_selector));
423
424 let disk: SignaturesDiskCache = (&cache).into();
426 let reloaded = SignaturesCache::from(disk);
427
428 assert_eq!(reloaded.get(&known_selector), Some(Some("transfer(address,uint256)".into())));
430 assert_eq!(reloaded.get(&unknown_selector), None);
432 assert!(!reloaded.contains_key(&unknown_selector));
433 }
434
435 #[tokio::test]
436 async fn abi_seeded_signatures_are_not_persisted_to_disk() {
437 let temp = tempfile::tempdir().unwrap();
438 let event = Event::parse("event CodexEphemeral(uint256 indexed value)").unwrap();
439 let mut abi = JsonAbi::default();
440 abi.events.insert(event.name.clone(), vec![event.clone()]);
441
442 {
443 let identifier =
444 SignaturesIdentifier::new_offline_with_abis_from_cache(Some(temp.path()), [&abi]);
445 let decoded = identifier.identify_event(event.selector()).await;
446 assert_eq!(decoded.as_ref().map(Event::full_signature), Some(event.full_signature()));
447 identifier.save();
448 }
449
450 let reloaded = SignaturesCache::load(&temp.path().join("signatures"));
451 assert!(!reloaded.contains_key(&SelectorKind::Event(event.selector())));
452 }
453
454 #[test]
455 fn abi_seeded_collisions_keep_first_signature() {
456 let first = Event::parse("event CodexCollision(uint256 indexed value)").unwrap();
457 let second = Event::parse("event CodexCollision(uint256 value)").unwrap();
458
459 let mut first_abi = JsonAbi::default();
460 first_abi.events.insert(first.name.clone(), vec![first.clone()]);
461 let mut second_abi = JsonAbi::default();
462 second_abi.events.insert(second.name.clone(), vec![second]);
463
464 let mut cache = SignaturesCache::default();
465 cache.extend_from_abis_without_collisions([&first_abi, &second_abi]);
466
467 assert_eq!(
468 cache.get(&SelectorKind::Event(first.selector())),
469 Some(Some(first.full_signature()))
470 );
471 }
472
473 #[tokio::test]
474 async fn abi_seeded_events_prefer_matching_indexed_count() {
475 let one_topic =
476 Event::parse("event CodexIndexedCount(uint256 indexed marker, uint256 value)").unwrap();
477 let two_topics =
478 Event::parse("event CodexIndexedCount(uint256 indexed marker, uint256 indexed value)")
479 .unwrap();
480
481 let mut one_topic_abi = JsonAbi::default();
482 one_topic_abi.events.insert(one_topic.name.clone(), vec![one_topic.clone()]);
483 let mut two_topics_abi = JsonAbi::default();
484 two_topics_abi.events.insert(two_topics.name.clone(), vec![two_topics.clone()]);
485
486 let identifier = SignaturesIdentifier::new_offline_with_abis_from_cache(
487 None,
488 [&two_topics_abi, &one_topic_abi],
489 );
490
491 let decoded_one_topic =
492 identifier.identify_event_with_indexed_count(one_topic.selector(), 1).await.unwrap();
493 let decoded_two_topics =
494 identifier.identify_event_with_indexed_count(two_topics.selector(), 2).await.unwrap();
495
496 assert_eq!(decoded_one_topic.full_signature(), one_topic.full_signature());
497 assert_eq!(decoded_two_topics.full_signature(), two_topics.full_signature());
498 }
499}