ab_core_primitives/
address.rs

1//! Address-related primitives
2
3use crate::shard::ShardIndex;
4use ab_io_type::metadata::IoTypeMetadataKind;
5use ab_io_type::trivial_type::TrivialType;
6use bech32::primitives::decode::CheckedHrpstring;
7use bech32::{Bech32m, ByteIterExt, Fe32IterExt, Hrp};
8use core::cmp::Ordering;
9use core::mem::MaybeUninit;
10use core::ops::Deref;
11use core::{fmt, ptr};
12use derive_more::Deref;
13
14/// Formatted address
15#[derive(Copy, Clone)]
16pub struct FormattedAddress {
17    buffer:
18        [u8; ShortHrp::MAX_HRP_LENGTH + FormattedAddress::MAX_ENCODING_WITHOUT_HRP_WITH_SEPARATOR],
19    length: usize,
20}
21
22impl fmt::Debug for FormattedAddress {
23    #[inline(always)]
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        self.as_str().fmt(f)
26    }
27}
28
29impl fmt::Display for FormattedAddress {
30    #[inline(always)]
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        self.as_str().fmt(f)
33    }
34}
35
36impl Deref for FormattedAddress {
37    type Target = str;
38
39    #[inline(always)]
40    fn deref(&self) -> &Self::Target {
41        self.as_str()
42    }
43}
44
45impl FormattedAddress {
46    const MAX_ENCODING_WITHOUT_HRP_NO_SEPARATOR: usize = 33;
47    const MAX_ENCODING_WITHOUT_HRP_WITH_SEPARATOR: usize = 39;
48
49    /// Get internal string representation
50    #[inline(always)]
51    pub const fn as_str(&self) -> &str {
52        // SAFETY: Guaranteed by formatting constructor
53        unsafe { str::from_utf8_unchecked(self.buffer.split_at_unchecked(self.length).0) }
54    }
55}
56
57/// Short human-readable address part
58#[derive(Debug, Copy, Clone, Eq, PartialEq, Deref)]
59pub struct ShortHrp(Hrp);
60
61impl ShortHrp {
62    /// Maximum length of the human-readable part of the address
63    pub const MAX_HRP_LENGTH: usize = 5;
64    /// Mainnet human-readable part
65    pub const MAINNET: Self = Self(Hrp::parse_unchecked("abc"));
66    /// Testnet human-readable part
67    pub const TESTNET: Self = Self(Hrp::parse_unchecked("xyz"));
68
69    /// Create a new instance.
70    ///
71    /// Returns `None` if length of human-readable part is longer than [`Self::MAX_HRP_LENGTH`].
72    // TODO: `const fn` once `bech32 > 0.11.0` is released
73    pub fn new(hrp: Hrp) -> Option<Self> {
74        if hrp.len() > Self::MAX_HRP_LENGTH {
75            return None;
76        }
77
78        Some(Self(hrp))
79    }
80}
81
82/// Logically the same as `u128`, but aligned to `8` bytes instead of `16`.
83///
84/// Byte layout is the same as `u128`, just alignment is different.
85///
86/// The first 20 bits correspond to the shard index (the least significant bits first), and the
87/// remaining 108 bits (the most significant bits first) are an address allocated within that shard.
88/// This way, an address will have a bunch of zeroes in the middle that is shrinking as more shards
89/// are added and more addresses are allocated.
90#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
91#[repr(C)]
92pub struct Address(u64, u64);
93
94// SAFETY: Any bit pattern is valid, so it is safe to implement `TrivialType` for this type
95unsafe impl TrivialType for Address {
96    const METADATA: &[u8] = &[IoTypeMetadataKind::Address as u8];
97}
98
99// Ensure this never mismatches with code in `ab-io-type` despite being in a different crate
100const _: () = {
101    let (type_details, _metadata) = IoTypeMetadataKind::type_details(Address::METADATA)
102        .expect("Statically correct metadata; qed");
103    assert!(size_of::<Address>() == type_details.recommended_capacity as usize);
104    assert!(align_of::<Address>() == type_details.alignment.get() as usize);
105};
106
107impl fmt::Debug for Address {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.debug_tuple("Address").field(&self.as_u128()).finish()
110    }
111}
112
113impl PartialEq<&Address> for Address {
114    #[inline(always)]
115    fn eq(&self, other: &&Address) -> bool {
116        self.0 == other.0
117    }
118}
119
120impl PartialEq<Address> for &Address {
121    #[inline(always)]
122    fn eq(&self, other: &Address) -> bool {
123        self.0 == other.0
124    }
125}
126
127impl Ord for Address {
128    #[inline(always)]
129    fn cmp(&self, other: &Address) -> Ordering {
130        self.as_u128().cmp(&other.as_u128())
131    }
132}
133
134impl PartialOrd for Address {
135    #[inline(always)]
136    fn partial_cmp(&self, other: &Address) -> Option<Ordering> {
137        Some(self.cmp(other))
138    }
139}
140
141impl From<u128> for Address {
142    #[inline(always)]
143    fn from(value: u128) -> Self {
144        Self::new(value)
145    }
146}
147
148impl From<Address> for u128 {
149    #[inline(always)]
150    fn from(value: Address) -> Self {
151        value.as_u128()
152    }
153}
154
155// TODO: Method for getting creation shard out of the address
156// TODO: There should be a notion of global address
157impl Address {
158    // TODO: Various system contracts
159    /// Sentinel contract address, inaccessible and not owned by anyone
160    pub const NULL: Self = Self::new(0);
161    /// System contract for managing code of other contracts
162    pub const SYSTEM_CODE: Self = Self::new(1);
163    /// System contract for managing block state
164    pub const SYSTEM_BLOCK: Self = Self::new(2);
165    /// System contract for managing state of other contracts
166    pub const SYSTEM_STATE: Self = Self::new(3);
167    /// System contract for native token
168    pub const SYSTEM_NATIVE_TOKEN: Self = Self::new(4);
169    /// System simple wallet base contract that can be used by end user wallets
170    pub const SYSTEM_SIMPLE_WALLET_BASE: Self = Self::new(10);
171
172    // Formatting-related constants
173    const FORMAT_SEPARATOR_INTERVAL: [usize; 7] = [
174        // `1` + shard ID
175        1 + 4,
176        4,
177        3,
178        4,
179        4,
180        3,
181        4,
182    ];
183    const FORMAT_SEPARATOR: u8 = b'-';
184    const FORMAT_ALL_ZEROES: u8 = b'q';
185    const FORMAT_CHECKSUM_LENGTH: usize = 6;
186
187    /// Create a value from `u128`
188    #[inline(always)]
189    const fn new(n: u128) -> Self {
190        let mut result = MaybeUninit::<Self>::uninit();
191        // SAFETY: correct size, valid pointer, and all bits are valid
192        unsafe {
193            result.as_mut_ptr().cast::<u128>().write_unaligned(n);
194            result.assume_init()
195        }
196    }
197
198    /// Parse address from a string formatted using [`Self::format()`].
199    ///
200    /// Returns `None` if the address is formatted incorrectly.
201    pub fn parse(s: &str) -> Option<(ShortHrp, Self)> {
202        let (hrp, other) = s.split_once('1')?;
203        if hrp.len() > ShortHrp::MAX_HRP_LENGTH {
204            return None;
205        }
206
207        let mut scratch = FormattedAddress {
208            buffer: [Self::FORMAT_ALL_ZEROES; _],
209            length: 0,
210        };
211
212        // Copy human-readable part + `1`
213        scratch.buffer[..hrp.len() + 1].copy_from_slice(&s.as_bytes()[..hrp.len() + 1]);
214        // Set length to full
215        scratch.length = hrp.len() + FormattedAddress::MAX_ENCODING_WITHOUT_HRP_NO_SEPARATOR;
216
217        let mut chunks = other.rsplit(char::from(Self::FORMAT_SEPARATOR));
218        // Copy checksum into target location
219        {
220            let checksum = chunks.next()?;
221
222            if checksum.len() != Self::FORMAT_CHECKSUM_LENGTH {
223                return None;
224            }
225            scratch.buffer[..scratch.length][scratch.length - Self::FORMAT_CHECKSUM_LENGTH..]
226                .copy_from_slice(checksum.as_bytes());
227        }
228
229        {
230            let mut buffer = &mut scratch.buffer[..scratch.length - Self::FORMAT_CHECKSUM_LENGTH];
231            let mut iterator = chunks
232                .zip(Self::FORMAT_SEPARATOR_INTERVAL.into_iter().rev())
233                .peekable();
234            while let Some((chunk, max_chunk_length)) = iterator.next() {
235                let chunk = chunk.as_bytes();
236
237                if iterator.peek().is_none() {
238                    // Finish with shard index
239                    buffer[hrp.len() + 1..][..chunk.len()].copy_from_slice(chunk);
240                    break;
241                }
242
243                if chunk.len() > max_chunk_length {
244                    return None;
245                }
246
247                let target_chunk;
248                (buffer, target_chunk) = buffer.split_at_mut(buffer.len() - max_chunk_length);
249
250                target_chunk[max_chunk_length - chunk.len()..].copy_from_slice(chunk);
251            }
252        }
253
254        let checked_hrp_string = CheckedHrpstring::new::<Bech32m>(&scratch).ok()?;
255        let short_hrp = ShortHrp::new(checked_hrp_string.hrp())?;
256
257        let mut address_bytes = 0u128.to_be_bytes();
258        // Must decode the expected number of bytes
259        {
260            let mut address_bytes = address_bytes.as_mut_slice();
261            for byte in checked_hrp_string.byte_iter() {
262                if address_bytes.is_empty() {
263                    return None;
264                }
265
266                address_bytes[0] = byte;
267
268                address_bytes = &mut address_bytes[1..];
269            }
270
271            if !address_bytes.is_empty() {
272                return None;
273            }
274        }
275        let address = Address::from(u128::from_be_bytes(address_bytes));
276
277        Some((short_hrp, address))
278    }
279
280    /// Format address for presentation purposes
281    #[inline]
282    pub fn format(&self, hrp: &ShortHrp) -> FormattedAddress {
283        let mut scratch = FormattedAddress {
284            buffer: [0; _],
285            length: 0,
286        };
287
288        for char in self
289            .as_u128()
290            .to_be_bytes()
291            .into_iter()
292            .bytes_to_fes()
293            .with_checksum::<Bech32m>(hrp)
294            .bytes()
295        {
296            scratch.buffer[scratch.length] = char;
297            scratch.length += 1;
298        }
299
300        let (prefix_with_shard, other) = scratch
301            .as_str()
302            .split_at(hrp.len() + Self::FORMAT_SEPARATOR_INTERVAL[0]);
303        let (mut address_within_shard, checksum) =
304            other.split_at(Self::FORMAT_SEPARATOR_INTERVAL[1..].iter().sum());
305
306        let mut formatted_address = FormattedAddress {
307            buffer: [0; _],
308            length: 0,
309        };
310
311        // Shard index
312        {
313            let prefix_with_shard = prefix_with_shard
314                .trim_end_matches(char::from(Self::FORMAT_ALL_ZEROES))
315                .as_bytes();
316
317            formatted_address.buffer[..prefix_with_shard.len()].copy_from_slice(prefix_with_shard);
318            formatted_address.length = prefix_with_shard.len();
319
320            formatted_address.buffer[prefix_with_shard.len()] = Self::FORMAT_SEPARATOR;
321            formatted_address.length += 1;
322        }
323        // Address within shard
324        {
325            let mut finished_trimming = false;
326
327            for &chunk_size in Self::FORMAT_SEPARATOR_INTERVAL[1..].iter() {
328                let mut chunk;
329                (chunk, address_within_shard) = address_within_shard.split_at(chunk_size);
330
331                if !finished_trimming {
332                    chunk = chunk.trim_start_matches(char::from(Self::FORMAT_ALL_ZEROES));
333
334                    if chunk.is_empty() {
335                        continue;
336                    }
337
338                    finished_trimming = true;
339                }
340
341                formatted_address.buffer[formatted_address.length..][..chunk.len()]
342                    .copy_from_slice(chunk.as_bytes());
343                formatted_address.length += chunk.len();
344
345                formatted_address.buffer[formatted_address.length] = Self::FORMAT_SEPARATOR;
346                formatted_address.length += 1;
347            }
348        }
349        // Checksum
350        {
351            formatted_address.buffer[formatted_address.length..][..checksum.len()]
352                .copy_from_slice(checksum.as_bytes());
353            formatted_address.length += checksum.len();
354        }
355
356        formatted_address
357    }
358
359    /// Get inner `u128` representation
360    #[inline(always)]
361    const fn as_u128(self) -> u128 {
362        // SAFETY: correct size, valid pointer, and all bits are valid
363        unsafe { ptr::from_ref(&self).cast::<u128>().read_unaligned() }
364    }
365
366    /// System contract for address allocation on a particular shard index
367    #[inline(always)]
368    pub const fn system_address_allocator(shard_index: ShardIndex) -> Self {
369        // Shard `0` doesn't have its own allocator because there are no user-deployable contracts
370        // there, so address `0` is `NULL`, the rest up to `ShardIndex::MAX_SHARD_INDEX` correspond
371        // to address allocators of respective shards
372        Self::new((shard_index.as_u32() as u128).reverse_bits())
373    }
374}