Skip to content

Commit

Permalink
Bump universal resolver & introduce support for gasless DNSSEC resolu…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
Antony1060 committed Feb 11, 2024
1 parent 047df75 commit f86b663
Show file tree
Hide file tree
Showing 17 changed files with 144 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .github/.k8s/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ spec:
- name: REDIS_URL
value: redis://redis.enstate.svc.cluster.local:6379
- name: UNIVERSAL_RESOLVER
value: 0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62
value: 0x8cab227b1162f03b8338331adaad7aadc83b895e
resources:
requests:
cpu: 100m
Expand Down
2 changes: 1 addition & 1 deletion .github/.k8s/deploy_goerli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ spec:
- name: RPC_URL
value: https://rpc.ankr.com/eth_goerli,https://ethereum-goerli.publicnode.com,https://goerli.gateway.tenderly.co
- name: UNIVERSAL_RESOLVER
value: 0x3952Be0b2186f8B113193a84b69bD71ad3fc1ae3
value: 0xfc4AC75C46C914aF5892d6d3eFFcebD7917293F1
resources:
requests:
cpu: 100m
Expand Down
2 changes: 1 addition & 1 deletion .github/.k8s/deploy_sepolia.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ spec:
- name: RPC_URL
value: https://rpc.ankr.com/eth_sepolia,https://ethereum-sepolia.publicnode.com,https://sepolia.gateway.tenderly.co
- name: UNIVERSAL_RESOLVER
value: 0x21B000Fd62a880b2125A61e36a284BB757b76025
value: 0xBaBC7678D7A63104f1658c11D6AE9A21cdA09725
resources:
requests:
cpu: 100m
Expand Down
18 changes: 1 addition & 17 deletions server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ futures = "0.3.29"
utoipa = { version = "4.1.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "4.0.0", features = ["axum"] }
redis = { version = "0.23.0", features = ["connection-manager", "tokio-comp"] }
ethers-ccip-read = { git = "https://github.com/ensdomains/ethers-ccip-read", branch = "main" }
tower-http = { version = "0.4.4", features = ["cors", "tracing", "trace"] }
rand = "0.8.5"
chrono = "0.4.31"
Expand Down
1 change: 1 addition & 0 deletions shared/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod error;
pub mod lookup_data;
pub mod profile;
pub mod records;
pub mod universal_resolver;

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

Expand Down
12 changes: 10 additions & 2 deletions shared/src/core/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::core::error::ProfileError;
use crate::core::lookup_data::LookupInfo;
use crate::core::ENSService;
use crate::models::lookup::{ENSLookup, ENSLookupError, LookupState};
use crate::models::universal_resolver::resolve_universal;

use super::universal_resolver::resolve_universal;

pub struct ResolvedCalldata {
pub resolver: Address,
Expand Down Expand Up @@ -80,7 +81,14 @@ impl ENSService {
// Assume results & calldata have the same length
// Look through all calldata and decode the results at the same index
for (index, calldata) in calldata.iter().enumerate() {
let result = calldata.decode(data[index], &lookup_state).await;
let res = data[index];
// TODO: think about this
// current behaviour ignores all errors from a resolver
let result = if res.success {
calldata.decode(&res.data, &lookup_state).await
} else {
Ok(String::new())
};

match result {
Ok(result) if !result.is_empty() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,45 @@ use ethers::{
types::{transaction::eip2718::TypedTransaction, Address, Bytes},
};
use ethers_ccip_read::{CCIPReadMiddlewareError, CCIPRequest};
use ethers_contract::abigen;
use ethers_core::abi;
use ethers_core::abi::{ParamType, Token};
use ethers_core::types::H160;
use hex_literal::hex;
use lazy_static::lazy_static;

use crate::core::error::ProfileError;
use crate::core::CCIPProvider;
use crate::models::lookup::ENSLookup;
use crate::models::lookup::{addr, ENSLookup};
use crate::utils::dns::dns_encode;
use crate::utils::vec::dedup_ord;

abigen!(
IUResolver,
r#"[
function resolve(bytes name, bytes[] data) external view returns (bytes[], address)
]"#,
);
lazy_static! {
static ref OFFCHAIN_DNS_RESOLVER: Address =
Address::from(hex!("F142B308cF687d4358410a4cB885513b30A42025"));
}

const MAGIC_UNIVERSAL_RESOLVER_ERROR_MESSAGE: &str =
"Wildcard on non-extended resolvers is not supported";
pub struct UniversalResolverResult {
pub(crate) success: bool,
pub(crate) data: Vec<u8>,
}

pub async fn resolve_universal(
name: &str,
data: &[ENSLookup],
provider: &CCIPProvider,
universal_resolver: &H160,
) -> Result<(Vec<Vec<u8>>, Address, Vec<String>), ProfileError> {
) -> Result<(Vec<UniversalResolverResult>, Address, Vec<String>), ProfileError> {
let name_hash = namehash(name);

// Prepare the variables
let dns_encoded_node = dns_encode(name).map_err(ProfileError::DNSEncodeError)?;

// FIXME: don't force address lookup
let data = [&[ENSLookup::Addr] as &[ENSLookup], data].concat();

let wildcard_data = data
.iter()
.map(|x| x.calldata(&name_hash))
.map(|it| it.calldata(&name_hash))
.map(Token::Bytes)
.collect();

Expand All @@ -55,34 +59,39 @@ pub async fn resolve_universal(
// Prepare transaction data
let transaction_data = [resolve_selector, encoded_data].concat();

// Setup the transaction
// Set up the transaction
typed_transaction.set_to(*universal_resolver);
typed_transaction.set_data(Bytes::from(transaction_data));

// Call the transaction
let (res, ccip_requests) = provider
.call_ccip(&typed_transaction, None)
.await
.map_err(|err| {
let CCIPReadMiddlewareError::MiddlewareError(provider_error) = err else {
return ProfileError::CCIPError(err);
};

let JsonRpcClientError(rpc_err) = &provider_error else {
return ProfileError::RPCError(provider_error);
};

if matches!(rpc_err.as_error_response(), Some(rpc_err_raw) if rpc_err_raw.message.contains(MAGIC_UNIVERSAL_RESOLVER_ERROR_MESSAGE)) {
return ProfileError::NotFound;
}

ProfileError::RPCError(provider_error)
})?;
let (res, ccip_requests) =
provider
.call_ccip(&typed_transaction, None)
.await
.map_err(|err| {
let CCIPReadMiddlewareError::MiddlewareError(provider_error) = err else {
return ProfileError::CCIPError(err);
};

let JsonRpcClientError(rpc_err) = &provider_error else {
return ProfileError::RPCError(provider_error);
};

// TODO: better error handling
if rpc_err.as_error_response().is_some() {
return ProfileError::NotFound;
}

ProfileError::RPCError(provider_error)
})?;

// Abi Decode
let result = abi::decode(
&[
ParamType::Array(Box::new(ParamType::Bytes)),
ParamType::Array(Box::new(ParamType::Tuple(vec![
ParamType::Bool,
ParamType::Bytes,
]))),
ParamType::Address,
],
res.as_ref(),
Expand All @@ -94,24 +103,50 @@ pub async fn resolve_universal(
return Err(ProfileError::ImplementationError("".to_string()));
}

let result_data = result.get(0).expect("result[0] should exist").clone();
let result_data = result.first().expect("result[0] should exist").clone();
let result_address = result.get(1).expect("result[1] should exist").clone();

let resolver = result_address
.into_address()
.expect("result[1] should be an address");

if resolver.is_zero() {
let mut parsed: Vec<UniversalResolverResult> = result_data
.into_array()
.expect("result[0] should be an array")
.into_iter()
.map(|t| {
let tuple = t.into_tuple().expect("result[0] should be a tuple");

let success = tuple
.first()
.expect("result[0][0] should exist")
.clone()
.into_bool()
.expect("result[0][0] should be a boolean");
let data = tuple
.get(1)
.expect("result[0][1] should exist")
.clone()
.into_bytes()
.expect("result[0][1] elements should be bytes");

UniversalResolverResult { success, data }
})
.collect();

let addr = addr::decode(&parsed.remove(0).data).await;

// if we got a CCIP response, where the resolver is an OffchainDNSResolver and the address if zero
// it's a non-existing name
if resolver.is_zero()
|| (resolver == *OFFCHAIN_DNS_RESOLVER
&& matches!(addr, Ok(ref addr) if addr == "0x0000000000000000000000000000000000000000"))
{
return Err(ProfileError::NotFound);
}

Ok((
result_data
.into_array()
.expect("result[0] should be an array")
.into_iter()
.map(|t| t.into_bytes().expect("result[0] elements should be bytes"))
.collect(),
parsed,
resolver,
dedup_ord(
&ccip_requests
Expand All @@ -137,7 +172,7 @@ fn urls_from_request(request: &CCIPRequest) -> Vec<String> {
)
.unwrap_or_default();

let Some(Token::Array(requests)) = decoded.get(0) else {
let Some(Token::Array(requests)) = decoded.first() else {
return Vec::new();
};

Expand Down Expand Up @@ -172,8 +207,8 @@ mod tests {
use ethers_core::abi::ParamType;
use ethers_core::types::Address;

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

#[tokio::test]
async fn test_resolve_universal() {
Expand All @@ -192,12 +227,12 @@ mod tests {
"antony.sh",
&calldata,
&CCIPReadMiddleware::new(Arc::new(provider)),
&Address::from_str("0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62").unwrap(),
&Address::from_str("0x8cab227b1162f03b8338331adaad7aadc83b895e").unwrap(),
)
.await
.unwrap();

let address = ethers_core::abi::decode(&[ParamType::Address], &res.0[0])
let address = ethers_core::abi::decode(&[ParamType::Address], &res.0[0].data)
.unwrap()
.first()
.unwrap()
Expand All @@ -208,7 +243,7 @@ mod tests {
let text_response: Vec<String> = res.0[1..]
.iter()
.map(|t| {
ethers_core::abi::decode(&[ParamType::String], t)
ethers_core::abi::decode(&[ParamType::String], &t.data)
.unwrap()
.first()
.unwrap()
Expand Down
2 changes: 1 addition & 1 deletion shared/src/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub fn gen_app_meta() -> AppMeta {

let tag = info
.tags
.get(0)
.first()
.map(|val| val.to_string())
.unwrap_or_else(|| format!("git:{}", &commit_id));

Expand Down
2 changes: 1 addition & 1 deletion shared/src/models/lookup/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub async fn decode(data: &[u8]) -> Result<String, ENSLookupError> {
let decoded_abi = ethers_core::abi::decode(&[ParamType::Address], data)
.map_err(|_| ENSLookupError::AbiDecodeError)?;

let Some(Token::Address(address)) = decoded_abi.get(0) else {
let Some(Token::Address(address)) = decoded_abi.first() else {
return Err(ENSLookupError::AbiDecodeError);
};

Expand Down
2 changes: 1 addition & 1 deletion shared/src/models/lookup/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub fn calldata(namehash: &H256, record: &str) -> Vec<u8> {
pub async fn decode(data: &[u8], state: &LookupState) -> Result<String, ENSLookupError> {
let decoded_abi = abi_decode_universal_ccip(data, &[ParamType::String])?;

let Some(Token::String(value)) = decoded_abi.get(0) else {
let Some(Token::String(value)) = decoded_abi.first() else {
return Err(ENSLookupError::AbiDecodeError);
};

Expand Down
25 changes: 23 additions & 2 deletions shared/src/models/lookup/multicoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use ethers_core::{
use hex_literal::hex;

use crate::models::multicoin::cointype::coins::CoinType;
use crate::models::multicoin::cointype::evm::ChainId;
use crate::models::multicoin::cointype::slip44::SLIP44;

use super::{abi_decode_universal_ccip, ENSLookupError};

Expand All @@ -22,13 +24,32 @@ pub fn calldata(namehash: &H256, coin_type: &CoinType) -> Vec<u8> {
}

pub async fn decode(data: &[u8], coin_type: &CoinType) -> Result<String, ENSLookupError> {
let decoded_abi = abi_decode_universal_ccip(data, &[ParamType::Bytes])?;
// hell
let decoded_abi = abi_decode_universal_ccip(data, &[ParamType::Bytes]).or_else(|err| {
if coin_type != &CoinType::Slip44(SLIP44::Ethereum)
&& coin_type != &CoinType::Evm(ChainId::Ethereum)
{
return Err(err);
}

// hell^2
abi_decode_universal_ccip(data, &[ParamType::Address]).map(|mut addr| {
vec![Token::Bytes(
addr.remove(0)
.into_address()
.expect("token should be an address")
.as_bytes()
.into(),
)]
})
})?;

let Some(Token::Bytes(bytes)) = decoded_abi.first() else {
return Err(ENSLookupError::AbiDecodeError);
};

if bytes.is_empty() {
// please nobody take inspiration from this function
if bytes.is_empty() || bytes.iter().all(|&b| b == 0) {
return Ok(String::new());
}

Expand Down
Loading

0 comments on commit f86b663

Please sign in to comment.