foundry_common/
ens.rs

1//! ENS Name resolving utilities.
2
3#![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
12// ENS Registry and Resolver contracts.
13sol! {
14    /// ENS Registry contract.
15    #[sol(rpc)]
16    contract EnsRegistry {
17        /// Returns the resolver for the specified node.
18        function resolver(bytes32 node) view returns (address);
19    }
20
21    /// ENS Resolver interface.
22    #[sol(rpc)]
23    contract EnsResolver {
24        /// Returns the address associated with the specified node.
25        function addr(bytes32 node) view returns (address);
26
27        /// Returns the name associated with an ENS node, for reverse records.
28        function name(bytes32 node) view returns (string);
29    }
30}
31
32/// ENS registry address (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`)
33pub const ENS_ADDRESS: Address = address!("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e");
34
35pub const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse";
36
37/// Error type for ENS resolution.
38#[derive(Debug, thiserror::Error)]
39pub enum EnsError {
40    /// Failed to get resolver from the ENS registry.
41    #[error("Failed to get resolver from the ENS registry: {0}")]
42    Resolver(alloy_contract::Error),
43    /// Failed to get resolver from the ENS registry.
44    #[error("ENS resolver not found for name {0:?}")]
45    ResolverNotFound(String),
46    /// Failed to lookup ENS name from an address.
47    #[error("Failed to lookup ENS name from an address: {0}")]
48    Lookup(alloy_contract::Error),
49    /// Failed to resolve ENS name to an address.
50    #[error("Failed to resolve ENS name to an address: {0}")]
51    Resolve(alloy_contract::Error),
52}
53
54/// ENS name or Ethereum Address.
55#[derive(Clone, Debug, PartialEq, Eq)]
56pub enum NameOrAddress {
57    /// An ENS Name (format does not get checked)
58    Name(String),
59    /// An Ethereum Address
60    Address(Address),
61}
62
63impl NameOrAddress {
64    /// Resolves the name to an Ethereum Address.
65    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/// Extension trait for ENS contract calls.
112#[async_trait]
113pub trait ProviderEnsExt<N: Network, P: Provider<N>> {
114    /// Returns the resolver for the specified node. The `&str` is only used for error messages.
115    async fn get_resolver(
116        &self,
117        node: B256,
118        error_name: &str,
119    ) -> Result<EnsResolverInstance<(), &P, N>, EnsError>;
120
121    /// Performs a forward lookup of an ENS name to an address.
122    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    /// Performs a reverse lookup of an address to an ENS name.
138    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
167/// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137)
168pub fn namehash(name: &str) -> B256 {
169    if name.is_empty() {
170        return B256::ZERO
171    }
172
173    // Remove the variation selector `U+FE0F` if present.
174    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    // Generate the node starting from the right.
182    // This buffer is `[node @ [u8; 32], label_hash @ [u8; 32]]`.
183    let mut buffer = [0u8; 64];
184    for label in name.rsplit('.') {
185        // node = keccak256([node, keccak256(label)])
186
187        // Hash the label.
188        let mut label_hasher = Keccak256::new();
189        label_hasher.update(label.as_bytes());
190        label_hasher.finalize_into(&mut buffer[32..]);
191
192        // Hash both the node and the label hash, writing into the node.
193        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
200/// Returns the reverse-registrar name of an address.
201pub 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", // 41
248            "0x00000000000000000000000000000000000000000", // 43
249            "0x28679A1a632125fbBf7A68d850E50623194A709E123", // 44
250        ] {
251            assert!(NameOrAddress::from_str(addr).is_err());
252        }
253    }
254}