foundry_evm_traces/identifier/
signatures.rs
1use alloy_json_abi::{Error, Event, Function};
2use alloy_primitives::{hex, map::HashSet};
3use foundry_common::{
4 abi::{get_error, get_event, get_func},
5 fs,
6 selectors::{OpenChainClient, SelectorType},
7};
8use serde::{Deserialize, Serialize};
9use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
10use tokio::sync::RwLock;
11
12pub type SingleSignaturesIdentifier = Arc<RwLock<SignaturesIdentifier>>;
13
14#[derive(Debug, Default, Serialize, Deserialize)]
15pub struct CachedSignatures {
16 pub errors: BTreeMap<String, String>,
17 pub events: BTreeMap<String, String>,
18 pub functions: BTreeMap<String, String>,
19}
20
21impl CachedSignatures {
22 #[instrument(target = "evm::traces")]
23 pub fn load(cache_path: PathBuf) -> Self {
24 let path = cache_path.join("signatures");
25 if path.is_file() {
26 fs::read_json_file(&path)
27 .map_err(
28 |err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file"),
29 )
30 .unwrap_or_default()
31 } else {
32 if let Err(err) = std::fs::create_dir_all(cache_path) {
33 warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err);
34 }
35 Self::default()
36 }
37 }
38}
39#[derive(Debug)]
42pub struct SignaturesIdentifier {
43 cached: CachedSignatures,
45 cached_path: Option<PathBuf>,
47 unavailable: HashSet<String>,
49 client: Option<OpenChainClient>,
51}
52
53impl SignaturesIdentifier {
54 #[instrument(target = "evm::traces")]
55 pub fn new(
56 cache_path: Option<PathBuf>,
57 offline: bool,
58 ) -> eyre::Result<SingleSignaturesIdentifier> {
59 let client = if !offline { Some(OpenChainClient::new()?) } else { None };
60
61 let identifier = if let Some(cache_path) = cache_path {
62 let path = cache_path.join("signatures");
63 trace!(target: "evm::traces", ?path, "reading signature cache");
64 let cached = CachedSignatures::load(cache_path);
65 Self { cached, cached_path: Some(path), unavailable: HashSet::default(), client }
66 } else {
67 Self {
68 cached: Default::default(),
69 cached_path: None,
70 unavailable: HashSet::default(),
71 client,
72 }
73 };
74
75 Ok(Arc::new(RwLock::new(identifier)))
76 }
77
78 #[instrument(target = "evm::traces", skip(self))]
79 pub fn save(&self) {
80 if let Some(cached_path) = &self.cached_path {
81 if let Some(parent) = cached_path.parent() {
82 if let Err(err) = std::fs::create_dir_all(parent) {
83 warn!(target: "evm::traces", ?parent, ?err, "failed to create cache");
84 }
85 }
86 if let Err(err) = fs::write_json_file(cached_path, &self.cached) {
87 warn!(target: "evm::traces", ?cached_path, ?err, "failed to flush signature cache");
88 } else {
89 trace!(target: "evm::traces", ?cached_path, "flushed signature cache")
90 }
91 }
92 }
93}
94
95impl SignaturesIdentifier {
96 async fn identify<T>(
97 &mut self,
98 selector_type: SelectorType,
99 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
100 get_type: impl Fn(&str) -> eyre::Result<T>,
101 ) -> Vec<Option<T>> {
102 let cache = match selector_type {
103 SelectorType::Function => &mut self.cached.functions,
104 SelectorType::Event => &mut self.cached.events,
105 SelectorType::Error => &mut self.cached.errors,
106 };
107
108 let hex_identifiers: Vec<String> =
109 identifiers.into_iter().map(hex::encode_prefixed).collect();
110
111 if let Some(client) = &self.client {
112 let query: Vec<_> = hex_identifiers
113 .iter()
114 .filter(|v| !cache.contains_key(v.as_str()))
115 .filter(|v| !self.unavailable.contains(v.as_str()))
116 .collect();
117
118 if let Ok(res) = client.decode_selectors(selector_type, query.clone()).await {
119 for (hex_id, selector_result) in query.into_iter().zip(res.into_iter()) {
120 let mut found = false;
121 if let Some(decoded_results) = selector_result {
122 if let Some(decoded_result) = decoded_results.into_iter().next() {
123 cache.insert(hex_id.clone(), decoded_result);
124 found = true;
125 }
126 }
127 if !found {
128 self.unavailable.insert(hex_id.clone());
129 }
130 }
131 }
132 }
133
134 hex_identifiers.iter().map(|v| cache.get(v).and_then(|v| get_type(v).ok())).collect()
135 }
136
137 pub async fn identify_functions(
139 &mut self,
140 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
141 ) -> Vec<Option<Function>> {
142 self.identify(SelectorType::Function, identifiers, get_func).await
143 }
144
145 pub async fn identify_function(&mut self, identifier: &[u8]) -> Option<Function> {
147 self.identify_functions(&[identifier]).await.pop().unwrap()
148 }
149
150 pub async fn identify_events(
152 &mut self,
153 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
154 ) -> Vec<Option<Event>> {
155 self.identify(SelectorType::Event, identifiers, get_event).await
156 }
157
158 pub async fn identify_event(&mut self, identifier: &[u8]) -> Option<Event> {
160 self.identify_events(&[identifier]).await.pop().unwrap()
161 }
162
163 pub async fn identify_errors(
165 &mut self,
166 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
167 ) -> Vec<Option<Error>> {
168 self.identify(SelectorType::Error, identifiers, get_error).await
169 }
170
171 pub async fn identify_error(&mut self, identifier: &[u8]) -> Option<Error> {
173 self.identify_errors(&[identifier]).await.pop().unwrap()
174 }
175}
176
177impl Drop for SignaturesIdentifier {
178 fn drop(&mut self) {
179 self.save();
180 }
181}