1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4};
5
6use bech32::{FromBase32, ToBase32, Variant::Bech32m};
7use fuel_tx::{Address, Bytes32, ContractId};
8
9use crate::errors::{Error, Result};
10
11pub const FUEL_BECH32_HRP: &str = "fuel";
13
14macro_rules! bech32type {
17 ($i:ident) => {
18 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
19 pub struct $i {
20 pub hrp: String,
21 pub hash: Bytes32,
22 }
23
24 impl $i {
25 pub fn new<T: Into<[u8; 32]>>(hrp: &str, hash: T) -> Self {
26 Self {
27 hrp: hrp.to_string(),
28 hash: Bytes32::from(hash.into()),
29 }
30 }
31
32 pub fn hash(&self) -> Bytes32 {
33 self.hash
34 }
35
36 pub fn hrp(&self) -> &str {
37 &self.hrp
38 }
39 }
40
41 impl Default for $i {
42 fn default() -> $i {
43 Self {
44 hrp: FUEL_BECH32_HRP.to_string(),
45 hash: Bytes32::new([0u8; 32]),
46 }
47 }
48 }
49
50 impl FromStr for $i {
51 type Err = Error;
52
53 fn from_str(s: &str) -> Result<Self> {
54 let (hrp, pubkey_hash_base32, _) = bech32::decode(s)?;
55
56 let pubkey_hash: [u8; Address::LEN] = Vec::<u8>::from_base32(&pubkey_hash_base32)?
57 .as_slice()
58 .try_into()?;
59
60 Ok(Self {
61 hrp,
62 hash: Bytes32::new(pubkey_hash),
63 })
64 }
65 }
66
67 impl Display for $i {
68 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69 let data_base32 = self.hash.to_base32();
70 let encoding = bech32::encode(&self.hrp, &data_base32, Bech32m).unwrap();
71
72 write!(f, "{}", encoding)
73 }
74 }
75 };
76}
77
78bech32type!(Bech32Address);
79bech32type!(Bech32ContractId);
80
81impl From<&Bech32Address> for Address {
83 fn from(data: &Bech32Address) -> Address {
84 Address::new(*data.hash)
85 }
86}
87impl From<Bech32Address> for Address {
88 fn from(data: Bech32Address) -> Address {
89 Address::new(*data.hash)
90 }
91}
92impl From<Address> for Bech32Address {
93 fn from(address: Address) -> Self {
94 Self {
95 hrp: FUEL_BECH32_HRP.to_string(),
96 hash: Bytes32::new(*address),
97 }
98 }
99}
100
101impl From<&Bech32ContractId> for ContractId {
103 fn from(data: &Bech32ContractId) -> ContractId {
104 ContractId::new(*data.hash)
105 }
106}
107impl From<Bech32ContractId> for ContractId {
108 fn from(data: Bech32ContractId) -> ContractId {
109 ContractId::new(*data.hash)
110 }
111}
112impl From<ContractId> for Bech32ContractId {
113 fn from(contract_id: ContractId) -> Self {
114 Self {
115 hrp: FUEL_BECH32_HRP.to_string(),
116 hash: Bytes32::new(*contract_id),
117 }
118 }
119}
120
121#[cfg(test)]
122mod test {
123 use super::*;
124
125 #[test]
126 fn test_new() {
127 let pubkey_hash = [
128 107, 50, 223, 89, 84, 225, 186, 222, 175, 254, 253, 44, 15, 197, 229, 148, 220, 255,
129 55, 19, 170, 227, 221, 24, 183, 217, 102, 98, 75, 1, 0, 39,
130 ];
131
132 {
133 let bech32_addr = &Bech32Address::new(FUEL_BECH32_HRP, Bytes32::new(pubkey_hash));
135 let bech32_cid = &Bech32ContractId::new(FUEL_BECH32_HRP, Bytes32::new(pubkey_hash));
136
137 assert_eq!(*bech32_addr.hash(), pubkey_hash);
138 assert_eq!(*bech32_cid.hash(), pubkey_hash);
139 }
140
141 {
142 let bech32_addr = &Bech32Address::new(FUEL_BECH32_HRP, ContractId::new(pubkey_hash));
144 let bech32_cid = &Bech32ContractId::new(FUEL_BECH32_HRP, ContractId::new(pubkey_hash));
145
146 assert_eq!(*bech32_addr.hash(), pubkey_hash);
147 assert_eq!(*bech32_cid.hash(), pubkey_hash);
148 }
149
150 {
151 let bech32_addr = &Bech32Address::new(FUEL_BECH32_HRP, Address::new(pubkey_hash));
153 let bech32_cid = &Bech32ContractId::new(FUEL_BECH32_HRP, Address::new(pubkey_hash));
154
155 assert_eq!(*bech32_addr.hash(), pubkey_hash);
156 assert_eq!(*bech32_cid.hash(), pubkey_hash);
157 }
158 }
159
160 #[test]
161 fn test_from_str() {
162 let pubkey_hashes = [
163 [
164 107, 50, 223, 89, 84, 225, 186, 222, 175, 254, 253, 44, 15, 197, 229, 148, 220,
165 255, 55, 19, 170, 227, 221, 24, 183, 217, 102, 98, 75, 1, 0, 39,
166 ],
167 [
168 49, 83, 18, 64, 150, 242, 119, 146, 83, 184, 84, 96, 160, 212, 110, 69, 81, 34,
169 101, 86, 182, 99, 62, 68, 44, 28, 40, 26, 131, 21, 221, 64,
170 ],
171 [
172 48, 101, 49, 52, 48, 102, 48, 55, 48, 100, 49, 97, 102, 117, 51, 57, 49, 50, 48,
173 54, 48, 98, 48, 100, 48, 56, 49, 53, 48, 52, 49, 52,
174 ],
175 ];
176 let bech32m_encodings = [
177 "fuel1dved7k25uxadatl7l5kql309jnw07dcn4t3a6x9hm9nxyjcpqqns50p7n2",
178 "fuel1x9f3ysyk7fmey5ac23s2p4rwg4gjye2kke3nu3pvrs5p4qc4m4qqwx56k3",
179 "fuel1xpjnzdpsvccrwvryx9skvafn8ycnyvpkxp3rqeps8qcn2vp5xy6qu7yyz7",
180 ];
181
182 for (b32m_e, pbkh) in bech32m_encodings.iter().zip(pubkey_hashes) {
183 let bech32_contract_id = &Bech32ContractId::from_str(b32m_e).unwrap();
184 assert_eq!(*bech32_contract_id.hash(), pbkh);
185 }
186
187 for (b32m_e, pbkh) in bech32m_encodings.iter().zip(pubkey_hashes) {
188 let bech32_contract_id = &Bech32Address::from_str(b32m_e).unwrap();
189 assert_eq!(*bech32_contract_id.hash(), pbkh);
190 }
191 }
192
193 #[test]
194 fn test_from_invalid_bech32_string() {
195 {
196 let expected = [
197 Error::from(bech32::Error::InvalidChecksum),
198 Error::from(bech32::Error::InvalidChar('b')),
199 Error::from(bech32::Error::MissingSeparator),
200 ];
201 let invalid_bech32 = [
202 "fuel1x9f3ysyk7fmey5ac23s2p4rwg4gjye2kke3nu3pvrs5p4qc4m4qqwx32k3",
203 "fuel1xpjnzdpsvccrwvryx9skvafn8ycnyvpkxp3rqeps8qcn2vp5xy6qu7yyb7",
204 "fuelldved7k25uxadatl7l5kql309jnw07dcn4t3a6x9hm9nxyjcpqqns50p7n2",
205 ];
206
207 for (b32m_e, e) in invalid_bech32.iter().zip(expected.iter()) {
208 let result = &Bech32ContractId::from_str(b32m_e).expect_err("should error");
209 assert_eq!(result.to_string(), e.to_string());
210 }
211
212 for (b32m_e, e) in invalid_bech32.iter().zip(expected) {
213 let result = &Bech32Address::from_str(b32m_e).expect_err("should error");
214 assert_eq!(result.to_string(), e.to_string());
215 }
216 }
217 }
218}