radix_transactions/data/
formatter.rs

1use crate::data::{to_decimal, to_non_fungible_local_id, to_precise_decimal};
2use crate::internal_prelude::*;
3use radix_common::types::NonFungibleGlobalId;
4use radix_engine_interface::types::ResourceAddress;
5use radix_rust::unicode::{CustomCharEscaper, EscapeBehaviour};
6use sbor::rust::fmt;
7use sbor::*;
8
9#[derive(Clone, Copy, Debug)]
10pub struct MultiLine {
11    margin: usize,
12    indent: usize,
13}
14
15#[derive(Clone, Copy, Debug, Default)]
16pub struct ManifestDecompilationDisplayContext<'a> {
17    pub address_bech32_encoder: Option<&'a AddressBech32Encoder>,
18    pub object_names: ManifestObjectNamesRef<'a>,
19    pub multi_line: Option<MultiLine>,
20}
21
22impl<'a> ManifestDecompilationDisplayContext<'a> {
23    pub fn no_context() -> Self {
24        Self::default()
25    }
26
27    pub fn with_optional_bech32(address_bech32_encoder: Option<&'a AddressBech32Encoder>) -> Self {
28        Self {
29            address_bech32_encoder,
30            ..Default::default()
31        }
32    }
33
34    pub fn with_bech32_and_names(
35        address_bech32_encoder: Option<&'a AddressBech32Encoder>,
36        object_names: ManifestObjectNamesRef<'a>,
37    ) -> Self {
38        Self {
39            address_bech32_encoder,
40            object_names,
41            ..Default::default()
42        }
43    }
44
45    pub fn with_multi_line(mut self, margin: usize, indent: usize) -> Self {
46        self.multi_line = Some(MultiLine { margin, indent });
47        self
48    }
49
50    pub fn get_object_names(&self) -> ManifestObjectNamesRef {
51        self.object_names
52    }
53
54    pub fn get_indent(&self, depth: usize) -> String {
55        if let Some(MultiLine { margin, indent }) = self.multi_line {
56            " ".repeat(margin + indent * depth)
57        } else {
58            String::new()
59        }
60    }
61
62    pub fn get_new_line(&self) -> &str {
63        if self.multi_line.is_some() {
64            "\n"
65        } else {
66            " "
67        }
68    }
69}
70
71impl<'a> Into<ManifestDecompilationDisplayContext<'a>> for &'a AddressBech32Encoder {
72    fn into(self) -> ManifestDecompilationDisplayContext<'a> {
73        ManifestDecompilationDisplayContext::with_optional_bech32(Some(self))
74    }
75}
76
77impl<'a> Into<ManifestDecompilationDisplayContext<'a>> for Option<&'a AddressBech32Encoder> {
78    fn into(self) -> ManifestDecompilationDisplayContext<'a> {
79        ManifestDecompilationDisplayContext::with_optional_bech32(self)
80    }
81}
82
83impl<'a> ContextualDisplay<ManifestDecompilationDisplayContext<'a>> for ManifestValue {
84    type Error = fmt::Error;
85
86    fn contextual_format<F: fmt::Write>(
87        &self,
88        f: &mut F,
89        context: &ManifestDecompilationDisplayContext<'a>,
90    ) -> Result<(), Self::Error> {
91        format_manifest_value(f, self, context, false, 0)
92    }
93}
94
95macro_rules! write_with_indent {
96    ($f:expr, $context:expr, $should_indent:expr, $depth:expr, $($args:expr),*) => {
97        if $should_indent {
98            write!($f,
99                "{}{}",
100                $context.get_indent($depth),
101                format!($($args),*)
102            )
103        } else {
104            write!($f, $($args),*)
105        }
106    };
107}
108
109pub fn format_manifest_value<F: fmt::Write>(
110    f: &mut F,
111    value: &ManifestValue,
112    context: &ManifestDecompilationDisplayContext,
113    indent_start: bool,
114    depth: usize,
115) -> fmt::Result {
116    match value {
117        // primitive types
118        Value::Bool { value } => write_with_indent!(f, context, indent_start, depth, "{}", value)?,
119        Value::I8 { value } => write_with_indent!(f, context, indent_start, depth, "{}i8", value)?,
120        Value::I16 { value } => {
121            write_with_indent!(f, context, indent_start, depth, "{}i16", value)?
122        }
123        Value::I32 { value } => {
124            write_with_indent!(f, context, indent_start, depth, "{}i32", value)?
125        }
126        Value::I64 { value } => {
127            write_with_indent!(f, context, indent_start, depth, "{}i64", value)?
128        }
129        Value::I128 { value } => {
130            write_with_indent!(f, context, indent_start, depth, "{}i128", value)?
131        }
132        Value::U8 { value } => write_with_indent!(f, context, indent_start, depth, "{}u8", value)?,
133        Value::U16 { value } => {
134            write_with_indent!(f, context, indent_start, depth, "{}u16", value)?
135        }
136        Value::U32 { value } => {
137            write_with_indent!(f, context, indent_start, depth, "{}u32", value)?
138        }
139        Value::U64 { value } => {
140            write_with_indent!(f, context, indent_start, depth, "{}u64", value)?
141        }
142        Value::U128 { value } => {
143            write_with_indent!(f, context, indent_start, depth, "{}u128", value)?
144        }
145        Value::String { value } => {
146            write_with_indent!(
147                f,
148                context,
149                indent_start,
150                depth,
151                "{}",
152                ManifestCustomCharEscaper::escaped(value.as_str())
153            )?;
154        }
155        Value::Tuple { fields } => {
156            if fields.len() == 2 {
157                if let (
158                    ManifestValue::Custom {
159                        value: ManifestCustomValue::Address(ManifestAddress::Static(address)),
160                    },
161                    ManifestValue::Custom {
162                        value: ManifestCustomValue::NonFungibleLocalId(id),
163                    },
164                ) = (&fields[0], &fields[1])
165                {
166                    if let Ok(resource_address) = ResourceAddress::try_from(address.0.as_ref()) {
167                        let global_id = NonFungibleGlobalId::new(
168                            resource_address,
169                            to_non_fungible_local_id(id.clone()),
170                        );
171                        return write_with_indent!(
172                            f,
173                            context,
174                            indent_start,
175                            depth,
176                            "NonFungibleGlobalId(\"{}\")",
177                            global_id.display(context.address_bech32_encoder)
178                        );
179                    }
180                }
181            }
182
183            if fields.is_empty() {
184                write_with_indent!(f, context, indent_start, depth, "Tuple()")?;
185            } else {
186                write_with_indent!(
187                    f,
188                    context,
189                    indent_start,
190                    depth,
191                    "Tuple({}",
192                    context.get_new_line()
193                )?;
194                format_elements(f, fields, context, depth + 1)?;
195                write_with_indent!(f, context, true, depth, ")")?;
196            }
197        }
198        Value::Enum {
199            discriminator,
200            fields,
201        } => {
202            if fields.is_empty() {
203                write_with_indent!(
204                    f,
205                    context,
206                    indent_start,
207                    depth,
208                    "Enum<{}u8>()",
209                    discriminator
210                )?;
211            } else {
212                write_with_indent!(
213                    f,
214                    context,
215                    indent_start,
216                    depth,
217                    "Enum<{}u8>({}",
218                    discriminator,
219                    context.get_new_line()
220                )?;
221                format_elements(f, fields, context, depth + 1)?;
222                write_with_indent!(f, context, true, depth, ")")?;
223            }
224        }
225        Value::Array {
226            element_value_kind,
227            elements,
228        } => match element_value_kind {
229            ValueKind::U8 => {
230                let vec: Vec<u8> = elements
231                    .iter()
232                    .map(|e| match e {
233                        Value::U8 { value } => Ok(*value),
234                        _ => Err(fmt::Error),
235                    })
236                    .collect::<Result<_, _>>()?;
237
238                write_with_indent!(
239                    f,
240                    context,
241                    indent_start,
242                    depth,
243                    "Bytes(\"{}\")",
244                    hex::encode(vec)
245                )?;
246            }
247            _ => {
248                if elements.is_empty() {
249                    write_with_indent!(
250                        f,
251                        context,
252                        indent_start,
253                        depth,
254                        "Array<{}>()",
255                        format_value_kind(element_value_kind)
256                    )?;
257                } else {
258                    write_with_indent!(
259                        f,
260                        context,
261                        indent_start,
262                        depth,
263                        "Array<{}>({}",
264                        format_value_kind(element_value_kind),
265                        context.get_new_line()
266                    )?;
267                    format_elements(f, elements, context, depth + 1)?;
268                    write_with_indent!(f, context, true, depth, ")")?;
269                }
270            }
271        },
272        Value::Map {
273            key_value_kind,
274            value_value_kind,
275            entries,
276        } => {
277            if entries.is_empty() {
278                write_with_indent!(
279                    f,
280                    context,
281                    indent_start,
282                    depth,
283                    "Map<{}, {}>()",
284                    format_value_kind(key_value_kind),
285                    format_value_kind(value_value_kind)
286                )?;
287            } else {
288                write_with_indent!(
289                    f,
290                    context,
291                    indent_start,
292                    depth,
293                    "Map<{}, {}>({}",
294                    format_value_kind(key_value_kind),
295                    format_value_kind(value_value_kind),
296                    context.get_new_line()
297                )?;
298                format_kv_entries(f, entries, context, depth + 1)?;
299                write_with_indent!(f, context, true, depth, ")")?;
300            }
301        }
302        // custom types
303        Value::Custom { value } => {
304            format_custom_value(f, value, context, indent_start, depth)?;
305        }
306    };
307    Ok(())
308}
309
310pub fn format_elements<F: fmt::Write>(
311    f: &mut F,
312    values: &[ManifestValue],
313    context: &ManifestDecompilationDisplayContext,
314    depth: usize,
315) -> fmt::Result {
316    for (i, x) in values.iter().enumerate() {
317        format_manifest_value(f, x, context, true, depth)?;
318        if i == values.len() - 1 {
319            write!(f, "{}", context.get_new_line())?;
320        } else {
321            write!(f, ",{}", context.get_new_line())?;
322        }
323    }
324    Ok(())
325}
326
327pub fn format_kv_entries<F: fmt::Write>(
328    f: &mut F,
329    entries: &[(ManifestValue, ManifestValue)],
330    context: &ManifestDecompilationDisplayContext,
331    depth: usize,
332) -> fmt::Result {
333    for (i, x) in entries.iter().enumerate() {
334        format_manifest_value(f, &x.0, context, true, depth)?;
335        write!(f, " => ")?;
336        format_manifest_value(f, &x.1, context, false, depth)?;
337        if i == entries.len() - 1 {
338            write!(f, "{}", context.get_new_line())?;
339        } else {
340            write!(f, ",{}", context.get_new_line())?;
341        }
342    }
343    Ok(())
344}
345
346pub fn format_custom_value<F: fmt::Write>(
347    f: &mut F,
348    value: &ManifestCustomValue,
349    context: &ManifestDecompilationDisplayContext,
350    indent_start: bool,
351    depth: usize,
352) -> fmt::Result {
353    match value {
354        ManifestCustomValue::Address(value) => match value {
355            ManifestAddress::Static(node_id) => {
356                write_with_indent!(
357                    f,
358                    context,
359                    indent_start,
360                    depth,
361                    "Address(\"{}\")",
362                    if let Some(encoder) = context.address_bech32_encoder {
363                        if let Ok(bech32) = encoder.encode(node_id.as_ref()) {
364                            bech32
365                        } else {
366                            hex::encode(node_id.as_bytes())
367                        }
368                    } else {
369                        hex::encode(node_id.as_bytes())
370                    }
371                )?;
372            }
373            ManifestAddress::Named(address_id) => {
374                write_with_indent!(
375                    f,
376                    context,
377                    indent_start,
378                    depth,
379                    "NamedAddress(\"{}\")",
380                    context.get_object_names().address_name(*address_id)
381                )?;
382            }
383        },
384        ManifestCustomValue::Bucket(value) => {
385            write_with_indent!(
386                f,
387                context,
388                indent_start,
389                depth,
390                "Bucket(\"{}\")",
391                context.get_object_names().bucket_name(*value)
392            )?;
393        }
394        ManifestCustomValue::Proof(value) => {
395            write_with_indent!(
396                f,
397                context,
398                indent_start,
399                depth,
400                "Proof(\"{}\")",
401                context.get_object_names().proof_name(*value)
402            )?;
403        }
404        ManifestCustomValue::AddressReservation(value) => {
405            write_with_indent!(
406                f,
407                context,
408                indent_start,
409                depth,
410                "AddressReservation(\"{}\")",
411                context.get_object_names().address_reservation_name(*value)
412            )?;
413        }
414        ManifestCustomValue::Expression(value) => {
415            write_with_indent!(
416                f,
417                context,
418                indent_start,
419                depth,
420                "Expression(\"{}\")",
421                match value {
422                    ManifestExpression::EntireWorktop => "ENTIRE_WORKTOP",
423                    ManifestExpression::EntireAuthZone => "ENTIRE_AUTH_ZONE",
424                }
425            )?;
426        }
427        ManifestCustomValue::Blob(value) => {
428            write_with_indent!(
429                f,
430                context,
431                indent_start,
432                depth,
433                "Blob(\"{}\")",
434                hex::encode(&value.0)
435            )?;
436        }
437        ManifestCustomValue::Decimal(value) => {
438            write_with_indent!(
439                f,
440                context,
441                indent_start,
442                depth,
443                "Decimal(\"{}\")",
444                to_decimal(value.clone())
445            )?;
446        }
447        ManifestCustomValue::PreciseDecimal(value) => {
448            write_with_indent!(
449                f,
450                context,
451                indent_start,
452                depth,
453                "PreciseDecimal(\"{}\")",
454                to_precise_decimal(value.clone())
455            )?;
456        }
457        ManifestCustomValue::NonFungibleLocalId(value) => {
458            write_with_indent!(
459                f,
460                context,
461                indent_start,
462                depth,
463                "NonFungibleLocalId(\"{}\")",
464                to_non_fungible_local_id(value.clone())
465            )?;
466        }
467    }
468    Ok(())
469}
470
471pub fn format_value_kind(value_kind: &ManifestValueKind) -> &str {
472    match value_kind {
473        ValueKind::Bool => "Bool",
474        ValueKind::I8 => "I8",
475        ValueKind::I16 => "I16",
476        ValueKind::I32 => "I32",
477        ValueKind::I64 => "I64",
478        ValueKind::I128 => "I128",
479        ValueKind::U8 => "U8",
480        ValueKind::U16 => "U16",
481        ValueKind::U32 => "U32",
482        ValueKind::U64 => "U64",
483        ValueKind::U128 => "U128",
484        ValueKind::String => "String",
485        ValueKind::Enum => "Enum",
486        ValueKind::Array => "Array",
487        ValueKind::Tuple => "Tuple",
488        ValueKind::Map => "Map",
489        ValueKind::Custom(value_kind) => match value_kind {
490            ManifestCustomValueKind::Address => "Address",
491            ManifestCustomValueKind::Bucket => "Bucket",
492            ManifestCustomValueKind::Proof => "Proof",
493            ManifestCustomValueKind::Expression => "Expression",
494            ManifestCustomValueKind::Blob => "Blob",
495            ManifestCustomValueKind::Decimal => "Decimal",
496            ManifestCustomValueKind::PreciseDecimal => "PreciseDecimal",
497            ManifestCustomValueKind::NonFungibleLocalId => "NonFungibleLocalId",
498            ManifestCustomValueKind::AddressReservation => "AddressReservation",
499        },
500    }
501}
502
503pub fn display_value_kind(value_kind: &ManifestValueKind) -> DisplayableManifestValueKind {
504    DisplayableManifestValueKind(value_kind)
505}
506
507pub struct DisplayableManifestValueKind<'a>(&'a ManifestValueKind);
508
509impl<'a> fmt::Display for DisplayableManifestValueKind<'a> {
510    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
511        write!(f, "{}", format_value_kind(&self.0))
512    }
513}
514
515impl<'a> ContextualDisplay<ManifestDecompilationDisplayContext<'a>> for ManifestCustomValue {
516    type Error = fmt::Error;
517
518    fn contextual_format<F: fmt::Write>(
519        &self,
520        f: &mut F,
521        context: &ManifestDecompilationDisplayContext<'a>,
522    ) -> Result<(), Self::Error> {
523        format_custom_value(f, self, context, false, 0)
524    }
525}
526
527pub struct ManifestCustomCharEscaper;
528
529impl CustomCharEscaper for ManifestCustomCharEscaper {
530    fn resolve_escape_behaviour(c: char) -> EscapeBehaviour {
531        match c {
532            '\\' => EscapeBehaviour::Replace(r#"\\"#),
533            '\n' => EscapeBehaviour::Replace(r#"\n"#),
534            '\r' => EscapeBehaviour::Replace(r#"\r"#),
535            '\t' => EscapeBehaviour::Replace(r#"\t"#),
536            '\x08' => EscapeBehaviour::Replace(r#"\b"#),
537            '\x0c' => EscapeBehaviour::Replace(r#"\f"#),
538            '"' => EscapeBehaviour::Replace(r#"\""#),
539            _ if should_escape_unicode_char(c) => EscapeBehaviour::UnicodeEscape,
540            _ => EscapeBehaviour::None,
541        }
542    }
543
544    fn format_unicode_escaped_char(f: &mut impl fmt::Write, c: char) -> fmt::Result {
545        radix_rust::unicode::format_json_utf16_escaped_char(f, c)
546    }
547}
548
549fn should_escape_unicode_char(c: char) -> bool {
550    // Per the JSON spec, we need to encode as least control characters.
551    //
552    // Some JSON encoders default to encoding everything that is non-ASCII.
553    // But this is a bit too restrictive, as it's common for people to
554    // want to use emoji or non-ASCII in metadata, and it would be nice
555    // for the manifest canonical encoding to display this.
556    //
557    // If we try to be minimal, and just encode control characters
558    // (e.g. given by `char.is_control()`), this only covers the
559    // `Cc` category which misses things like the RTL override which
560    // is in the `Cf` category. Such characters could mess up the display
561    // of manifests, so we should exclude them.
562    //
563    // There are also other characters which may cause confusion in
564    // text, such as grapheme extenders which can be used to add arbitrary
565    // accents to characters.
566    //
567    // So when Rust formats debug strings, it also escapes characters
568    // such as grapheme extenders, and other characters it views as
569    // "non-printable". We view this as a sensible default behaviour,
570    // so we follow this whne choosing to display manifest strings.
571    radix_rust::unicode::rust_1_81_should_unicode_escape_in_debug_str(c)
572}