Skip to main content

chaincodec_evm/
fingerprint.rs

1//! EVM event fingerprint computation.
2//!
3//! The fingerprint of an EVM event is the keccak256 hash of its canonical
4//! signature string, e.g.:
5//!   keccak256("Swap(address,address,int256,int256,uint160,uint128,int24)")
6//!   → 0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67
7//!
8//! For raw events, topics[0] IS the fingerprint — we do not need to recompute it.
9
10use chaincodec_core::event::EventFingerprint;
11use tiny_keccak::{Hasher, Keccak};
12
13/// Compute the keccak256 fingerprint of an event signature string.
14/// Input: `"EventName(type1,type2,...)"` — the canonical ABI signature.
15pub fn keccak256_signature(signature: &str) -> EventFingerprint {
16    let mut hasher = Keccak::v256();
17    let mut output = [0u8; 32];
18    hasher.update(signature.as_bytes());
19    hasher.finalize(&mut output);
20    EventFingerprint::new(format!("0x{}", hex::encode(output)))
21}
22
23/// Extract the fingerprint directly from a raw EVM event (topics[0]).
24/// Returns `None` if topics is empty or the first topic is malformed.
25pub fn from_topics(topics: &[String]) -> Option<EventFingerprint> {
26    let first = topics.first()?;
27    // Validate it looks like a 32-byte hex hash
28    let hex = first.strip_prefix("0x").unwrap_or(first);
29    if hex.len() == 64 && hex.chars().all(|c| c.is_ascii_hexdigit()) {
30        Some(EventFingerprint::new(first.clone()))
31    } else {
32        None
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn uniswap_v3_swap_fingerprint() {
42        // Well-known fingerprint for Uniswap V3 Swap event
43        let sig = "Swap(address,address,int256,int256,uint160,uint128,int24)";
44        let fp = keccak256_signature(sig);
45        assert_eq!(
46            fp.as_hex(),
47            "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"
48        );
49    }
50
51    #[test]
52    fn erc20_transfer_fingerprint() {
53        let sig = "Transfer(address,address,uint256)";
54        let fp = keccak256_signature(sig);
55        assert_eq!(
56            fp.as_hex(),
57            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
58        );
59    }
60
61    #[test]
62    fn from_topics_valid() {
63        let topics = vec![
64            "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67".to_string(),
65        ];
66        let fp = from_topics(&topics);
67        assert!(fp.is_some());
68    }
69
70    #[test]
71    fn from_topics_empty() {
72        assert!(from_topics(&[]).is_none());
73    }
74}