clear_signing/resolver/
source.rs1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4
5use crate::error::ResolveError;
6use crate::types::descriptor::Descriptor;
7
8#[derive(Debug, Clone)]
10pub struct ResolvedDescriptor {
11 pub descriptor: Descriptor,
12 pub chain_id: u64,
13 pub address: String,
14}
15
16#[derive(Debug, Clone)]
18pub struct TypedDescriptorLookup {
19 pub chain_id: u64,
20 pub verifying_contract: String,
21 pub primary_type: String,
22 pub encode_type_hash: Option<String>,
23}
24
25pub trait DescriptorSource: Send + Sync {
27 fn resolve_calldata(
29 &self,
30 chain_id: u64,
31 address: &str,
32 ) -> Pin<Box<dyn Future<Output = Result<ResolvedDescriptor, ResolveError>> + Send + '_>>;
33
34 fn resolve_typed_candidates(
36 &self,
37 lookup: TypedDescriptorLookup,
38 ) -> Pin<Box<dyn Future<Output = Result<Vec<ResolvedDescriptor>, ResolveError>> + Send + '_>>;
39}
40
41pub struct StaticSource {
43 calldata: HashMap<String, Descriptor>,
45 typed: HashMap<String, Vec<Descriptor>>,
46}
47
48impl StaticSource {
49 pub fn new() -> Self {
50 Self {
51 calldata: HashMap::new(),
52 typed: HashMap::new(),
53 }
54 }
55
56 fn make_key(chain_id: u64, address: &str) -> String {
57 format!("{}:{}", chain_id, address.to_lowercase())
58 }
59
60 pub fn add_calldata(&mut self, chain_id: u64, address: &str, descriptor: Descriptor) {
62 self.calldata
63 .insert(Self::make_key(chain_id, address), descriptor);
64 }
65
66 pub fn add_typed(&mut self, chain_id: u64, address: &str, descriptor: Descriptor) {
68 self.typed
69 .entry(Self::make_key(chain_id, address))
70 .or_default()
71 .push(descriptor);
72 }
73
74 pub fn add_calldata_json(
76 &mut self,
77 chain_id: u64,
78 address: &str,
79 json: &str,
80 ) -> Result<(), ResolveError> {
81 let descriptor: Descriptor =
82 serde_json::from_str(json).map_err(|e| ResolveError::Parse(e.to_string()))?;
83 self.add_calldata(chain_id, address, descriptor);
84 Ok(())
85 }
86
87 pub fn add_typed_json(
89 &mut self,
90 chain_id: u64,
91 address: &str,
92 json: &str,
93 ) -> Result<(), ResolveError> {
94 let descriptor: Descriptor =
95 serde_json::from_str(json).map_err(|e| ResolveError::Parse(e.to_string()))?;
96 self.add_typed(chain_id, address, descriptor);
97 Ok(())
98 }
99}
100
101impl Default for StaticSource {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl DescriptorSource for StaticSource {
108 fn resolve_calldata(
109 &self,
110 chain_id: u64,
111 address: &str,
112 ) -> Pin<Box<dyn Future<Output = Result<ResolvedDescriptor, ResolveError>> + Send + '_>> {
113 let key = Self::make_key(chain_id, address);
114 let result = self
115 .calldata
116 .get(&key)
117 .cloned()
118 .map(|descriptor| ResolvedDescriptor {
119 descriptor,
120 chain_id,
121 address: address.to_lowercase(),
122 })
123 .ok_or_else(|| ResolveError::NotFound {
124 chain_id,
125 address: address.to_string(),
126 });
127 Box::pin(async move { result })
128 }
129
130 fn resolve_typed_candidates(
131 &self,
132 lookup: TypedDescriptorLookup,
133 ) -> Pin<Box<dyn Future<Output = Result<Vec<ResolvedDescriptor>, ResolveError>> + Send + '_>>
134 {
135 let key = Self::make_key(lookup.chain_id, &lookup.verifying_contract);
136 let address_lower = lookup.verifying_contract.to_lowercase();
137 let primary_type = lookup.primary_type.clone();
138 let expected_hash = lookup.encode_type_hash.clone();
139 let result = self
140 .typed
141 .get(&key)
142 .map(|descriptors| {
143 descriptors
144 .iter()
145 .filter(|descriptor| {
146 descriptor.display.formats.keys().any(|key| {
147 let primary_matches = key == &primary_type
148 || key.strip_prefix(&format!("{primary_type}(")).is_some();
149 if !primary_matches {
150 return false;
151 }
152
153 match expected_hash.as_deref() {
154 Some(expected) if key.contains('(') => {
155 crate::eip712::format_key_hash_hex(key)
156 .eq_ignore_ascii_case(expected)
157 }
158 _ => true,
159 }
160 })
161 })
162 .cloned()
163 .map(|descriptor| ResolvedDescriptor {
164 descriptor,
165 chain_id: lookup.chain_id,
166 address: address_lower.clone(),
167 })
168 .collect::<Vec<_>>()
169 })
170 .unwrap_or_default();
171 let result = if result.is_empty() {
172 Err(ResolveError::NotFound {
173 chain_id: lookup.chain_id,
174 address: lookup.verifying_contract.clone(),
175 })
176 } else {
177 Ok(result)
178 };
179 Box::pin(async move { result })
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[tokio::test]
188 async fn test_static_source_not_found() {
189 let source = StaticSource::new();
190 let result = source.resolve_calldata(1, "0xabc").await;
191 assert!(result.is_err());
192 }
193}