Skip to main content

ab_core_primitives/
address.rs

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