diff --git a/server/.env.example b/server/.env.example index ba5c7af..f5784e5 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,5 +1,6 @@ REDIS_URL=redis://localhost:6379 RPC_URL=https://rpc.ankr.com/eth +OPENSEA_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx # Optionally you can specify a comma-seperated list PROFILE_RECORDS, however if not provided there are sensible defaults # PROFILE_RECORDS=com.discord,com.twitter diff --git a/server/Cargo.lock b/server/Cargo.lock index 8d3580f..a58425d 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -972,7 +972,7 @@ dependencies = [ "crc16", "crc32fast", "ethers", - "ethers-ccip-read 0.1.1 (git+https://github.com/v3xlabs/rust-ethers-ccip-read?branch=feat/caller-ccip-requests)", + "ethers-ccip-read 0.1.1 (git+https://github.com/ensdomains/ethers-ccip-read)", "ethers-contract", "ethers-core", "getrandom", @@ -1110,34 +1110,34 @@ dependencies = [ [[package]] name = "ethers-ccip-read" version = "0.1.1" -source = "git+https://github.com/v3xlabs/rust-ethers-ccip-read?branch=feat/caller-ccip-requests#2f52e1c144e947c31d4d80c0a1f87ec339bc02a6" +source = "git+https://github.com/ensdomains/ethers-ccip-read?branch=main#5669e42e13bca733d34bbcfb7896a68af5ca79a2" dependencies = [ "async-recursion", "async-trait", "ethers-core", "ethers-providers", "futures-util", - "getrandom", "reqwest", - "serde", "serde_json", "thiserror", - "tracing", ] [[package]] name = "ethers-ccip-read" version = "0.1.1" -source = "git+https://github.com/ensdomains/ethers-ccip-read?branch=main#5669e42e13bca733d34bbcfb7896a68af5ca79a2" +source = "git+https://github.com/ensdomains/ethers-ccip-read#e4a1adbcf045abe792e0280b7cbf52d1ca6844fa" dependencies = [ "async-recursion", "async-trait", "ethers-core", "ethers-providers", "futures-util", + "getrandom", "reqwest", + "serde", "serde_json", "thiserror", + "tracing", ] [[package]] diff --git a/server/src/routes/address.rs b/server/src/routes/address.rs index 9e63174..fb22de4 100644 --- a/server/src/routes/address.rs +++ b/server/src/routes/address.rs @@ -10,15 +10,14 @@ use enstate_shared::models::profile::Profile; use crate::cache; use crate::routes::{http_simple_status_error, profile_http_error_mapper, FreshQuery, RouteError}; -// TODO: figure out UNPROCESSABLE_ENTITY -// Reverse record not owned by this address. #[utoipa::path( get, path = "/a/{address}", responses( - (status = 200, description = "Successfully found address", body = ENSProfile), + (status = 200, description = "Successfully found address.", body = ENSProfile), (status = BAD_REQUEST, description = "Invalid address.", body = ErrorResponse), (status = NOT_FOUND, description = "No name was associated with this address.", body = ErrorResponse), + (status = UNPROCESSABLE_ENTITY, description = "Reverse record not owned by this address.", body = ErrorResponse), ), params( ("address" = String, Path, description = "Address to lookup name data for"), @@ -44,7 +43,7 @@ pub async fn get( let profile = Profile::from_address( address, - query.fresh.unwrap_or(false), + query.fresh, cache, rpc, opensea_api_key, diff --git a/server/src/routes/header.rs b/server/src/routes/header.rs index 9605e21..685d913 100644 --- a/server/src/routes/header.rs +++ b/server/src/routes/header.rs @@ -16,6 +16,7 @@ use crate::routes::{ // TODO: figure out body // (status = 200, description = "Successfully found name or address.", body = ENSProfile), // (status = NOT_FOUND, description = "No name or address could be found."), +// (status = UNPROCESSABLE_ENTITY, description = "Reverse record not owned by this address.", body = ErrorResponse), // ), // params( // ("name_or_address" = String, Path, description = "Name or address to lookup the header for."), @@ -32,10 +33,9 @@ pub async fn get( .ok_or_else(|| http_simple_status_error(StatusCode::INTERNAL_SERVER_ERROR))? .clone(); - let profile = - universal_profile_resolve(&name_or_address, query.fresh.unwrap_or(false), rpc, &state) - .await - .map_err(profile_http_error_mapper)?; + let profile = universal_profile_resolve(&name_or_address, query.fresh, rpc, &state) + .await + .map_err(profile_http_error_mapper)?; let Some(header) = profile.header else { return Err(http_simple_status_error(StatusCode::NOT_FOUND)); diff --git a/server/src/routes/image.rs b/server/src/routes/image.rs index db34ed3..7e218a7 100644 --- a/server/src/routes/image.rs +++ b/server/src/routes/image.rs @@ -16,6 +16,7 @@ use crate::routes::{ // TODO: figure out body // (status = 200, description = "Successfully found name or address.", body = ENSProfile), // (status = NOT_FOUND, description = "No name or address could be found."), +// (status = UNPROCESSABLE_ENTITY, description = "Reverse record not owned by this address.", body = ErrorResponse), // ), // params( // ("name_or_address" = String, Path, description = "Name or address to lookup the image for."), @@ -32,10 +33,9 @@ pub async fn get( .ok_or_else(|| http_simple_status_error(StatusCode::INTERNAL_SERVER_ERROR))? .clone(); - let profile = - universal_profile_resolve(&name_or_address, query.fresh.unwrap_or(false), rpc, &state) - .await - .map_err(profile_http_error_mapper)?; + let profile = universal_profile_resolve(&name_or_address, query.fresh, rpc, &state) + .await + .map_err(profile_http_error_mapper)?; let Some(avatar) = profile.avatar else { return Err(http_simple_status_error(StatusCode::NOT_FOUND)); diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 027c5b4..d287813 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -2,33 +2,45 @@ use axum::http::StatusCode; use axum::Json; use enstate_shared::models::profile::error::ProfileError; use enstate_shared::models::profile::Profile; +use ethers::prelude::ProviderError; use ethers::providers::{Http, Provider}; use ethers_core::types::Address; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use crate::cache; use crate::models::error::ErrorResponse; pub mod address; +pub mod four_oh_four; pub mod header; pub mod image; pub mod name; pub mod root; pub mod universal; -pub mod four_oh_four; - #[derive(Deserialize)] pub struct FreshQuery { - fresh: Option, + #[serde(default, deserialize_with = "bool_or_false")] + fresh: bool, +} + +#[allow(clippy::unnecessary_wraps)] +fn bool_or_false<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let value: Result = Deserialize::deserialize(deserializer); + Ok(value.unwrap_or_default()) } pub type RouteError = (StatusCode, Json); -pub fn profile_http_error_mapper(err: ProfileError) -> RouteError { +pub fn profile_http_error_mapper>(err: T) -> RouteError { + let err = err.as_ref(); let status = match err { ProfileError::NotFound => StatusCode::NOT_FOUND, ProfileError::CCIPError(_) => StatusCode::BAD_GATEWAY, + ProfileError::RPCError(ProviderError::EnsNotOwned(_)) => StatusCode::UNPROCESSABLE_ENTITY, _ => StatusCode::INTERNAL_SERVER_ERROR, }; diff --git a/server/src/routes/name.rs b/server/src/routes/name.rs index 9ec5d80..b575bba 100644 --- a/server/src/routes/name.rs +++ b/server/src/routes/name.rs @@ -39,7 +39,7 @@ pub async fn get( let profile = Profile::from_name( &name, - query.fresh.unwrap_or(false), + query.fresh, cache, rpc, opensea_api_key, diff --git a/server/src/routes/universal.rs b/server/src/routes/universal.rs index fa1af4c..f3075a1 100644 --- a/server/src/routes/universal.rs +++ b/server/src/routes/universal.rs @@ -18,6 +18,7 @@ use crate::routes::{ responses( (status = 200, description = "Successfully found name or address.", body = ENSProfile), (status = NOT_FOUND, description = "No name or address could be found.", body = ErrorResponse), + (status = UNPROCESSABLE_ENTITY, description = "Reverse record not owned by this address.", body = ErrorResponse), ), params( ("name_or_address" = String, Path, description = "Name or address to lookup the name data for."), @@ -34,10 +35,9 @@ pub async fn get( .ok_or_else(|| http_simple_status_error(StatusCode::INTERNAL_SERVER_ERROR))? .clone(); - let profile = - universal_profile_resolve(&name_or_address, query.fresh.unwrap_or(false), rpc, &state) - .await - .map_err(profile_http_error_mapper)?; + let profile = universal_profile_resolve(&name_or_address, query.fresh, rpc, &state) + .await + .map_err(profile_http_error_mapper)?; Ok(Json(profile)) } diff --git a/shared/Cargo.lock b/shared/Cargo.lock index 14f8eb0..62c5969 100644 --- a/shared/Cargo.lock +++ b/shared/Cargo.lock @@ -959,7 +959,7 @@ dependencies = [ [[package]] name = "ethers-ccip-read" version = "0.1.1" -source = "git+https://github.com/v3xlabs/rust-ethers-ccip-read?branch=feat/caller-ccip-requests#2f52e1c144e947c31d4d80c0a1f87ec339bc02a6" +source = "git+https://github.com/ensdomains/ethers-ccip-read#e4a1adbcf045abe792e0280b7cbf52d1ca6844fa" dependencies = [ "async-recursion", "async-trait", @@ -1297,9 +1297,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -1328,9 +1328,9 @@ checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", diff --git a/shared/src/models/lookup/addr.rs b/shared/src/models/lookup/addr.rs index 78882c1..3bad838 100644 --- a/shared/src/models/lookup/addr.rs +++ b/shared/src/models/lookup/addr.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use async_trait::async_trait; use ethers_core::{ abi::{ParamType, Token}, @@ -23,7 +21,7 @@ impl ENSLookup for Addr { [fn_selector, data].concat() } - async fn decode(&self, data: &[u8], _: Arc) -> Result { + async fn decode(&self, data: &[u8], _: &LookupState) -> Result { let decoded_abi = ethers_core::abi::decode(&[ParamType::Address], data) .map_err(|_| ENSLookupError::AbiDecodeError)?; diff --git a/shared/src/models/lookup/image.rs b/shared/src/models/lookup/image.rs index cbb532b..4cca1b0 100644 --- a/shared/src/models/lookup/image.rs +++ b/shared/src/models/lookup/image.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use async_trait::async_trait; use ethers_core::types::U256; use ethers_core::{ @@ -56,36 +54,43 @@ impl ENSLookup for Image { [fn_selector, data].concat() } - async fn decode(&self, data: &[u8], state: Arc) -> Result { + async fn decode(&self, data: &[u8], state: &LookupState) -> Result { let decoded_abi = abi_decode_universal_ccip(data, &[ParamType::String])?; - let value = decoded_abi.get(0).ok_or(ENSLookupError::AbiDecodeError)?; - let value = value.to_string(); + + let Some(Token::String(value)) = decoded_abi.get(0) else { + return Err(ENSLookupError::AbiDecodeError); + }; let opensea_api_key = state.opensea_api_key.clone(); - if let Some(captures) = IPFS_REGEX.captures(&value) { + if let Some(captures) = IPFS_REGEX.captures(value) { let hash = captures.get(1).unwrap().as_str(); return Ok(format!("{}{hash}", self.ipfs_gateway)); } - let Some(captures) = EIP155_REGEX.captures(&value) else { - return Ok(value); + let Some(captures) = EIP155_REGEX.captures(value) else { + return Ok(value.to_string()); }; - let chain_id = captures.get(1).unwrap().as_str(); - let contract_type = captures.get(2).unwrap().as_str(); - let contract_address = captures.get(3).unwrap().as_str(); - let token_id = captures.get(4).unwrap().as_str(); + let (Some(chain_id), Some(contract_type), Some(contract_address), Some(token_id)) = ( + captures.get(1), + captures.get(2), + captures.get(3), + captures.get(4), + ) else { + return Err(ENSLookupError::AbiDecodeError); + }; let chain_id = chain_id + .as_str() .parse::() .map_err(|err| ImageLookupError::FormatError(err.to_string()))?; - let token_id = U256::from_dec_str(token_id) + let token_id = U256::from_dec_str(token_id.as_str()) .map_err(|err| ImageLookupError::FormatError(err.to_string()))?; - let contract_type = match contract_type { + let contract_type = match contract_type.as_str() { "erc721" => EIP155ContractType::ERC721, "erc1155" => EIP155ContractType::ERC1155, _ => { @@ -95,6 +100,8 @@ impl ENSLookup for Image { } }; + let contract_address = contract_address.as_str(); + info!( "Encountered Avatar: {chain_id} {contract_type} {contract_address} {token_id}", chain_id = chain_id, diff --git a/shared/src/models/lookup/mod.rs b/shared/src/models/lookup/mod.rs index 277f703..766845e 100644 --- a/shared/src/models/lookup/mod.rs +++ b/shared/src/models/lookup/mod.rs @@ -44,7 +44,7 @@ pub enum ENSLookupError { #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait ENSLookup { fn calldata(&self, namehash: &H256) -> Vec; - async fn decode(&self, data: &[u8], state: Arc) -> Result; + async fn decode(&self, data: &[u8], state: &LookupState) -> Result; fn name(&self) -> String; fn to_boxed(self) -> Box diff --git a/shared/src/models/lookup/multicoin.rs b/shared/src/models/lookup/multicoin.rs index b5fec00..2a907c4 100644 --- a/shared/src/models/lookup/multicoin.rs +++ b/shared/src/models/lookup/multicoin.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use async_trait::async_trait; use ethers_core::{ abi::{ParamType, Token}, @@ -29,22 +27,18 @@ impl ENSLookup for Multicoin { [fn_selector, data].concat() } - async fn decode(&self, data: &[u8], _: Arc) -> Result { + async fn decode(&self, data: &[u8], _: &LookupState) -> Result { let decoded_abi = abi_decode_universal_ccip(data, &[ParamType::Bytes])?; - let value = decoded_abi - .get(0) - .ok_or(ENSLookupError::AbiDecodeError)? - .clone() - .into_bytes() - .expect("token should be bytes"); + let Some(Token::Bytes(bytes)) = decoded_abi.get(0) else { + return Err(ENSLookupError::AbiDecodeError); + }; - if value.is_empty() { - // Empty field + if bytes.is_empty() { return Ok(String::new()); } - Ok(self.coin_type.decode(&value)?) + Ok(self.coin_type.decode(bytes.as_ref())?) } fn name(&self) -> String { diff --git a/shared/src/models/lookup/text.rs b/shared/src/models/lookup/text.rs index 6559fb1..e3b917c 100644 --- a/shared/src/models/lookup/text.rs +++ b/shared/src/models/lookup/text.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use async_trait::async_trait; use ethers_core::{ abi::{ParamType, Token}, @@ -41,12 +39,14 @@ impl ENSLookup for Text { [fn_selector, data].concat() } - async fn decode(&self, data: &[u8], _: Arc) -> Result { + async fn decode(&self, data: &[u8], _: &LookupState) -> Result { let decoded_abi = abi_decode_universal_ccip(data, &[ParamType::String])?; - let value = decoded_abi.get(0).ok_or(ENSLookupError::AbiDecodeError)?; - let value = value.to_string(); - Ok(value) + let Some(Token::String(value)) = decoded_abi.get(0) else { + return Err(ENSLookupError::AbiDecodeError); + }; + + Ok(value.to_string()) } fn name(&self) -> String { diff --git a/shared/src/models/multicoin/decoding/bitcoin.rs b/shared/src/models/multicoin/decoding/bitcoin.rs index 7ba7888..d761bf9 100644 --- a/shared/src/models/multicoin/decoding/bitcoin.rs +++ b/shared/src/models/multicoin/decoding/bitcoin.rs @@ -1,5 +1,3 @@ -use std::string::ToString; - use lazy_static::lazy_static; use crate::models::multicoin::decoding::segwit::SegWitDecoder; @@ -8,7 +6,7 @@ use super::{p2pkh::P2PKHDecoder, p2sh::P2SHDecoder, MulticoinDecoder, MulticoinD lazy_static! { static ref BTC_SEGWIT_DECODER: SegWitDecoder = SegWitDecoder { - human_readable_part: "bc".to_string() + human_readable_part: "bc" }; } diff --git a/shared/src/models/multicoin/decoding/dogecoin.rs b/shared/src/models/multicoin/decoding/dogecoin.rs index d177395..edbae42 100644 --- a/shared/src/models/multicoin/decoding/dogecoin.rs +++ b/shared/src/models/multicoin/decoding/dogecoin.rs @@ -1,4 +1,4 @@ -use super::{MulticoinDecoder, MulticoinDecoderError, p2pkh::P2PKHDecoder, p2sh::P2SHDecoder}; +use super::{p2pkh::P2PKHDecoder, p2sh::P2SHDecoder, MulticoinDecoder, MulticoinDecoderError}; pub struct DogecoinDecoder {} @@ -7,7 +7,7 @@ impl MulticoinDecoder for DogecoinDecoder { match data.len() { 25 => P2PKHDecoder { version: 0x1e }.decode(data), 23 => P2SHDecoder { version: 0x16 }.decode(data), - _ => Err(MulticoinDecoderError::InvalidStructure(String::new())) + _ => Err(MulticoinDecoderError::InvalidStructure(String::new())), } } } diff --git a/shared/src/models/multicoin/decoding/litecoin.rs b/shared/src/models/multicoin/decoding/litecoin.rs index c1a63ab..1238fd3 100644 --- a/shared/src/models/multicoin/decoding/litecoin.rs +++ b/shared/src/models/multicoin/decoding/litecoin.rs @@ -2,10 +2,12 @@ use lazy_static::lazy_static; use crate::models::multicoin::decoding::segwit::SegWitDecoder; -use super::{MulticoinDecoder, MulticoinDecoderError, p2pkh::P2PKHDecoder, p2sh::P2SHDecoder}; +use super::{p2pkh::P2PKHDecoder, p2sh::P2SHDecoder, MulticoinDecoder, MulticoinDecoderError}; lazy_static! { - static ref LTC_SEGWIT_DECODER: SegWitDecoder = SegWitDecoder { human_readable_part: "ltc".to_string() }; + static ref LTC_SEGWIT_DECODER: SegWitDecoder = SegWitDecoder { + human_readable_part: "ltc" + }; } pub struct LitecoinDecoder {} diff --git a/shared/src/models/multicoin/decoding/monacoin.rs b/shared/src/models/multicoin/decoding/monacoin.rs index 06b2852..b608cb6 100644 --- a/shared/src/models/multicoin/decoding/monacoin.rs +++ b/shared/src/models/multicoin/decoding/monacoin.rs @@ -1,4 +1,4 @@ -use super::{MulticoinDecoder, MulticoinDecoderError, p2pkh::P2PKHDecoder, p2sh::P2SHDecoder}; +use super::{p2pkh::P2PKHDecoder, p2sh::P2SHDecoder, MulticoinDecoder, MulticoinDecoderError}; pub struct MonacoinDecoder {} @@ -7,7 +7,7 @@ impl MulticoinDecoder for MonacoinDecoder { match data.len() { 25 => P2PKHDecoder { version: 0x32 }.decode(data), 23 => P2SHDecoder { version: 0x05 }.decode(data), - _ => Err(MulticoinDecoderError::InvalidStructure(String::new())) + _ => Err(MulticoinDecoderError::InvalidStructure(String::new())), } } } diff --git a/shared/src/models/multicoin/decoding/segwit.rs b/shared/src/models/multicoin/decoding/segwit.rs index 7967dcd..4844428 100644 --- a/shared/src/models/multicoin/decoding/segwit.rs +++ b/shared/src/models/multicoin/decoding/segwit.rs @@ -1,29 +1,33 @@ -use bech32::Fe32; use bech32::primitives::hrp::Hrp; +use bech32::Fe32; use crate::models::multicoin::decoding::{MulticoinDecoder, MulticoinDecoderError}; pub struct SegWitDecoder { - pub human_readable_part: String, + pub human_readable_part: &'static str, } impl MulticoinDecoder for SegWitDecoder { fn decode(&self, data: &[u8]) -> Result { if data.len() < 2 { - return Err(MulticoinDecoderError::InvalidStructure("len < 2".to_string())); + return Err(MulticoinDecoderError::InvalidStructure( + "len < 2".to_string(), + )); } let version = match data[0] { 0x00 => Ok(0x00), 0x51..=0x60 => Ok(data[0] - 0x50), - _ => Err(MulticoinDecoderError::InvalidStructure("invalid segwit version".to_string())) + _ => Err(MulticoinDecoderError::InvalidStructure( + "invalid segwit version".to_string(), + )), }?; bech32::segwit::encode( - &Hrp::parse_unchecked(self.human_readable_part.as_str()), + &Hrp::parse_unchecked(self.human_readable_part), Fe32::try_from(version).unwrap(), &data[2..], ) - .map_err(|_| MulticoinDecoderError::InvalidStructure("failed to bech32 encode".to_string())) + .map_err(|_| MulticoinDecoderError::InvalidStructure("failed to bech32 encode".to_string())) } } diff --git a/shared/src/models/multicoin/decoding/stellar.rs b/shared/src/models/multicoin/decoding/stellar.rs index 9597c83..03f23e6 100644 --- a/shared/src/models/multicoin/decoding/stellar.rs +++ b/shared/src/models/multicoin/decoding/stellar.rs @@ -16,6 +16,9 @@ impl MulticoinDecoder for StellarDecoder { full.push((checksum & 0xff) as u8); full.push(((checksum >> 8) & 0xff) as u8); - Ok(base32::encode(Alphabet::RFC4648 { padding: false }, full.as_slice())) + Ok(base32::encode( + Alphabet::RFC4648 { padding: false }, + full.as_slice(), + )) } } diff --git a/shared/src/models/multicoin/decoding/tezos.rs b/shared/src/models/multicoin/decoding/tezos.rs index 6b7666b..03bc088 100644 --- a/shared/src/models/multicoin/decoding/tezos.rs +++ b/shared/src/models/multicoin/decoding/tezos.rs @@ -11,7 +11,9 @@ const CONTRACT_PREFIX: &[u8; 3] = &[0x02, 0x5a, 0x79]; impl MulticoinDecoder for TezosDecoder { fn decode(&self, data: &[u8]) -> Result { if data.len() != 21 && data.len() != 22 { - return Err(MulticoinDecoderError::InvalidStructure("invalid address length".to_string())); + return Err(MulticoinDecoderError::InvalidStructure( + "invalid address length".to_string(), + )); } let prefix: &[u8; 3] = match data[0] { @@ -20,26 +22,29 @@ impl MulticoinDecoder for TezosDecoder { 0x01 => Ok(&[0x06, 0xa1, 0xa1]), 0x02 => Ok(&[0x06, 0xa1, 0xa4]), 0x03 => Ok(&[0x06, 0xa1, 0xa6]), - _ => Err(MulticoinDecoderError::InvalidStructure("invalid address format".to_string())) + _ => Err(MulticoinDecoderError::InvalidStructure( + "invalid address format".to_string(), + )), }, 0x01 => Ok(CONTRACT_PREFIX), - _ => Err(MulticoinDecoderError::InvalidStructure("invalid address type".to_string())) + _ => Err(MulticoinDecoderError::InvalidStructure( + "invalid address type".to_string(), + )), }?; let decoded = [ prefix as &[u8], match prefix { CONTRACT_PREFIX => &data[1..data.len() - 1], - _ => &data[2..] - } - ].concat(); + _ => &data[2..], + }, + ] + .concat(); let checksum = utils::sha256::hash(utils::sha256::hash(decoded.clone())); - Ok( - bs58::encode([&decoded, &checksum[..4]].concat()) - .with_alphabet(Alphabet::BITCOIN) - .into_string() - ) + Ok(bs58::encode([&decoded, &checksum[..4]].concat()) + .with_alphabet(Alphabet::BITCOIN) + .into_string()) } } diff --git a/shared/src/models/profile/error.rs b/shared/src/models/profile/error.rs index 81417cd..be1196d 100644 --- a/shared/src/models/profile/error.rs +++ b/shared/src/models/profile/error.rs @@ -24,3 +24,9 @@ pub enum ProfileError { #[error("Other: {0}")] Other(String), } + +impl AsRef for ProfileError { + fn as_ref(&self) -> &ProfileError { + self + } +} diff --git a/shared/src/models/profile/from_name.rs b/shared/src/models/profile/from_name.rs index c41d6b2..e975ea9 100644 --- a/shared/src/models/profile/from_name.rs +++ b/shared/src/models/profile/from_name.rs @@ -94,28 +94,34 @@ impl Profile { let rpc = Arc::new(rpc); - // ens CCIP unwrapper is limited to 50 sub-requests, i.e. per request - let calldata_chunks = calldata.chunks(50).collect::>(); + // ENS CCIP unwrapper is limited to 50 sub-requests, i.e. per request + let mut resolves = Vec::new(); - let (mut data, resolver, ccip_urls) = - resolve_universal(name, calldata_chunks[0], &rpc).await?; - - for &chunk in &calldata_chunks[1..] { - data = [data, resolve_universal(name, chunk, &rpc).await?.0].concat(); + for chunk in calldata.chunks(50) { + resolves.push(resolve_universal(name, chunk, &rpc).await?); } + let Some((_, resolver, ccip_urls)) = resolves.get(0) else { + return Err(ProfileError::ImplementationError(String::new())); + }; + + let data = resolves + .iter() + .flat_map(|(data, _, _)| data) + .collect::>(); + let mut results: Vec> = Vec::new(); let mut errors = BTreeMap::default(); - let lookup_state = Arc::new(LookupState { + let lookup_state = LookupState { rpc, opensea_api_key: opensea_api_key.to_string(), - }); + }; // 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.clone()).await; + let result = calldata.decode(data[index], &lookup_state).await; match result { Ok(result) => { @@ -183,8 +189,8 @@ impl Profile { records, chains, fresh: chrono::offset::Utc::now().timestamp_millis(), - resolver: EIP55Address(resolver), - ccip_urls, + resolver: EIP55Address(*resolver), + ccip_urls: ccip_urls.clone(), errors, }; diff --git a/worker/Cargo.lock b/worker/Cargo.lock index f5a3435..4a869bd 100644 --- a/worker/Cargo.lock +++ b/worker/Cargo.lock @@ -1006,6 +1006,7 @@ dependencies = [ [[package]] name = "ethers-ccip-read" version = "0.1.1" +source = "git+https://github.com/ensdomains/ethers-ccip-read#e4a1adbcf045abe792e0280b7cbf52d1ca6844fa" dependencies = [ "async-recursion", "async-trait", @@ -1343,9 +1344,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -1374,9 +1375,9 @@ checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -2031,9 +2032,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", @@ -3447,9 +3448,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", diff --git a/worker/src/http_util.rs b/worker/src/http_util.rs index 7541ef8..b0af582 100644 --- a/worker/src/http_util.rs +++ b/worker/src/http_util.rs @@ -1,4 +1,5 @@ use enstate_shared::models::profile::error::ProfileError; +use ethers::prelude::ProviderError; use http::status::StatusCode; use serde::Serialize; use worker::Response; @@ -13,6 +14,7 @@ pub fn profile_http_error_mapper(err: ProfileError) -> Response { let status = match err { ProfileError::NotFound => StatusCode::NOT_FOUND, ProfileError::CCIPError(_) => StatusCode::BAD_GATEWAY, + ProfileError::RPCError(ProviderError::EnsNotOwned(_)) => StatusCode::UNPROCESSABLE_ENTITY, _ => StatusCode::INTERNAL_SERVER_ERROR, };