anvil_zksync_traces/identifier/
signatures.rs1use crate::abi_utils::{get_error, get_event, get_func};
11use alloy::json_abi::{Error, Event, Function};
12use alloy::primitives::hex;
13use anvil_zksync_common::{
14 resolver::{SelectorType, SignEthClient},
15 utils::io::read_json_file,
16 utils::io::write_json_file,
17};
18use once_cell::sync::Lazy;
19use serde::{Deserialize, Serialize};
20use std::time::Instant;
21use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
22use tokio::sync::RwLock;
23
24static GLOBAL_CLIENT: Lazy<SignaturesIdentifier> = Lazy::new(SignaturesIdentifier::default);
26
27#[derive(Debug, Default, Serialize, Deserialize)]
28pub struct CachedSignatures {
29 pub errors: BTreeMap<String, Option<String>>,
30 pub events: BTreeMap<String, Option<String>>,
31 pub functions: BTreeMap<String, Option<String>>,
32}
33
34impl CachedSignatures {
35 pub fn load(cache_path: PathBuf) -> Self {
36 let path = cache_path.join("signatures");
37 if path.is_file() {
38 read_json_file(&path)
39 .map_err(
40 |err| tracing::warn!(target: "trace::signatures", ?path, ?err, "failed to read cache file"),
41 )
42 .unwrap_or_default()
43 } else {
44 if let Err(err) = std::fs::create_dir_all(cache_path) {
45 tracing::warn!(target: "trace::signatures", "could not create signatures cache dir: {:?}", err);
46 }
47 Self::default()
48 }
49 }
50
51 pub fn save(&self, cache_path: &PathBuf) {
52 if self.is_empty() {
53 return;
55 }
56 if let Some(parent) = cache_path.parent() {
57 if let Err(err) = std::fs::create_dir_all(parent) {
58 tracing::warn!(target: "trace::signatures", ?parent, ?err, "failed to create cache");
59 }
60 }
61 if let Err(err) = write_json_file(cache_path, &self) {
62 tracing::warn!(target: "trace::signatures", ?cache_path, ?err, "failed to flush signature cache");
63 } else {
64 tracing::trace!(target: "trace::signatures", ?cache_path, "flushed signature cache")
65 }
66 }
67
68 pub fn is_empty(&self) -> bool {
69 self.errors.is_empty() && self.events.is_empty() && self.functions.is_empty()
70 }
71}
72
73#[derive(Debug, Default, Clone)]
76pub struct SignaturesIdentifier {
77 inner: Arc<RwLock<SignaturesIdentifierInner>>,
78}
79
80#[derive(Debug, Default)]
81struct SignaturesIdentifierInner {
82 cached: CachedSignatures,
84 cached_path: Option<PathBuf>,
86 client: Option<SignEthClient>,
88}
89
90impl SignaturesIdentifierInner {
91 fn new(cache_path: Option<PathBuf>, offline: bool) -> eyre::Result<Self> {
92 let client = if !offline {
93 Some(SignEthClient::new())
94 } else {
95 None
96 };
97
98 let self_ = if let Some(cache_path) = cache_path {
99 let path = cache_path.join("signatures");
100 tracing::trace!(target: "trace::signatures", ?path, "reading signature cache");
101 let cached = CachedSignatures::load(cache_path);
102 SignaturesIdentifierInner {
103 cached,
104 cached_path: Some(path),
105 client,
106 }
107 } else {
108 SignaturesIdentifierInner {
109 cached: Default::default(),
110 cached_path: None,
111 client,
112 }
113 };
114 Ok(self_)
115 }
116
117 fn save(&self) {
118 if let Some(cached_path) = &self.cached_path {
119 self.cached.save(cached_path);
120 }
121 }
122
123 async fn identify<T>(
124 &mut self,
125 selector_type: SelectorType,
126 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
127 get_type: impl Fn(&str) -> eyre::Result<T>,
128 ) -> Vec<Option<T>> {
129 let cache = match selector_type {
130 SelectorType::Function => &mut self.cached.functions,
131 SelectorType::Event => &mut self.cached.events,
132 SelectorType::Error => &mut self.cached.errors,
133 };
134
135 let hex_identifiers: Vec<String> =
136 identifiers.into_iter().map(hex::encode_prefixed).collect();
137
138 if let Some(client) = &self.client {
139 let query: Vec<_> = hex_identifiers
140 .iter()
141 .filter(|v| !cache.contains_key(v.as_str()))
142 .collect();
143
144 if !query.is_empty() {
145 let start = Instant::now();
146 let n_queries = query.len();
147 let res = client.decode_selectors(selector_type, query.clone()).await;
150 if let Ok(res) = res {
151 for (hex_id, selector_result) in query.into_iter().zip(res.into_iter()) {
152 let mut found = false;
153 if let Some(decoded_results) = selector_result {
154 if let Some(decoded_result) = decoded_results.into_iter().next() {
155 cache.insert(hex_id.clone(), Some(decoded_result));
156 found = true;
157 }
158 }
159 if !found {
160 cache.insert(hex_id.clone(), None);
161 }
162 }
163 }
164 tracing::debug!(
165 "Queried {} signatures from remote source in {:?}",
166 n_queries,
167 start.elapsed()
168 );
169 }
170 }
171
172 hex_identifiers
173 .iter()
174 .map(|v| {
175 if let Some(name) = cache.get(v) {
176 name.as_ref().and_then(|s| get_type(s).ok())
177 } else {
178 None
179 }
180 })
181 .collect()
182 }
183}
184
185impl SignaturesIdentifier {
186 pub fn new(cache_path: Option<PathBuf>, offline: bool) -> eyre::Result<Self> {
187 let inner = SignaturesIdentifierInner::new(cache_path, offline)?;
188 Ok(Self {
189 inner: Arc::new(RwLock::new(inner)),
190 })
191 }
192
193 pub async fn save(&self) {
194 self.inner.read().await.save();
195 }
196
197 pub async fn install(cache_path: Option<PathBuf>, offline: bool) -> eyre::Result<()> {
198 *GLOBAL_CLIENT.inner.write().await = SignaturesIdentifierInner::new(cache_path, offline)?;
199
200 Ok(())
201 }
202
203 pub fn global() -> Self {
204 GLOBAL_CLIENT.clone()
205 }
206
207 pub async fn identify_functions(
209 &self,
210 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
211 ) -> Vec<Option<Function>> {
212 self.inner
213 .write()
214 .await
215 .identify(SelectorType::Function, identifiers, get_func)
216 .await
217 }
218
219 pub async fn identify_function(&self, identifier: &[u8]) -> Option<Function> {
221 self.identify_functions(&[identifier]).await.pop().unwrap()
222 }
223
224 pub async fn identify_events(
226 &self,
227 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
228 ) -> Vec<Option<Event>> {
229 self.inner
230 .write()
231 .await
232 .identify(SelectorType::Event, identifiers, get_event)
233 .await
234 }
235
236 pub async fn identify_event(&self, identifier: &[u8]) -> Option<Event> {
238 self.identify_events(&[identifier]).await.pop().unwrap()
239 }
240
241 pub async fn identify_errors(
243 &self,
244 identifiers: impl IntoIterator<Item = impl AsRef<[u8]>>,
245 ) -> Vec<Option<Error>> {
246 self.inner
247 .write()
248 .await
249 .identify(SelectorType::Error, identifiers, get_error)
250 .await
251 }
252
253 pub async fn identify_error(&self, identifier: &[u8]) -> Option<Error> {
255 self.identify_errors(&[identifier]).await.pop().unwrap()
256 }
257}
258
259#[cfg(test)]
260#[allow(clippy::needless_return)]
261mod tests {
262 use super::*;
263
264 #[tokio::test(flavor = "multi_thread")]
265 async fn can_query_signatures() {
266 let tmp = tempfile::Builder::new()
267 .prefix("sig-test")
268 .tempdir()
269 .expect("failed creating temporary dir");
270 {
271 let sigs = SignaturesIdentifier::new(Some(tmp.path().into()), false).unwrap();
272
273 assert!(sigs.inner.read().await.cached.events.is_empty());
274 assert!(sigs.inner.read().await.cached.functions.is_empty());
275
276 let func = sigs.identify_function(&[35, 184, 114, 221]).await.unwrap();
277 let event = sigs
278 .identify_event(&[
279 39, 119, 42, 220, 99, 219, 7, 170, 231, 101, 183, 30, 178, 181, 51, 6, 79, 167,
280 129, 189, 87, 69, 126, 27, 19, 133, 146, 216, 25, 141, 9, 89,
281 ])
282 .await
283 .unwrap();
284
285 assert_eq!(
286 func,
287 get_func("transferFrom(address,address,uint256)").unwrap()
288 );
289 assert_eq!(
290 event,
291 get_event("Transfer(address,address,uint128)").unwrap()
292 );
293
294 sigs.save().await;
296 }
297
298 let sigs = SignaturesIdentifier::new(Some(tmp.path().into()), false).unwrap();
299 assert_eq!(sigs.inner.read().await.cached.events.len(), 1);
300 assert_eq!(sigs.inner.read().await.cached.functions.len(), 1);
301 }
302}