foundry_evm/inspectors/
tempo_labels.rs1use alloy_primitives::{U256, keccak256, map::AddressMap};
2use foundry_evm_core::backend::DatabaseError;
3use revm::{
4 Database, Inspector,
5 context::ContextTr,
6 inspector::JournalExt,
7 interpreter::{CallInputs, CallOutcome, interpreter::EthInterpreter},
8};
9use tempo_primitives::TempoAddressExt;
10
11const MAX_NAME_BYTES: usize = 256;
13
14#[derive(Default, Clone, Debug)]
20pub struct TempoLabels {
21 pub(crate) labels: AddressMap<String>,
22}
23
24impl<CTX, D> Inspector<CTX, EthInterpreter> for TempoLabels
25where
26 D: Database<Error = DatabaseError>,
27 CTX: ContextTr<Db = D>,
28 CTX::Journal: JournalExt,
29{
30 fn call(&mut self, ctx: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
31 if inputs.target_address.is_tip20() && !self.labels.contains_key(&inputs.target_address) {
32 let name = 'decode: {
33 let db = ctx.db_mut();
34 let address = inputs.target_address;
35 let slot = tempo_precompiles::tip20::slots::NAME;
36 let Ok(value) = db.storage(address, slot) else { break 'decode None };
37 let bytes = value.to_be_bytes::<32>();
38 if bytes[31] & 1 == 0 {
39 let len = usize::from(bytes[31] / 2);
40 break 'decode (1..=31)
41 .contains(&len)
42 .then(|| String::from_utf8_lossy(&bytes[..len]).into_owned());
43 }
44
45 let len = value >> 1usize;
47 if len < U256::from(32) || len > U256::from(MAX_NAME_BYTES) {
48 break 'decode None;
49 }
50 let len = len.to::<usize>();
51 let start = U256::from_be_bytes(keccak256(slot.to_be_bytes::<32>()).0);
52 let mut data = Vec::with_capacity(len);
53 for i in 0..len.div_ceil(32) {
54 let Ok(chunk) = db.storage(address, start + U256::from(i)) else {
55 break 'decode None;
56 };
57 let chunk = chunk.to_be_bytes::<32>();
58 data.extend_from_slice(&chunk[..(len - data.len()).min(32)]);
59 }
60 Some(String::from_utf8_lossy(&data).into_owned())
61 }
62 .unwrap_or_else(|| "TIP20".to_string());
63 self.labels.insert(inputs.target_address, name);
64 }
65
66 None
67 }
68}