ethers-ccip-read 0.1.2

CCIP-Read middleware for ethers-rs
Documentation
use ethers_core::abi;
use ethers_core::abi::{Detokenize, ParamType};
use std::time::Duration;

pub(crate) fn truncate_str(src: &str, side: usize) -> String {
    if src.len() < side * 2 + 3 {
        return src.to_string();
    }

    format!("{}..{}", &src[..side], &src[src.len() - side..])
}

pub(crate) fn decode_bytes<T: Detokenize>(param: ParamType, bytes: &[u8]) -> Result<T, abi::Error> {
    let tokens = abi::decode(&[param], bytes)?;
    T::from_tokens(tokens).map_err(|err| abi::Error::Other(err.to_string().into()))
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn build_reqwest(timeout: Duration) -> reqwest::Client {
    reqwest::Client::builder()
        .timeout(timeout)
        .build()
        .expect("should be a valid reqwest client")
}

#[cfg(target_arch = "wasm32")]
pub(crate) fn build_reqwest(_timeout: Duration) -> reqwest::Client {
    // reqwest doesn't support timeouts on wasm
    reqwest::Client::new()
}

/// Encodes a domain name into its binary representation according to the DNS
/// wire format. Each label (i.e., substring separated by dots) in the domain
/// is prefixed with its length, and the encoded domain name is terminated
/// with a root label (length 0).
///
/// # Arguments
///
/// * `domain` - A domain name as a string (e.g., "tanrikulu.eth").
///
/// # Returns
///
/// * A `Result` containing the encoded domain name as a `Vec<u8>` on success, or an error message
///   as a `String` if any of the labels in the domain name are too long (exceeding 63 characters).
///
/// # Example
///
/// ```
/// use ethers_ccip_read::utils::{dns_encode};
///
/// let encoded = dns_encode("tanrikulu.eth").unwrap();
/// assert_eq!(encoded, vec![9, b't', b'a', b'n', b'r', b'i', b'k', b'u', b'l', b'u', 3, b'e', b't', b'h', 0]);
/// ```
pub fn dns_encode(domain: &str) -> Result<Vec<u8>, String> {
    let mut encoded = Vec::new();
    let labels = domain.split('.');

    for label in labels {
        let label_len = label.len();
        if label_len > 63 {
            return Err(format!("Label is too long: {}", label));
        }

        encoded.push(label_len as u8);
        encoded.extend(label.as_bytes());
    }

    // Append the root label (length 0)
    encoded.push(0);

    Ok(encoded)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_dns_encode() {
        let ens_name = "vitalik.eth";
        let expected_result = vec![
            7, b'v', b'i', b't', b'a', b'l', b'i', b'k', 3, b'e', b't', b'h', 0,
        ];

        let encoded_name = dns_encode(ens_name).unwrap();

        assert_eq!(
            encoded_name, expected_result,
            "Expected dns encoded result to be {:?}, but got {:?}",
            expected_result, encoded_name
        );
    }

    #[test]
    fn test_dns_encode_with_a_long_name() {
        let ens_name = "superlongbutmeaningfulnameforanextraordinaryrustproject1234567890x.eth";
        let mut labels = ens_name.split('.');

        let encoded_name = dns_encode(ens_name);

        assert_eq!(
            encoded_name.unwrap_err(),
            format!("Label is too long: {}", labels.next().unwrap()),
        );
    }
}