Skip to main content

chainerrors_core/
registry.rs

1//! Error signature registry — maps 4-byte selectors to known error ABIs.
2
3use serde::{Deserialize, Serialize};
4
5/// A known error signature from the registry.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ErrorSignature {
8    /// Error name (e.g. `"InsufficientBalance"`).
9    pub name: String,
10    /// Full signature string (e.g. `"InsufficientBalance(address,uint256)"`).
11    pub signature: String,
12    /// 4-byte selector (keccak256 of signature, first 4 bytes).
13    pub selector: [u8; 4],
14    /// ABI-encoded parameter types in order.
15    pub inputs: Vec<ErrorParam>,
16    /// Source of this signature (e.g. `"bundled"`, `"4byte.directory"`, `"user"`).
17    pub source: String,
18    /// Optional human-readable suggestion for this error.
19    pub suggestion: Option<String>,
20}
21
22/// A single parameter in an error signature.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ErrorParam {
25    /// Parameter name (may be empty for unnamed params).
26    pub name: String,
27    /// Solidity type string (e.g. `"address"`, `"uint256"`, `"bytes32"`).
28    pub ty: String,
29}
30
31/// Trait for looking up error signatures by 4-byte selector.
32pub trait ErrorSignatureRegistry: Send + Sync {
33    /// Look up all known signatures matching a 4-byte selector.
34    /// Returns multiple signatures when there are collisions.
35    fn get_by_selector(&self, selector: [u8; 4]) -> Vec<ErrorSignature>;
36
37    /// Look up a signature by full name (e.g. `"InsufficientBalance"`).
38    fn get_by_name(&self, name: &str) -> Option<ErrorSignature>;
39
40    /// Total number of registered signatures.
41    fn len(&self) -> usize;
42
43    /// Returns `true` if the registry is empty.
44    fn is_empty(&self) -> bool {
45        self.len() == 0
46    }
47}
48
49// ─── In-memory registry ───────────────────────────────────────────────────────
50
51use std::collections::HashMap;
52use std::sync::RwLock;
53
54/// A simple in-memory registry backed by `HashMap`.
55pub struct MemoryErrorRegistry {
56    /// selector → list of signatures (handle collisions)
57    by_selector: RwLock<HashMap<[u8; 4], Vec<ErrorSignature>>>,
58    /// name → first registered signature
59    by_name: RwLock<HashMap<String, ErrorSignature>>,
60}
61
62impl MemoryErrorRegistry {
63    /// Create an empty registry.
64    pub fn new() -> Self {
65        Self {
66            by_selector: RwLock::new(HashMap::new()),
67            by_name: RwLock::new(HashMap::new()),
68        }
69    }
70
71    /// Register a signature.
72    pub fn register(&self, sig: ErrorSignature) {
73        let mut by_sel = self.by_selector.write().unwrap();
74        by_sel.entry(sig.selector).or_default().push(sig.clone());
75        let mut by_name = self.by_name.write().unwrap();
76        by_name.entry(sig.name.clone()).or_insert(sig);
77    }
78
79    /// Load signatures from a JSON array string.
80    /// Expected format: `[{ "name": "...", "signature": "...", ... }, ...]`
81    pub fn load_json(&self, json: &str) -> Result<usize, serde_json::Error> {
82        let sigs: Vec<ErrorSignature> = serde_json::from_str(json)?;
83        let count = sigs.len();
84        for sig in sigs {
85            self.register(sig);
86        }
87        Ok(count)
88    }
89}
90
91impl Default for MemoryErrorRegistry {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl ErrorSignatureRegistry for MemoryErrorRegistry {
98    fn get_by_selector(&self, selector: [u8; 4]) -> Vec<ErrorSignature> {
99        self.by_selector
100            .read()
101            .unwrap()
102            .get(&selector)
103            .cloned()
104            .unwrap_or_default()
105    }
106
107    fn get_by_name(&self, name: &str) -> Option<ErrorSignature> {
108        self.by_name.read().unwrap().get(name).cloned()
109    }
110
111    fn len(&self) -> usize {
112        self.by_selector.read().unwrap().values().map(|v| v.len()).sum()
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    fn make_sig(name: &str, sig_str: &str, selector: [u8; 4]) -> ErrorSignature {
121        ErrorSignature {
122            name: name.to_string(),
123            signature: sig_str.to_string(),
124            selector,
125            inputs: vec![],
126            source: "test".to_string(),
127            suggestion: None,
128        }
129    }
130
131    #[test]
132    fn register_and_lookup() {
133        let reg = MemoryErrorRegistry::new();
134        let selector = [0x08, 0xc3, 0x79, 0xa0];
135        reg.register(make_sig("Error", "Error(string)", selector));
136
137        let results = reg.get_by_selector(selector);
138        assert_eq!(results.len(), 1);
139        assert_eq!(results[0].name, "Error");
140    }
141
142    #[test]
143    fn lookup_by_name() {
144        let reg = MemoryErrorRegistry::new();
145        reg.register(make_sig("Foo", "Foo(uint256)", [0x00, 0x00, 0x00, 0x01]));
146        assert!(reg.get_by_name("Foo").is_some());
147        assert!(reg.get_by_name("Bar").is_none());
148    }
149
150    #[test]
151    fn load_json_empty_array() {
152        let reg = MemoryErrorRegistry::new();
153        let count = reg.load_json("[]").unwrap();
154        assert_eq!(count, 0);
155        assert!(reg.is_empty());
156    }
157}