subxt_core/utils/
account_id20.rs1use alloc::format;
8use alloc::string::String;
9use codec::{Decode, Encode};
10use keccak_hash::keccak;
11use serde::{Deserialize, Serialize};
12use thiserror::Error as DeriveError;
13
14#[derive(
15 Copy,
16 Clone,
17 Eq,
18 PartialEq,
19 Ord,
20 PartialOrd,
21 Encode,
22 Decode,
23 Debug,
24 scale_encode::EncodeAsType,
25 scale_decode::DecodeAsType,
26 scale_info::TypeInfo,
27)]
28pub struct AccountId20(pub [u8; 20]);
30
31impl AsRef<[u8]> for AccountId20 {
32 fn as_ref(&self) -> &[u8] {
33 &self.0[..]
34 }
35}
36
37impl AsRef<[u8; 20]> for AccountId20 {
38 fn as_ref(&self) -> &[u8; 20] {
39 &self.0
40 }
41}
42
43impl From<[u8; 20]> for AccountId20 {
44 fn from(x: [u8; 20]) -> Self {
45 AccountId20(x)
46 }
47}
48
49impl AccountId20 {
50 pub fn checksum(&self) -> String {
52 let hex_address = hex::encode(self.0);
53 let hash = keccak(hex_address.as_bytes());
54
55 let mut checksum_address = String::with_capacity(42);
56 checksum_address.push_str("0x");
57
58 for (i, ch) in hex_address.chars().enumerate() {
59 let nibble = (hash[i / 2] >> (if i % 2 == 0 { 4 } else { 0 })) & 0xf;
61
62 if nibble >= 8 {
63 checksum_address.push(ch.to_ascii_uppercase());
64 } else {
65 checksum_address.push(ch);
66 }
67 }
68
69 checksum_address
70 }
71}
72
73#[derive(Clone, Copy, Eq, PartialEq, Debug, DeriveError)]
75#[allow(missing_docs)]
76pub enum FromChecksumError {
77 #[error("Length is bad")]
78 BadLength,
79 #[error("Invalid checksum")]
80 InvalidChecksum,
81 #[error("Invalid checksum prefix byte.")]
82 InvalidPrefix,
83}
84
85impl Serialize for AccountId20 {
86 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87 where
88 S: serde::Serializer,
89 {
90 serializer.serialize_str(&self.checksum())
91 }
92}
93
94impl<'de> Deserialize<'de> for AccountId20 {
95 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96 where
97 D: serde::Deserializer<'de>,
98 {
99 String::deserialize(deserializer)?
100 .parse::<AccountId20>()
101 .map_err(|e| serde::de::Error::custom(format!("{e:?}")))
102 }
103}
104
105impl core::fmt::Display for AccountId20 {
106 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
107 write!(f, "{}", self.checksum())
108 }
109}
110
111impl core::str::FromStr for AccountId20 {
112 type Err = FromChecksumError;
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 if s.len() != 42 {
115 return Err(FromChecksumError::BadLength);
116 }
117 if !s.starts_with("0x") {
118 return Err(FromChecksumError::InvalidPrefix);
119 }
120 hex::decode(&s.as_bytes()[2..])
121 .map_err(|_| FromChecksumError::InvalidChecksum)?
122 .try_into()
123 .map(AccountId20)
124 .map_err(|_| FromChecksumError::BadLength)
125 }
126}
127
128#[cfg(test)]
129mod test {
130 use super::*;
131
132 #[test]
133 fn deserialisation() {
134 let key_hashes = vec![
135 "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac",
136 "0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0",
137 "0x798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc",
138 "0x773539d4Ac0e786233D90A233654ccEE26a613D9",
139 "0xFf64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB",
140 "0xC0F0f4ab324C46e55D02D0033343B4Be8A55532d",
141 ];
142
143 for key_hash in key_hashes {
144 let parsed: AccountId20 = key_hash.parse().expect("Failed to parse");
145
146 let encoded = parsed.checksum();
147
148 assert_eq!(encoded, key_hash);
150 }
151 }
152}