Skip to main content

numbat_codec/
num_conv.rs

1/// Encodes number to minimimum number of bytes (top-encoding).
2///
3/// Smaller types need to be converted to u64 before using this function.
4///
5/// No generics here, we avoid monomorphization to make the SC binary as small as possible.
6pub fn top_encode_number(x: u64, signed: bool, buffer: &mut [u8; 8]) -> &[u8] {
7    *buffer = x.to_be_bytes();
8    if x == 0 {
9        // 0 is a special case
10        return &[];
11    }
12
13    if signed && x == u64::MAX {
14        // -1 is a special case
15        // will return a single 0xFF byte
16        return &buffer[7..];
17    }
18
19    let negative = signed &&  // only possible when signed flag
20		buffer[0] > 0x7fu8; // most significant bit is 1
21
22    let irrelevant_byte = if negative { 0xffu8 } else { 0x00u8 };
23
24    let mut offset = 0usize;
25    while buffer[offset] == irrelevant_byte {
26        debug_assert!(offset < 7);
27        offset += 1;
28    }
29
30    if signed && buffer[offset] >> 7 != negative as u8 {
31        debug_assert!(offset > 0);
32        offset -= 1;
33    }
34
35    &buffer[offset..]
36}
37
38/// Handles both top-encoding and nested-encoding, signed and unsigned, of any length.
39///
40/// The result needs to be validated to not exceed limits and then cast to the desired type.
41///
42/// No generics here, we avoid monomorphization to make the SC binary as small as possible.
43pub fn universal_decode_number(bytes: &[u8], signed: bool) -> u64 {
44    if bytes.is_empty() {
45        return 0;
46    }
47    let negative = signed && bytes[0] >> 7 == 1;
48    let mut result = if negative {
49        // start with all bits set to 1,
50        // to ensure that if there are fewer bytes than the result type width,
51        // the leading bits will be 1 instead of 0
52        u64::MAX
53    } else {
54        0u64
55    };
56    for byte in bytes.iter() {
57        result <<= 8;
58        result |= *byte as u64;
59    }
60    result
61}