ic_ethereum_types/address/
mod.rs

1#[cfg(test)]
2mod tests;
3
4use minicbor::{Decode, Encode};
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::fmt::{Formatter, LowerHex, UpperHex};
8use std::str::FromStr;
9
10/// An Ethereum account address.
11///
12/// # Examples
13///
14/// Parse an address from a string:
15/// ```
16/// use std::str::FromStr;
17/// let address = ic_ethereum_types::Address::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap();
18/// assert_eq!(address.into_bytes(), [0x7a, 0x25, 0x0d, 0x56, 0x30, 0xb4, 0xcf, 0x53, 0x97, 0x39, 0xdf, 0x2c, 0x5d, 0xac, 0xb4, 0xc6, 0x59, 0xf2, 0x48, 0x8d]);
19/// ```
20///
21/// Instantiate an address from raw bytes:
22/// ```
23/// let address = ic_ethereum_types::Address::new([0x7a, 0x25, 0x0d, 0x56, 0x30, 0xb4, 0xcf, 0x53, 0x97, 0x39, 0xdf, 0x2c, 0x5d, 0xac, 0xb4, 0xc6, 0x59, 0xf2, 0x48, 0x8d]);
24/// assert_eq!(address.to_string(), "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D");
25/// ```
26#[derive(
27    Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Decode, Deserialize, Encode, Serialize,
28)]
29#[serde(transparent)]
30#[cbor(transparent)]
31pub struct Address(
32    #[serde(with = "crate::serde_data")]
33    #[cbor(n(0), with = "minicbor::bytes")]
34    [u8; 20],
35);
36
37impl AsRef<[u8]> for Address {
38    fn as_ref(&self) -> &[u8] {
39        &self.0
40    }
41}
42
43impl Address {
44    /// Ethereum zero address.
45    ///
46    /// ```
47    /// let address = ic_ethereum_types::Address::ZERO;
48    /// assert_eq!(address.to_string(), "0x0000000000000000000000000000000000000000");
49    /// assert_eq!(address.into_bytes(), [0u8; 20]);
50    /// ```
51    pub const ZERO: Self = Self([0u8; 20]);
52
53    /// Create a new Ethereum address from raw bytes.
54    pub const fn new(bytes: [u8; 20]) -> Self {
55        Self(bytes)
56    }
57
58    /// Convert an Ethereum address into a 20-byte array.
59    pub const fn into_bytes(self) -> [u8; 20] {
60        self.0
61    }
62}
63
64impl LowerHex for Address {
65    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
66        write!(f, "0x{}", hex::encode(self.0))
67    }
68}
69
70impl UpperHex for Address {
71    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
72        write!(f, "0x{}", hex::encode_upper(self.0))
73    }
74}
75
76/// Parse an address from a 32-byte array with left zero padding.
77impl TryFrom<&[u8; 32]> for Address {
78    type Error = String;
79
80    fn try_from(value: &[u8; 32]) -> Result<Self, Self::Error> {
81        let (leading_zeroes, address_bytes) = value.split_at(12);
82        if !leading_zeroes.iter().all(|leading_zero| *leading_zero == 0) {
83            return Err(format!(
84                "address has leading non-zero bytes: {:?}",
85                leading_zeroes
86            ));
87        }
88        Ok(Address::new(
89            <[u8; 20]>::try_from(address_bytes).expect("vector has correct length"),
90        ))
91    }
92}
93
94/// Convert a 20-byte address to 32-byte array, with left zero padding.
95impl From<&Address> for [u8; 32] {
96    fn from(address: &Address) -> Self {
97        let bytes = address.as_ref();
98        let pad = 32 - bytes.len();
99        let mut padded: [u8; 32] = [0; 32];
100        padded[pad..32].copy_from_slice(bytes);
101        padded
102    }
103}
104
105impl FromStr for Address {
106    type Err = String;
107
108    fn from_str(s: &str) -> Result<Self, Self::Err> {
109        if !s.starts_with("0x") {
110            return Err("address doesn't start with '0x'".to_string());
111        }
112        let mut bytes = [0u8; 20];
113        hex::decode_to_slice(&s[2..], &mut bytes)
114            .map_err(|e| format!("address is not hex: {}", e))?;
115        Ok(Self(bytes))
116    }
117}
118
119impl fmt::Debug for Address {
120    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
121        write!(f, "{}", self)
122    }
123}
124
125/// Display address using [EIP-55](https://eips.ethereum.org/EIPS/eip-55).
126impl fmt::Display for Address {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        let mut addr_chars = [0u8; 20 * 2];
129        hex::encode_to_slice(self.0, &mut addr_chars)
130            .expect("bug: failed to encode an address as hex");
131
132        let checksum = keccak(&addr_chars[..]);
133        let mut cs_nibbles = [0u8; 32 * 2];
134        for i in 0..32 {
135            cs_nibbles[2 * i] = checksum[i] >> 4;
136            cs_nibbles[2 * i + 1] = checksum[i] & 0x0f;
137        }
138        write!(f, "0x")?;
139        for (a, cs) in addr_chars.iter().zip(cs_nibbles.iter()) {
140            let ascii_byte = if *cs >= 0x08 {
141                a.to_ascii_uppercase()
142            } else {
143                *a
144            };
145            write!(f, "{}", char::from(ascii_byte))?;
146        }
147        Ok(())
148    }
149}
150
151fn keccak(bytes: &[u8]) -> [u8; 32] {
152    ic_sha3::Keccak256::hash(bytes)
153}