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}