eth_address/
address.rs

1#![allow(dead_code)]
2//! The functions are rust implementations of functions from ethers.js library,
3//! one of the most popular web3 libraries out there.
4//! https://github.com/ethers-io/ethers.js/blob/master/packages/address/src.ts/index.ts
5
6use anyhow::{anyhow, Result};
7use crypto::{digest::Digest, sha3::Sha3};
8use regex::Regex;
9
10pub fn is_hex_string(value: String, length: usize) -> Result<bool> {
11    let re = Regex::new("^0x[0-9A-Fa-f]*$")?;
12    let is_hex = if !re.is_match(&value) {
13        false
14    } else if value.len() != 2 + 2 * length {
15        false
16    } else {
17        true
18    };
19    Ok(is_hex)
20}
21
22// Inspired from https://github.com/miguelmota/rust-eth-checksum
23pub fn get_checksum_address(a: String) -> Result<String> {
24    if !is_hex_string(a.clone(), 20)? {
25        return Err(anyhow!("Invalid address. Not a hex address"));
26    }
27    let addr = a.trim_start_matches("0x").to_lowercase();
28    let address_hash = {
29        let mut hasher = Sha3::keccak256();
30        hasher.input(addr.as_bytes());
31        hasher.result_str()
32    };
33
34    Ok(addr
35        .char_indices()
36        .fold(String::from("0x"), |mut acc, (index, address_char)| {
37            // this cannot fail since it's Keccak256 hashed
38            let n = u16::from_str_radix(&address_hash[index..index + 1], 16).unwrap();
39
40            if n > 7 {
41                // make char uppercase if ith character is 9..f
42                acc.push_str(&address_char.to_uppercase().to_string())
43            } else {
44                // already lowercased
45                acc.push(address_char)
46            }
47
48            acc
49        }))
50}
51
52pub fn get_address(addr: String) -> Result<String> {
53    let re = Regex::new("^(0x)?[0-9a-fA-F]{40}$")?;
54    let mut prefixed_addr = String::from("0x");
55    let result: String;
56
57    if re.is_match(&addr) {
58        // Add the missing the 0x prefix
59        if &addr[..2] != "0x" {
60            prefixed_addr.push_str(&addr);
61        } else {
62            prefixed_addr = addr;
63        }
64
65        result = get_checksum_address(prefixed_addr.clone())?;
66
67        // Checksum regex
68        let checksum_re = Regex::new("([A-F].*[a-f])|([a-f].*[A-F])")?;
69
70        // Check if it is a checksummed address with a bad checksum
71        if checksum_re.is_match(&prefixed_addr) && result != prefixed_addr {
72            return Err(anyhow!(
73                "Bad address checksum for address {}",
74                prefixed_addr
75            ));
76        }
77    } else {
78        return Err(anyhow!("Invalid address"));
79    }
80
81    Ok(result)
82}
83
84pub fn is_address(addr: String) -> bool {
85    match get_address(addr) {
86        Ok(_) => true,
87        Err(_) => false,
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use anyhow::Result;
95
96    #[test]
97    fn test_is_address() {
98        let good = String::from("0xC0404ed740a69d11201f5eD297c5732F562c6E4e");
99        assert_eq!(is_address(good), true);
100
101        //
102        let bad = String::from("0xC0404ed740a69d11201fffr5y7c5732F562c6E4e");
103        assert_eq!(is_address(bad), false);
104    }
105
106    #[test]
107    fn test_get_address() -> Result<()> {
108        // without `0x` prefix but rest is correct
109        let good = String::from("C0404ed740a69d11201f5eD297c5732F562c6E4e");
110        let expected = String::from("0xC0404ed740a69d11201f5eD297c5732F562c6E4e");
111        assert_eq!(get_address(good)?, expected);
112
113        // bad checksummed address
114        let mut bad = String::from("0xa54D3c09E34aC96807c1CC397404bF2B98DC4eFb");
115        let mut expected_err = String::from(format!("Bad address checksum for address {}", bad));
116        assert_eq!(get_address(bad).unwrap_err().to_string(), expected_err);
117
118        // totally bad address
119        bad = String::from("c09E34aC96807c1CCZUUWS882SSS");
120        expected_err = String::from("Invalid address");
121        assert_eq!(get_address(bad).unwrap_err().to_string(), expected_err);
122
123        Ok(())
124    }
125
126    #[test]
127    fn test_get_checksum_address() -> Result<()> {
128        let bad_addr = String::from("0xzZzZ4ed740a69d11201f5eD297c5732F562c6E4e");
129        let expected_err = String::from("Invalid address. Not a hex address");
130        assert_eq!(
131            get_checksum_address(bad_addr).unwrap_err().to_string(),
132            expected_err
133        );
134
135        let good_addr = String::from("0xC0404ed740a69d11201f5eD297c5732F562c6E4e");
136        assert_eq!(get_checksum_address(good_addr.clone())?, good_addr);
137
138        Ok(())
139    }
140
141    #[test]
142    fn test_is_hex_string() -> Result<()> {
143        let good = String::from("0xC0404ed740a69d11201f5eD297c5732F562c6E4e");
144        assert_eq!(is_hex_string(good, 20)?, true);
145
146        let mut bad = String::from("0xC0404ed740a69d11201fffr5y7c5732F562c6E4e");
147        assert_eq!(is_hex_string(bad, 20)?, false);
148
149        // length too much
150        bad = String::from(
151            "0xC0404ed740a69d11201f5eD297c5732F562c6E4eC0404ed740a69d11201f5eD297c5732F562c6E4e",
152        );
153        assert_eq!(is_hex_string(bad, 20)?, false);
154
155        Ok(())
156    }
157}