multiversx_chain_scenario_format/value_interpreter/
reconstructor.rs

1use num_bigint::BigUint;
2
3use crate::{
4    reconstruct_trait::ReconstructorContext,
5    serde_raw::ValueSubTree,
6    value_interpreter::functions::{
7        SC_ADDRESS_NUM_LEADING_ZEROS, SC_ADDRESS_RESERVED_PREFIX_LENGTH,
8    },
9};
10pub enum ExprReconstructorHint {
11    // NoHint indicates that the type if not known
12    NoHint,
13
14    // NumberHint hints that value should be a number
15    UnsignedNumberHint,
16
17    // AddressHint hints that value should be an address
18    AddressHint,
19
20    // StrHint hints that value should be a string expression, e.g. a username, "str:..."
21    StrHint,
22
23    // CodeHint hints that value should be a smart contract code, normally loaded from a file
24    CodeHint,
25}
26
27const MAX_BYTES_INTERPRETED_AS_NUMBER: usize = 15;
28
29const SC_ADDRESS_LENGTH: usize = 32;
30const SC_CODE_LENGTH: usize = 20;
31
32pub fn reconstruct(
33    value: &[u8],
34    hint: &ExprReconstructorHint,
35    _context: &ReconstructorContext,
36) -> ValueSubTree {
37    let str: String = match hint {
38        ExprReconstructorHint::UnsignedNumberHint => BigUint::from_bytes_be(value).to_string(),
39        ExprReconstructorHint::StrHint => format!("str:{}", String::from_utf8_lossy(value)),
40        ExprReconstructorHint::AddressHint => address_pretty(value),
41        ExprReconstructorHint::CodeHint => code_pretty(value),
42        _ => unknown_byte_array_pretty(value),
43    };
44    ValueSubTree::Str(str)
45}
46
47pub fn reconstruct_from_biguint(value: BigUint, context: &ReconstructorContext) -> ValueSubTree {
48    reconstruct(
49        &value.to_bytes_be(),
50        &ExprReconstructorHint::UnsignedNumberHint,
51        context,
52    )
53}
54
55pub fn reconstruct_from_u64(value: u64, context: &ReconstructorContext) -> ValueSubTree {
56    reconstruct(
57        &BigUint::from(value).to_bytes_be(),
58        &ExprReconstructorHint::UnsignedNumberHint,
59        context,
60    )
61}
62
63pub fn reconstruction_list(
64    values: &[&[u8]],
65    hint: &ExprReconstructorHint,
66    context: &ReconstructorContext,
67) -> ValueSubTree {
68    let mut strings: Vec<ValueSubTree> = Vec::new();
69    for value in values.iter() {
70        strings.push(reconstruct(value, hint, context));
71    }
72    ValueSubTree::List(strings)
73}
74
75fn unknown_byte_array_pretty(bytes: &[u8]) -> String {
76    if bytes.is_empty() {
77        return String::new();
78    }
79
80    // fully interpret as string
81    if can_interpret_as_string(bytes) {
82        return format!(
83            "0x{} (str:{})",
84            hex::encode(bytes),
85            String::from_utf8_lossy(bytes)
86        );
87    }
88
89    // interpret as number
90    if bytes.len() < MAX_BYTES_INTERPRETED_AS_NUMBER {
91        let as_uint = BigUint::from_bytes_be(bytes).to_string();
92        return format!("0x{} ({})", hex::encode(bytes), as_uint);
93    }
94
95    // default interpret as string with escaped bytes
96    format!(
97        "0x{} (str:{:?})",
98        hex::encode(bytes),
99        String::from_utf8_lossy(bytes).to_string(),
100    )
101}
102
103fn address_pretty(value: &[u8]) -> String {
104    if value.len() != 32 {
105        return unknown_byte_array_pretty(value);
106    }
107
108    // smart contract address
109    if value[..SC_ADDRESS_NUM_LEADING_ZEROS] == [0; 8] {
110        if value[SC_ADDRESS_LENGTH - 1] == b'_' {
111            let address_str =
112                String::from_utf8_lossy(&value[SC_ADDRESS_RESERVED_PREFIX_LENGTH..]).to_string();
113            return format!("sc:{}", address_str.trim_end_matches('_').to_owned());
114        } else {
115            // last byte is the shard id and is explicit
116
117            let address_str = String::from_utf8_lossy(
118                &value[SC_ADDRESS_RESERVED_PREFIX_LENGTH..SC_ADDRESS_LENGTH - 1],
119            )
120            .to_string();
121            let shard_id = value[SC_ADDRESS_LENGTH - 1];
122            return format!(
123                "sc:{}#{:x}",
124                address_str.trim_end_matches('_').to_owned(),
125                shard_id
126            );
127        }
128    }
129
130    // regular addresses
131    if value[SC_ADDRESS_LENGTH - 1] == b'_' {
132        let address_str = String::from_utf8_lossy(value).to_string();
133        format!("address:{}", address_str.trim_end_matches('_').to_owned())
134    } else {
135        let mut address_str = String::from_utf8_lossy(&value[..SC_ADDRESS_LENGTH - 1]).to_string();
136        address_str = address_str.trim_end_matches('_').to_string();
137        let shard_id = value[SC_ADDRESS_LENGTH - 1];
138        let address_expr = format!("address:{address_str}#{shard_id:02x}");
139        if !can_interpret_as_string(&[value[SC_ADDRESS_LENGTH - 1]]) {
140            return format!("0x{} ({})", hex::encode(value), address_expr);
141        }
142        address_expr
143    }
144}
145
146fn can_interpret_as_string(bytes: &[u8]) -> bool {
147    if bytes.is_empty() {
148        return false;
149    }
150    !bytes.iter().any(|&b| !(32..=126).contains(&b))
151}
152
153fn code_pretty(bytes: &[u8]) -> String {
154    if bytes.is_empty() {
155        return String::new();
156    }
157    let encoded = hex::encode(bytes);
158
159    if encoded.len() > SC_CODE_LENGTH {
160        return format!("0x{}...", &encoded[..SC_CODE_LENGTH]);
161    }
162
163    format!("0x{encoded}")
164}