foundry_common/
ens.rs
1#![allow(missing_docs)]
4
5use self::EnsResolver::EnsResolverInstance;
6use alloy_primitives::{address, Address, Keccak256, B256};
7use alloy_provider::{Network, Provider};
8use alloy_sol_types::sol;
9use async_trait::async_trait;
10use std::{borrow::Cow, str::FromStr};
11
12sol! {
14 #[sol(rpc)]
16 contract EnsRegistry {
17 function resolver(bytes32 node) view returns (address);
19 }
20
21 #[sol(rpc)]
23 contract EnsResolver {
24 function addr(bytes32 node) view returns (address);
26
27 function name(bytes32 node) view returns (string);
29 }
30}
31
32pub const ENS_ADDRESS: Address = address!("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e");
34
35pub const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse";
36
37#[derive(Debug, thiserror::Error)]
39pub enum EnsError {
40 #[error("Failed to get resolver from the ENS registry: {0}")]
42 Resolver(alloy_contract::Error),
43 #[error("ENS resolver not found for name {0:?}")]
45 ResolverNotFound(String),
46 #[error("Failed to lookup ENS name from an address: {0}")]
48 Lookup(alloy_contract::Error),
49 #[error("Failed to resolve ENS name to an address: {0}")]
51 Resolve(alloy_contract::Error),
52}
53
54#[derive(Clone, Debug, PartialEq, Eq)]
56pub enum NameOrAddress {
57 Name(String),
59 Address(Address),
61}
62
63impl NameOrAddress {
64 pub async fn resolve<N: Network, P: Provider<N>>(
66 &self,
67 provider: &P,
68 ) -> Result<Address, EnsError> {
69 match self {
70 Self::Name(name) => provider.resolve_name(name).await,
71 Self::Address(addr) => Ok(*addr),
72 }
73 }
74}
75
76impl From<String> for NameOrAddress {
77 fn from(name: String) -> Self {
78 Self::Name(name)
79 }
80}
81
82impl From<&String> for NameOrAddress {
83 fn from(name: &String) -> Self {
84 Self::Name(name.clone())
85 }
86}
87
88impl From<Address> for NameOrAddress {
89 fn from(addr: Address) -> Self {
90 Self::Address(addr)
91 }
92}
93
94impl FromStr for NameOrAddress {
95 type Err = <Address as FromStr>::Err;
96
97 fn from_str(s: &str) -> Result<Self, Self::Err> {
98 match Address::from_str(s) {
99 Ok(addr) => Ok(Self::Address(addr)),
100 Err(err) => {
101 if s.contains('.') {
102 Ok(Self::Name(s.to_string()))
103 } else {
104 Err(err)
105 }
106 }
107 }
108 }
109}
110
111#[async_trait]
113pub trait ProviderEnsExt<N: Network, P: Provider<N>> {
114 async fn get_resolver(
116 &self,
117 node: B256,
118 error_name: &str,
119 ) -> Result<EnsResolverInstance<(), &P, N>, EnsError>;
120
121 async fn resolve_name(&self, name: &str) -> Result<Address, EnsError> {
123 let node = namehash(name);
124 let resolver = self.get_resolver(node, name).await?;
125 let addr = resolver
126 .addr(node)
127 .call()
128 .await
129 .map_err(EnsError::Resolve)
130 .inspect_err(|e| {
131 let _ = sh_eprintln!("{e:?}");
132 })?
133 ._0;
134 Ok(addr)
135 }
136
137 async fn lookup_address(&self, address: &Address) -> Result<String, EnsError> {
139 let name = reverse_address(address);
140 let node = namehash(&name);
141 let resolver = self.get_resolver(node, &name).await?;
142 let name = resolver.name(node).call().await.map_err(EnsError::Lookup)?._0;
143 Ok(name)
144 }
145}
146
147#[async_trait]
148impl<N, P> ProviderEnsExt<N, P> for P
149where
150 P: Provider<N>,
151 N: Network,
152{
153 async fn get_resolver(
154 &self,
155 node: B256,
156 error_name: &str,
157 ) -> Result<EnsResolverInstance<(), &P, N>, EnsError> {
158 let registry = EnsRegistry::new(ENS_ADDRESS, self);
159 let address = registry.resolver(node).call().await.map_err(EnsError::Resolver)?._0;
160 if address == Address::ZERO {
161 return Err(EnsError::ResolverNotFound(error_name.to_string()));
162 }
163 Ok(EnsResolverInstance::new(address, self))
164 }
165}
166
167pub fn namehash(name: &str) -> B256 {
169 if name.is_empty() {
170 return B256::ZERO
171 }
172
173 const VARIATION_SELECTOR: char = '\u{fe0f}';
175 let name = if name.contains(VARIATION_SELECTOR) {
176 Cow::Owned(name.replace(VARIATION_SELECTOR, ""))
177 } else {
178 Cow::Borrowed(name)
179 };
180
181 let mut buffer = [0u8; 64];
184 for label in name.rsplit('.') {
185 let mut label_hasher = Keccak256::new();
189 label_hasher.update(label.as_bytes());
190 label_hasher.finalize_into(&mut buffer[32..]);
191
192 let mut buffer_hasher = Keccak256::new();
194 buffer_hasher.update(buffer.as_slice());
195 buffer_hasher.finalize_into(&mut buffer[..32]);
196 }
197 buffer[..32].try_into().unwrap()
198}
199
200pub fn reverse_address(addr: &Address) -> String {
202 format!("{addr:x}.{ENS_REVERSE_REGISTRAR_DOMAIN}")
203}
204
205#[cfg(test)]
206mod test {
207 use super::*;
208 use alloy_primitives::hex;
209
210 fn assert_hex(hash: B256, val: &str) {
211 assert_eq!(hash.0[..], hex::decode(val).unwrap()[..]);
212 }
213
214 #[test]
215 fn test_namehash() {
216 for (name, expected) in &[
217 ("", "0x0000000000000000000000000000000000000000000000000000000000000000"),
218 ("eth", "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"),
219 ("foo.eth", "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f"),
220 ("alice.eth", "0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec"),
221 ("ret↩️rn.eth", "0x3de5f4c02db61b221e7de7f1c40e29b6e2f07eb48d65bf7e304715cd9ed33b24"),
222 ] {
223 assert_hex(namehash(name), expected);
224 }
225 }
226
227 #[test]
228 fn test_reverse_address() {
229 for (addr, expected) in [
230 (
231 "0x314159265dd8dbb310642f98f50c066173c1259b",
232 "314159265dd8dbb310642f98f50c066173c1259b.addr.reverse",
233 ),
234 (
235 "0x28679A1a632125fbBf7A68d850E50623194A709E",
236 "28679a1a632125fbbf7a68d850e50623194a709e.addr.reverse",
237 ),
238 ] {
239 assert_eq!(reverse_address(&addr.parse().unwrap()), expected, "{addr}");
240 }
241 }
242
243 #[test]
244 fn test_invalid_address() {
245 for addr in [
246 "0x314618",
247 "0x000000000000000000000000000000000000000", "0x00000000000000000000000000000000000000000", "0x28679A1a632125fbBf7A68d850E50623194A709E123", ] {
251 assert!(NameOrAddress::from_str(addr).is_err());
252 }
253 }
254}