Skip to content

Commit

Permalink
Patch reverse name resolution (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
Antony1060 authored Feb 23, 2024
1 parent 4ff8b7f commit a583ac1
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 13 deletions.
1 change: 1 addition & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::env;

use dotenvy::dotenv;
use ethers::prelude::namehash;
use futures::FutureExt;
use tokio::signal::unix::{signal, SignalKind};
use tokio_util::sync::CancellationToken;
Expand Down
24 changes: 16 additions & 8 deletions shared/src/core/address.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use ethers::providers::{Middleware, ProviderError};
use ethers::middleware::MiddlewareBuilder;
use ethers::providers::ProviderError;
use ethers_ccip_read::CCIPReadMiddleware;
use ethers_core::types::Address;
use thiserror::Error;

use crate::core::ENSService;
use crate::core::resolvers::reverse::{resolve_reverse, ReverseResolveError};

#[derive(Error, Debug)]
pub enum AddressResolveError {
Expand All @@ -14,6 +17,9 @@ pub enum AddressResolveError {

#[error("RPC error: {0}")]
RPCError(#[from] ProviderError),

#[error("Reverse resolution error: {0}")]
ReverseResolutionError(#[from] ReverseResolveError),
}

impl ENSService {
Expand All @@ -25,6 +31,7 @@ impl ENSService {
let cache_key = format!("a:{address:?}");

let rpc = self.rpc.get_instance();
let rpc = rpc.wrap_into(CCIPReadMiddleware::new);

// TODO: improve
let cached_name = if fresh {
Expand All @@ -37,14 +44,15 @@ impl ENSService {
let name = if let Some(name) = cached_name {
name
} else {
let result = rpc
.lookup_address(*address)
let result = resolve_reverse(&rpc, address, &self.universal_resolver)
.await
.or_else(|error| match error {
// address doesn't resolve, cache ""
ProviderError::EnsError(_) => Ok("".to_string()),
// yield error up, don't cache
_ => Err(error),
.or_else(|error| {
match error {
// address doesn't resolve, cache ""
ReverseResolveError::MissingPrimaryName => Ok("".to_string()),
// yield error up, don't cache
_ => Err(error),
}
})?;

// Cache the value, and expire it after 10 minutes
Expand Down
2 changes: 1 addition & 1 deletion shared/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod error;
pub mod lookup_data;
pub mod profile;
pub mod records;
pub mod universal_resolver;
pub mod resolvers;

pub type CCIPProvider = CCIPReadMiddleware<Arc<Provider<Http>>>;

Expand Down
3 changes: 1 addition & 2 deletions shared/src/core/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::core::lookup_data::LookupInfo;
use crate::core::ENSService;
use crate::models::lookup::{ENSLookup, ENSLookupError, LookupState};

use super::universal_resolver::resolve_universal;
use super::resolvers::universal::resolve_universal;

pub struct ResolvedCalldata {
pub resolver: Address,
Expand All @@ -35,7 +35,6 @@ impl ENSService {
// let cache_key = format!("n:{name}");

let rpc = self.rpc.get_instance();

let rpc = rpc.wrap_into(CCIPReadMiddleware::new);

// If the value is in the cache, return it
Expand Down
2 changes: 2 additions & 0 deletions shared/src/core/resolvers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod reverse;
pub mod universal;
168 changes: 168 additions & 0 deletions shared/src/core/resolvers/reverse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
use ethers::middleware::Middleware;
use ethers::prelude::{Address, H256, ProviderError};
use ethers::providers::{namehash, Provider};
use ethers_contract::providers::Http;
use ethers_core::abi;
use ethers_core::abi::{AbiEncode, ParamType, Token};
use ethers_core::types::Bytes;
use ethers_core::types::transaction::eip2718::TypedTransaction;
use hex_literal::hex;
use lazy_static::lazy_static;
use thiserror::Error;

use crate::core::CCIPProvider;
use crate::core::resolvers::universal::resolve_universal;
use crate::models::lookup::{addr, ENSLookup};

#[derive(Error, Debug)]
pub enum ReverseResolveError {
#[error("Address doesn't have a primary name")]
MissingPrimaryName,

#[error("Address on name doesn't match with lookup address")]
AddressMismatch,

#[error("Failed to lookup address for reverse record: {0}")]
AddressLookupError(String),

#[error("RPC provider error: {0}")]
RPCError(#[from] ProviderError),

#[error("ABI decode error: {0}")]
AbiDecodeError(#[from] abi::Error),
}

lazy_static! {
static ref BASE_REGISTRY: Address = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
.parse()
.expect("should be a valid address");
}

const REVERSE_NAME_SUFFIX: &str = "addr.reverse";

const RESOLVE_SELECTOR: [u8; 4] = hex!("0178b8bf");
const NAME_SELECTOR: [u8; 4] = hex!("691f3431");

async fn find_resolver(
rpc: &Provider<Http>,
namehash: &H256,
) -> Result<Address, ReverseResolveError> {
let mut transaction = TypedTransaction::default();

transaction.set_to(*BASE_REGISTRY);

let encoded = abi::encode(&[Token::FixedBytes(namehash.encode())]);
transaction.set_data(Bytes::from(
[&RESOLVE_SELECTOR, encoded.as_slice()].concat(),
));

let res = rpc.call(&transaction, None).await?;

let address = abi::decode(&[ParamType::Address], &res)?
.first()
.unwrap()
.clone()
.into_address()
.unwrap();

Ok(address)
}

pub async fn resolve_reverse(
rpc: &CCIPProvider,
address: &Address,
universal_resolver: &Address,
) -> Result<String, ReverseResolveError> {
let reverse_namehash = namehash(&format!(
"{}.{REVERSE_NAME_SUFFIX}",
hex::encode(address.as_bytes())
));

let resolver = find_resolver(rpc.inner(), &reverse_namehash).await?;

if resolver.is_zero() {
return Err(ReverseResolveError::MissingPrimaryName);
}

let mut transaction = TypedTransaction::default();

transaction.set_to(resolver);

let encoded = abi::encode(&[Token::FixedBytes(reverse_namehash.encode())]);
transaction.set_data(Bytes::from([&NAME_SELECTOR, encoded.as_slice()].concat()));

let res = rpc.inner().call(&transaction, None).await?;

let name = abi::decode(&[ParamType::String], &res)?
.first()
.unwrap()
.clone()
.into_string()
.unwrap();

let (mut res, _, _) = resolve_universal(&name, &[ENSLookup::Addr], rpc, universal_resolver)
.await
.map_err(|err| ReverseResolveError::AddressLookupError(err.to_string()))?;

let addr_result = res.remove(0);
if !addr_result.success {
return Err(ReverseResolveError::AddressMismatch);
}

let decoded = addr::decode(&addr_result.data)
.await
.map_err(|err| ReverseResolveError::AddressLookupError(err.to_string()))?;

if !matches!(decoded.parse::<Address>(), Ok(ref parsed) if parsed == address) {
return Err(ReverseResolveError::AddressMismatch);
}

Ok(name)
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use ethers::prelude::{Http, Provider};
use ethers_ccip_read::CCIPReadMiddleware;

use crate::core::resolvers::reverse::resolve_reverse;

#[tokio::test]
async fn test() {
let provider = Provider::<Http>::try_from("https://rpc.ankr.com/eth").unwrap();

let provider = CCIPReadMiddleware::new(Arc::new(provider));

assert_eq!(
resolve_reverse(
&provider,
&"0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"
.parse()
.unwrap(),
&"0x8cab227b1162f03b8338331adaad7aadc83b895e"
.parse()
.unwrap(),
)
.await
.ok(),
Some("nick.eth".to_string())
);

assert_eq!(
resolve_reverse(
&provider,
&"0x2B5c7025998f88550Ef2fEce8bf87935f542C190"
.parse()
.unwrap(),
&"0x8cab227b1162f03b8338331adaad7aadc83b895e"
.parse()
.unwrap(),
)
.await
.ok(),
Some("antony.sh".to_string())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ mod tests {
use ethers_core::abi::ParamType;
use ethers_core::types::Address;

use crate::core::universal_resolver;
use crate::core::resolvers::universal;
use crate::models::lookup::ENSLookup;

#[tokio::test]
Expand All @@ -221,7 +221,7 @@ mod tests {
ENSLookup::StaticText("location"),
];

let res = universal_resolver::resolve_universal(
let res = universal::resolve_universal(
"antony.sh",
&calldata,
&CCIPReadMiddleware::new(Arc::new(provider)),
Expand Down

0 comments on commit a583ac1

Please sign in to comment.