Skip to main content

ab_core_primitives/
address.rs

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