tronz_primitives/
address.rs1use core::{fmt, str::FromStr};
8
9use alloy_primitives::keccak256;
10use k256::ecdsa::VerifyingKey;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13use crate::error::AddressError;
14
15pub const ADDRESS_PREFIX: u8 = 0x41;
17
18pub const ADDRESS_LEN: usize = 21;
20
21pub const EVM_ADDRESS_LEN: usize = 20;
23
24#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub struct Address([u8; ADDRESS_LEN]);
27
28impl Address {
29 pub const ZERO: Self = {
31 let mut bytes = [0u8; ADDRESS_LEN];
32 bytes[0] = ADDRESS_PREFIX;
33 Self(bytes)
34 };
35
36 pub fn from_bytes(bytes: [u8; ADDRESS_LEN]) -> Result<Self, AddressError> {
38 if bytes[0] != ADDRESS_PREFIX {
39 return Err(AddressError::BadPrefix(bytes[0]));
40 }
41 Ok(Self(bytes))
42 }
43
44 pub fn from_slice(slice: &[u8]) -> Result<Self, AddressError> {
46 let bytes: [u8; ADDRESS_LEN] = slice
47 .try_into()
48 .map_err(|_| AddressError::BadLength { expected: ADDRESS_LEN, got: slice.len() })?;
49 Self::from_bytes(bytes)
50 }
51
52 pub fn from_evm_bytes(evm: [u8; EVM_ADDRESS_LEN]) -> Self {
54 let mut bytes = [0u8; ADDRESS_LEN];
55 bytes[0] = ADDRESS_PREFIX;
56 bytes[1..].copy_from_slice(&evm);
57 Self(bytes)
58 }
59
60 pub fn from_public_key(key: &VerifyingKey) -> Self {
64 let point = key.to_encoded_point(false);
65 let hash = keccak256(&point.as_bytes()[1..]);
68 let mut evm = [0u8; EVM_ADDRESS_LEN];
69 evm.copy_from_slice(&hash[12..]);
70 Self::from_evm_bytes(evm)
71 }
72
73 pub fn from_base58(s: &str) -> Result<Self, AddressError> {
75 let decoded = bs58::decode(s).with_check(None).into_vec()?;
76 Self::from_slice(&decoded)
77 }
78
79 pub fn from_hex(s: &str) -> Result<Self, AddressError> {
82 let s = s.strip_prefix("0x").unwrap_or(s);
83 let bytes = hex::decode(s)?;
84 Self::from_slice(&bytes)
85 }
86
87 pub fn as_bytes(&self) -> &[u8; ADDRESS_LEN] {
89 &self.0
90 }
91
92 pub fn as_evm_bytes(&self) -> &[u8; EVM_ADDRESS_LEN] {
95 self.0[1..].try_into().expect("address body is always 20 bytes")
96 }
97
98 pub fn to_base58(&self) -> String {
100 bs58::encode(&self.0).with_check().into_string()
101 }
102
103 pub fn to_hex(&self) -> String {
105 hex::encode(self.0)
106 }
107}
108
109impl fmt::Display for Address {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.write_str(&self.to_base58())
112 }
113}
114
115impl fmt::Debug for Address {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 write!(f, "Address({})", self.to_base58())
118 }
119}
120
121impl FromStr for Address {
122 type Err = AddressError;
123
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 let hexish = s.strip_prefix("0x").unwrap_or(s);
129 let looks_hex =
130 hexish.len() == ADDRESS_LEN * 2 && hexish.bytes().all(|b| b.is_ascii_hexdigit());
131 if looks_hex { Self::from_hex(s) } else { Self::from_base58(s) }
132 }
133}
134
135impl From<Address> for alloy_primitives::Address {
138 fn from(a: Address) -> Self {
139 alloy_primitives::Address::from(*a.as_evm_bytes())
140 }
141}
142
143impl From<alloy_primitives::Address> for Address {
144 fn from(a: alloy_primitives::Address) -> Self {
146 Address::from_evm_bytes(a.into_array())
147 }
148}
149
150impl Serialize for Address {
153 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
154 serializer.serialize_str(&self.to_base58())
155 }
156}
157
158impl<'de> Deserialize<'de> for Address {
159 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
160 let s = String::deserialize(deserializer)?;
161 s.parse().map_err(serde::de::Error::custom)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 const B58: &str = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
171 const HEX: &str = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c";
172
173 #[test]
174 fn base58_roundtrip() {
175 let a = Address::from_base58(B58).unwrap();
176 assert_eq!(a.to_base58(), B58);
177 assert_eq!(a.to_hex(), HEX);
178 }
179
180 #[test]
181 fn hex_roundtrip() {
182 let a = Address::from_hex(HEX).unwrap();
183 assert_eq!(a.to_base58(), B58);
184 }
185
186 #[test]
187 fn fromstr_detects_format() {
188 assert_eq!(B58.parse::<Address>().unwrap().to_hex(), HEX);
189 assert_eq!(HEX.parse::<Address>().unwrap().to_base58(), B58);
190 let with_0x = format!("0x{HEX}");
191 assert_eq!(with_0x.parse::<Address>().unwrap().to_base58(), B58);
192 }
193
194 #[test]
195 fn bad_prefix_rejected() {
196 let mut bytes = [0u8; ADDRESS_LEN];
197 bytes[0] = 0x42;
198 assert!(matches!(Address::from_bytes(bytes), Err(AddressError::BadPrefix(0x42))));
199 }
200
201 #[test]
202 fn alloy_bridge_roundtrip() {
203 let a = Address::from_base58(B58).unwrap();
204 let evm: alloy_primitives::Address = a.into();
205 assert_eq!(evm.as_slice(), a.as_evm_bytes());
206 let back: Address = evm.into();
207 assert_eq!(back, a);
208 }
209
210 #[test]
211 fn evm_bytes_strip_prefix() {
212 let a = Address::from_hex(HEX).unwrap();
213 assert_eq!(a.as_evm_bytes().len(), 20);
214 assert_eq!(&a.as_bytes()[1..], a.as_evm_bytes());
215 }
216}