ethers_ccip_read/
utils.rs

1use ethers_core::abi;
2use ethers_core::abi::{Detokenize, ParamType};
3use std::time::Duration;
4
5pub(crate) fn truncate_str(src: &str, side: usize) -> String {
6    if src.len() < side * 2 + 3 {
7        return src.to_string();
8    }
9
10    format!("{}..{}", &src[..side], &src[src.len() - side..])
11}
12
13pub(crate) fn decode_bytes<T: Detokenize>(param: ParamType, bytes: &[u8]) -> Result<T, abi::Error> {
14    let tokens = abi::decode(&[param], bytes)?;
15    T::from_tokens(tokens).map_err(|err| abi::Error::Other(err.to_string().into()))
16}
17
18#[cfg(not(target_arch = "wasm32"))]
19pub(crate) fn build_reqwest(timeout: Duration) -> reqwest::Client {
20    reqwest::Client::builder()
21        .timeout(timeout)
22        .build()
23        .expect("should be a valid reqwest client")
24}
25
26#[cfg(target_arch = "wasm32")]
27pub(crate) fn build_reqwest(_timeout: Duration) -> reqwest::Client {
28    // reqwest doesn't support timeouts on wasm
29    reqwest::Client::new()
30}
31
32/// Encodes a domain name into its binary representation according to the DNS
33/// wire format. Each label (i.e., substring separated by dots) in the domain
34/// is prefixed with its length, and the encoded domain name is terminated
35/// with a root label (length 0).
36///
37/// # Arguments
38///
39/// * `domain` - A domain name as a string (e.g., "tanrikulu.eth").
40///
41/// # Returns
42///
43/// * A `Result` containing the encoded domain name as a `Vec<u8>` on success, or an error message
44///   as a `String` if any of the labels in the domain name are too long (exceeding 63 characters).
45///
46/// # Example
47///
48/// ```
49/// use ethers_ccip_read::utils::{dns_encode};
50///
51/// let encoded = dns_encode("tanrikulu.eth").unwrap();
52/// 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]);
53/// ```
54pub fn dns_encode(domain: &str) -> Result<Vec<u8>, String> {
55    let mut encoded = Vec::new();
56    let labels = domain.split('.');
57
58    for label in labels {
59        let label_len = label.len();
60        if label_len > 63 {
61            return Err(format!("Label is too long: {}", label));
62        }
63
64        encoded.push(label_len as u8);
65        encoded.extend(label.as_bytes());
66    }
67
68    // Append the root label (length 0)
69    encoded.push(0);
70
71    Ok(encoded)
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_dns_encode() {
80        let ens_name = "vitalik.eth";
81        let expected_result = vec![
82            7, b'v', b'i', b't', b'a', b'l', b'i', b'k', 3, b'e', b't', b'h', 0,
83        ];
84
85        let encoded_name = dns_encode(ens_name).unwrap();
86
87        assert_eq!(
88            encoded_name, expected_result,
89            "Expected dns encoded result to be {:?}, but got {:?}",
90            expected_result, encoded_name
91        );
92    }
93
94    #[test]
95    fn test_dns_encode_with_a_long_name() {
96        let ens_name = "superlongbutmeaningfulnameforanextraordinaryrustproject1234567890x.eth";
97        let mut labels = ens_name.split('.');
98
99        let encoded_name = dns_encode(ens_name);
100
101        assert_eq!(
102            encoded_name.unwrap_err(),
103            format!("Label is too long: {}", labels.next().unwrap()),
104        );
105    }
106}