sui_json_rpc_types/
sui_move.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use colored::Colorize;
5use itertools::Itertools;
6use move_binary_format::file_format::{Ability, AbilitySet, DatatypeTyParameter, Visibility};
7use move_binary_format::normalized::{
8    self, Enum as NormalizedEnum, Field as NormalizedField, Function as NormalizedFunction,
9    Module as NormalizedModule, Struct as NormalizedStruct, Type as NormalizedType,
10};
11use move_command_line_common::error_bitset::ErrorBitset;
12use move_core_types::annotated_value::{MoveStruct, MoveValue, MoveVariant};
13use move_core_types::identifier::Identifier;
14use move_core_types::language_storage::StructTag;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use serde_json::{Value, json};
18use serde_with::serde_as;
19use std::collections::BTreeMap;
20use std::fmt;
21use std::fmt::{Display, Formatter, Write};
22use std::hash::Hash;
23use sui_macros::EnumVariantOrder;
24use tracing::warn;
25
26use sui_types::base_types::{ObjectID, SuiAddress};
27use sui_types::execution_status::MoveLocation;
28use sui_types::sui_serde::SuiStructTag;
29
30pub type SuiMoveTypeParameterIndex = u16;
31
32#[cfg(test)]
33#[path = "unit_tests/sui_move_tests.rs"]
34mod sui_move_tests;
35
36#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
37pub enum SuiMoveAbility {
38    Copy,
39    Drop,
40    Store,
41    Key,
42}
43
44#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
45pub struct SuiMoveAbilitySet {
46    pub abilities: Vec<SuiMoveAbility>,
47}
48
49#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
50pub enum SuiMoveVisibility {
51    Private,
52    Public,
53    Friend,
54}
55
56#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
57#[serde(rename_all = "camelCase")]
58pub struct SuiMoveStructTypeParameter {
59    pub constraints: SuiMoveAbilitySet,
60    pub is_phantom: bool,
61}
62
63#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
64pub struct SuiMoveNormalizedField {
65    pub name: String,
66    #[serde(rename = "type")]
67    pub type_: SuiMoveNormalizedType,
68}
69
70#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
71#[serde(rename_all = "camelCase")]
72pub struct SuiMoveNormalizedStruct {
73    pub abilities: SuiMoveAbilitySet,
74    pub type_parameters: Vec<SuiMoveStructTypeParameter>,
75    pub fields: Vec<SuiMoveNormalizedField>,
76}
77
78#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
79#[serde(rename_all = "camelCase")]
80pub struct SuiMoveNormalizedEnum {
81    pub abilities: SuiMoveAbilitySet,
82    pub type_parameters: Vec<SuiMoveStructTypeParameter>,
83    pub variants: BTreeMap<String, Vec<SuiMoveNormalizedField>>,
84    #[serde(default)]
85    pub variant_declaration_order: Option<Vec<String>>,
86}
87
88#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
89pub enum SuiMoveNormalizedType {
90    Bool,
91    U8,
92    U16,
93    U32,
94    U64,
95    U128,
96    U256,
97    Address,
98    Signer,
99    Struct {
100        #[serde(flatten)]
101        inner: Box<SuiMoveNormalizedStructType>,
102    },
103    Vector(Box<SuiMoveNormalizedType>),
104    TypeParameter(SuiMoveTypeParameterIndex),
105    Reference(Box<SuiMoveNormalizedType>),
106    MutableReference(Box<SuiMoveNormalizedType>),
107}
108
109#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
110#[serde(rename_all = "camelCase")]
111pub struct SuiMoveNormalizedStructType {
112    pub address: String,
113    pub module: String,
114    pub name: String,
115    pub type_arguments: Vec<SuiMoveNormalizedType>,
116}
117
118#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
119#[serde(rename_all = "camelCase")]
120pub struct SuiMoveNormalizedFunction {
121    pub visibility: SuiMoveVisibility,
122    pub is_entry: bool,
123    pub type_parameters: Vec<SuiMoveAbilitySet>,
124    pub parameters: Vec<SuiMoveNormalizedType>,
125    pub return_: Vec<SuiMoveNormalizedType>,
126}
127
128#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
129pub struct SuiMoveModuleId {
130    address: String,
131    name: String,
132}
133
134#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
135#[serde(rename_all = "camelCase")]
136pub struct SuiMoveNormalizedModule {
137    pub file_format_version: u32,
138    pub address: String,
139    pub name: String,
140    pub friends: Vec<SuiMoveModuleId>,
141    pub structs: BTreeMap<String, SuiMoveNormalizedStruct>,
142    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
143    pub enums: BTreeMap<String, SuiMoveNormalizedEnum>,
144    pub exposed_functions: BTreeMap<String, SuiMoveNormalizedFunction>,
145}
146
147impl PartialEq for SuiMoveNormalizedModule {
148    fn eq(&self, other: &Self) -> bool {
149        self.file_format_version == other.file_format_version
150            && self.address == other.address
151            && self.name == other.name
152    }
153}
154
155impl<S: std::hash::Hash + Eq + ToString> From<&NormalizedModule<S>> for SuiMoveNormalizedModule {
156    fn from(module: &NormalizedModule<S>) -> Self {
157        Self {
158            file_format_version: module.file_format_version,
159            address: module.address().to_hex_literal(),
160            name: module.name().to_string(),
161            friends: module
162                .friends
163                .iter()
164                .map(|module_id| SuiMoveModuleId {
165                    address: module_id.address.to_hex_literal(),
166                    name: module_id.name.to_string(),
167                })
168                .collect::<Vec<SuiMoveModuleId>>(),
169            structs: module
170                .structs
171                .iter()
172                .map(|(name, struct_)| {
173                    (name.to_string(), SuiMoveNormalizedStruct::from(&**struct_))
174                })
175                .collect::<BTreeMap<String, SuiMoveNormalizedStruct>>(),
176            enums: module
177                .enums
178                .iter()
179                .map(|(name, enum_)| (name.to_string(), SuiMoveNormalizedEnum::from(&**enum_)))
180                .collect(),
181            exposed_functions: module
182                .functions
183                .iter()
184                .filter(|(_name, function)| {
185                    function.is_entry || function.visibility != Visibility::Private
186                })
187                .map(|(name, function)| {
188                    // TODO: Do we want to expose the private functions as well?
189
190                    (
191                        name.to_string(),
192                        SuiMoveNormalizedFunction::from(&**function),
193                    )
194                })
195                .collect::<BTreeMap<String, SuiMoveNormalizedFunction>>(),
196        }
197    }
198}
199
200impl<S: Hash + Eq + ToString> From<&NormalizedFunction<S>> for SuiMoveNormalizedFunction {
201    fn from(function: &NormalizedFunction<S>) -> Self {
202        Self {
203            visibility: match function.visibility {
204                Visibility::Private => SuiMoveVisibility::Private,
205                Visibility::Public => SuiMoveVisibility::Public,
206                Visibility::Friend => SuiMoveVisibility::Friend,
207            },
208            is_entry: function.is_entry,
209            type_parameters: function
210                .type_parameters
211                .iter()
212                .copied()
213                .map(|a| a.into())
214                .collect::<Vec<SuiMoveAbilitySet>>(),
215            parameters: function
216                .parameters
217                .iter()
218                .map(|t| SuiMoveNormalizedType::from(&**t))
219                .collect::<Vec<SuiMoveNormalizedType>>(),
220            return_: function
221                .return_
222                .iter()
223                .map(|t| SuiMoveNormalizedType::from(&**t))
224                .collect::<Vec<SuiMoveNormalizedType>>(),
225        }
226    }
227}
228
229impl<S: Hash + Eq + ToString> From<&NormalizedStruct<S>> for SuiMoveNormalizedStruct {
230    fn from(struct_: &NormalizedStruct<S>) -> Self {
231        Self {
232            abilities: struct_.abilities.into(),
233            type_parameters: struct_
234                .type_parameters
235                .iter()
236                .copied()
237                .map(SuiMoveStructTypeParameter::from)
238                .collect::<Vec<SuiMoveStructTypeParameter>>(),
239            fields: struct_
240                .fields
241                .0
242                .values()
243                .map(|f| SuiMoveNormalizedField::from(&**f))
244                .collect::<Vec<SuiMoveNormalizedField>>(),
245        }
246    }
247}
248
249impl<S: Hash + Eq + ToString> From<&NormalizedEnum<S>> for SuiMoveNormalizedEnum {
250    fn from(value: &NormalizedEnum<S>) -> Self {
251        let variants = value
252            .variants
253            .values()
254            .map(|variant| {
255                (
256                    variant.name.to_string(),
257                    variant
258                        .fields
259                        .0
260                        .values()
261                        .map(|f| SuiMoveNormalizedField::from(&**f))
262                        .collect::<Vec<SuiMoveNormalizedField>>(),
263                )
264            })
265            .collect::<Vec<(String, Vec<SuiMoveNormalizedField>)>>();
266        let variant_declaration_order = variants
267            .iter()
268            .map(|(name, _)| name.clone())
269            .collect::<Vec<String>>();
270        let variants = variants.into_iter().collect();
271        Self {
272            abilities: value.abilities.into(),
273            type_parameters: value
274                .type_parameters
275                .iter()
276                .copied()
277                .map(SuiMoveStructTypeParameter::from)
278                .collect::<Vec<SuiMoveStructTypeParameter>>(),
279            variants,
280            variant_declaration_order: Some(variant_declaration_order),
281        }
282    }
283}
284
285impl From<DatatypeTyParameter> for SuiMoveStructTypeParameter {
286    fn from(type_parameter: DatatypeTyParameter) -> Self {
287        Self {
288            constraints: type_parameter.constraints.into(),
289            is_phantom: type_parameter.is_phantom,
290        }
291    }
292}
293
294impl<S: ToString> From<&NormalizedField<S>> for SuiMoveNormalizedField {
295    fn from(normalized_field: &NormalizedField<S>) -> Self {
296        Self {
297            name: normalized_field.name.to_string(),
298            type_: SuiMoveNormalizedType::from(&normalized_field.type_),
299        }
300    }
301}
302
303impl<S: ToString> From<&NormalizedType<S>> for SuiMoveNormalizedType {
304    fn from(type_: &NormalizedType<S>) -> Self {
305        match type_ {
306            NormalizedType::Bool => SuiMoveNormalizedType::Bool,
307            NormalizedType::U8 => SuiMoveNormalizedType::U8,
308            NormalizedType::U16 => SuiMoveNormalizedType::U16,
309            NormalizedType::U32 => SuiMoveNormalizedType::U32,
310            NormalizedType::U64 => SuiMoveNormalizedType::U64,
311            NormalizedType::U128 => SuiMoveNormalizedType::U128,
312            NormalizedType::U256 => SuiMoveNormalizedType::U256,
313            NormalizedType::Address => SuiMoveNormalizedType::Address,
314            NormalizedType::Signer => SuiMoveNormalizedType::Signer,
315            NormalizedType::Datatype(dt) => {
316                let normalized::Datatype {
317                    module,
318                    name,
319                    type_arguments,
320                } = &**dt;
321                SuiMoveNormalizedType::new_struct(
322                    module.address.to_hex_literal(),
323                    module.name.to_string(),
324                    name.to_string(),
325                    type_arguments
326                        .iter()
327                        .map(SuiMoveNormalizedType::from)
328                        .collect::<Vec<SuiMoveNormalizedType>>(),
329                )
330            }
331            NormalizedType::Vector(v) => {
332                SuiMoveNormalizedType::Vector(Box::new(SuiMoveNormalizedType::from(&**v)))
333            }
334            NormalizedType::TypeParameter(t) => SuiMoveNormalizedType::TypeParameter(*t),
335            NormalizedType::Reference(false, r) => {
336                SuiMoveNormalizedType::Reference(Box::new(SuiMoveNormalizedType::from(&**r)))
337            }
338            NormalizedType::Reference(true, mr) => SuiMoveNormalizedType::MutableReference(
339                Box::new(SuiMoveNormalizedType::from(&**mr)),
340            ),
341        }
342    }
343}
344
345impl From<AbilitySet> for SuiMoveAbilitySet {
346    fn from(set: AbilitySet) -> SuiMoveAbilitySet {
347        Self {
348            abilities: set
349                .into_iter()
350                .map(|a| match a {
351                    Ability::Copy => SuiMoveAbility::Copy,
352                    Ability::Drop => SuiMoveAbility::Drop,
353                    Ability::Key => SuiMoveAbility::Key,
354                    Ability::Store => SuiMoveAbility::Store,
355                })
356                .collect::<Vec<SuiMoveAbility>>(),
357        }
358    }
359}
360
361impl SuiMoveNormalizedType {
362    pub fn new_struct(
363        address: String,
364        module: String,
365        name: String,
366        type_arguments: Vec<SuiMoveNormalizedType>,
367    ) -> Self {
368        SuiMoveNormalizedType::Struct {
369            inner: Box::new(SuiMoveNormalizedStructType {
370                address,
371                module,
372                name,
373                type_arguments,
374            }),
375        }
376    }
377}
378
379#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
380pub enum ObjectValueKind {
381    ByImmutableReference,
382    ByMutableReference,
383    ByValue,
384}
385
386#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
387pub enum MoveFunctionArgType {
388    Pure,
389    Object(ObjectValueKind),
390}
391
392#[serde_as]
393#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq, EnumVariantOrder)]
394#[serde(untagged, rename = "MoveValue")]
395pub enum SuiMoveValue {
396    // u64 and u128 are converted to String to avoid overflow
397    Number(u32),
398    Bool(bool),
399    Address(SuiAddress),
400    Vector(Vec<SuiMoveValue>),
401    String(String),
402    UID { id: ObjectID },
403    Struct(SuiMoveStruct),
404    Option(Box<Option<SuiMoveValue>>),
405    Variant(SuiMoveVariant),
406}
407
408impl SuiMoveValue {
409    /// Extract values from MoveValue without type information in json format
410    pub fn to_json_value(self) -> Value {
411        match self {
412            SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(),
413            SuiMoveValue::Vector(values) => SuiMoveStruct::Runtime(values).to_json_value(),
414            SuiMoveValue::Number(v) => json!(v),
415            SuiMoveValue::Bool(v) => json!(v),
416            SuiMoveValue::Address(v) => json!(v),
417            SuiMoveValue::String(v) => json!(v),
418            SuiMoveValue::UID { id } => json!({ "id": id }),
419            SuiMoveValue::Option(v) => json!(v),
420            SuiMoveValue::Variant(v) => v.to_json_value(),
421        }
422    }
423}
424
425impl Display for SuiMoveValue {
426    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
427        let mut writer = String::new();
428        match self {
429            SuiMoveValue::Number(value) => write!(writer, "{}", value)?,
430            SuiMoveValue::Bool(value) => write!(writer, "{}", value)?,
431            SuiMoveValue::Address(value) => write!(writer, "{}", value)?,
432            SuiMoveValue::String(value) => write!(writer, "{}", value)?,
433            SuiMoveValue::UID { id } => write!(writer, "{id}")?,
434            SuiMoveValue::Struct(value) => write!(writer, "{}", value)?,
435            SuiMoveValue::Option(value) => write!(writer, "{:?}", value)?,
436            SuiMoveValue::Vector(vec) => {
437                write!(
438                    writer,
439                    "{}",
440                    vec.iter().map(|value| format!("{value}")).join(",\n")
441                )?;
442            }
443            SuiMoveValue::Variant(value) => write!(writer, "{}", value)?,
444        }
445        write!(f, "{}", writer.trim_end_matches('\n'))
446    }
447}
448
449impl From<MoveValue> for SuiMoveValue {
450    fn from(value: MoveValue) -> Self {
451        match value {
452            MoveValue::U8(value) => SuiMoveValue::Number(value.into()),
453            MoveValue::U16(value) => SuiMoveValue::Number(value.into()),
454            MoveValue::U32(value) => SuiMoveValue::Number(value),
455            MoveValue::U64(value) => SuiMoveValue::String(format!("{value}")),
456            MoveValue::U128(value) => SuiMoveValue::String(format!("{value}")),
457            MoveValue::U256(value) => SuiMoveValue::String(format!("{value}")),
458            MoveValue::Bool(value) => SuiMoveValue::Bool(value),
459            MoveValue::Vector(values) => {
460                SuiMoveValue::Vector(values.into_iter().map(|value| value.into()).collect())
461            }
462            MoveValue::Struct(value) => {
463                // Best effort Sui core type conversion
464                let MoveStruct { type_, fields } = &value;
465                if let Some(value) = try_convert_type(type_, fields) {
466                    return value;
467                }
468                SuiMoveValue::Struct(value.into())
469            }
470            MoveValue::Signer(value) | MoveValue::Address(value) => {
471                SuiMoveValue::Address(SuiAddress::from(ObjectID::from(value)))
472            }
473            MoveValue::Variant(MoveVariant {
474                type_,
475                variant_name,
476                tag: _,
477                fields,
478            }) => SuiMoveValue::Variant(SuiMoveVariant {
479                type_: type_.clone(),
480                variant: variant_name.to_string(),
481                fields: fields
482                    .into_iter()
483                    .map(|(id, value)| (id.into_string(), value.into()))
484                    .collect::<BTreeMap<_, _>>(),
485            }),
486        }
487    }
488}
489
490fn to_bytearray(value: &[MoveValue]) -> Option<Vec<u8>> {
491    if value.iter().all(|value| matches!(value, MoveValue::U8(_))) {
492        let bytearray = value
493            .iter()
494            .flat_map(|value| {
495                if let MoveValue::U8(u8) = value {
496                    Some(*u8)
497                } else {
498                    None
499                }
500            })
501            .collect::<Vec<_>>();
502        Some(bytearray)
503    } else {
504        None
505    }
506}
507
508#[serde_as]
509#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
510#[serde(rename = "MoveVariant")]
511pub struct SuiMoveVariant {
512    #[schemars(with = "String")]
513    #[serde(rename = "type")]
514    #[serde_as(as = "SuiStructTag")]
515    pub type_: StructTag,
516    pub variant: String,
517    pub fields: BTreeMap<String, SuiMoveValue>,
518}
519
520impl SuiMoveVariant {
521    pub fn to_json_value(self) -> Value {
522        // We only care about values here, assuming type information is known at the client side.
523        let fields = self
524            .fields
525            .into_iter()
526            .map(|(key, value)| (key, value.to_json_value()))
527            .collect::<BTreeMap<_, _>>();
528        json!({
529            "variant": self.variant,
530            "fields": fields,
531        })
532    }
533}
534
535impl Display for SuiMoveVariant {
536    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
537        let mut writer = String::new();
538        let SuiMoveVariant {
539            type_,
540            variant,
541            fields,
542        } = self;
543        writeln!(writer)?;
544        writeln!(writer, "  {}: {type_}", "type".bold().bright_black())?;
545        writeln!(writer, "  {}: {variant}", "variant".bold().bright_black())?;
546        for (name, value) in fields {
547            let value = format!("{}", value);
548            let value = if value.starts_with('\n') {
549                indent(&value, 2)
550            } else {
551                value
552            };
553            writeln!(writer, "  {}: {value}", name.bold().bright_black())?;
554        }
555
556        write!(f, "{}", writer.trim_end_matches('\n'))
557    }
558}
559
560#[serde_as]
561#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq, EnumVariantOrder)]
562#[serde(untagged, rename = "MoveStruct")]
563pub enum SuiMoveStruct {
564    Runtime(Vec<SuiMoveValue>),
565    WithTypes {
566        #[schemars(with = "String")]
567        #[serde(rename = "type")]
568        #[serde_as(as = "SuiStructTag")]
569        type_: StructTag,
570        fields: BTreeMap<String, SuiMoveValue>,
571    },
572    WithFields(BTreeMap<String, SuiMoveValue>),
573}
574
575impl SuiMoveStruct {
576    /// Extract values from MoveStruct without type information in json format
577    pub fn to_json_value(self) -> Value {
578        // Unwrap MoveStructs
579        match self {
580            SuiMoveStruct::Runtime(values) => {
581                let values = values
582                    .into_iter()
583                    .map(|value| value.to_json_value())
584                    .collect::<Vec<_>>();
585                json!(values)
586            }
587            // We only care about values here, assuming struct type information is known at the client side.
588            SuiMoveStruct::WithTypes { type_: _, fields } | SuiMoveStruct::WithFields(fields) => {
589                let fields = fields
590                    .into_iter()
591                    .map(|(key, value)| (key, value.to_json_value()))
592                    .collect::<BTreeMap<_, _>>();
593                json!(fields)
594            }
595        }
596    }
597
598    pub fn field_value(&self, field_name: &str) -> Option<SuiMoveValue> {
599        match self {
600            SuiMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
601            SuiMoveStruct::WithTypes { type_: _, fields } => fields.get(field_name).cloned(),
602            _ => None,
603        }
604    }
605}
606
607impl Display for SuiMoveStruct {
608    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
609        let mut writer = String::new();
610        match self {
611            SuiMoveStruct::Runtime(_) => {}
612            SuiMoveStruct::WithFields(fields) => {
613                for (name, value) in fields {
614                    writeln!(writer, "{}: {value}", name.bold().bright_black())?;
615                }
616            }
617            SuiMoveStruct::WithTypes { type_, fields } => {
618                writeln!(writer)?;
619                writeln!(writer, "  {}: {type_}", "type".bold().bright_black())?;
620                for (name, value) in fields {
621                    let value = format!("{}", value);
622                    let value = if value.starts_with('\n') {
623                        indent(&value, 2)
624                    } else {
625                        value
626                    };
627                    writeln!(writer, "  {}: {value}", name.bold().bright_black())?;
628                }
629            }
630        }
631        write!(f, "{}", writer.trim_end_matches('\n'))
632    }
633}
634
635#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
636pub struct SuiMoveAbort {
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub module_id: Option<String>,
639    #[serde(skip_serializing_if = "Option::is_none")]
640    pub function: Option<String>,
641    #[serde(skip_serializing_if = "Option::is_none")]
642    pub line: Option<u16>,
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub error_code: Option<u64>,
645}
646
647impl SuiMoveAbort {
648    pub fn new(move_location: MoveLocation, code: u64) -> Self {
649        let module = move_location.module.to_canonical_string(true);
650        let (error_code, line) = match ErrorBitset::from_u64(code) {
651            Some(c) => (c.error_code().map(|c| c as u64), c.line_number()),
652            None => (Some(code), None),
653        };
654        Self {
655            module_id: Some(module),
656            function: move_location.function_name.clone(),
657            line,
658            error_code,
659        }
660    }
661}
662
663impl Display for SuiMoveAbort {
664    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
665        let mut writer = String::new();
666        if let Some(module_id) = &self.module_id {
667            writeln!(writer, "Module ID: {module_id}")?;
668        }
669        if let Some(function) = &self.function {
670            writeln!(writer, "Function: {function}")?;
671        }
672        if let Some(line) = &self.line {
673            writeln!(writer, "Line: {line}")?;
674        }
675        if let Some(error_code) = &self.error_code {
676            writeln!(writer, "Error code: {error_code}")?;
677        }
678        write!(f, "{}", writer.trim_end_matches('\n'))
679    }
680}
681
682fn indent<T: Display>(d: &T, indent: usize) -> String {
683    d.to_string()
684        .lines()
685        .map(|line| format!("{:indent$}{}", "", line))
686        .join("\n")
687}
688
689fn try_convert_type(type_: &StructTag, fields: &[(Identifier, MoveValue)]) -> Option<SuiMoveValue> {
690    let struct_name = format!(
691        "0x{}::{}::{}",
692        type_.address.short_str_lossless(),
693        type_.module,
694        type_.name
695    );
696    let mut values = fields
697        .iter()
698        .map(|(id, value)| (id.to_string(), value))
699        .collect::<BTreeMap<_, _>>();
700    match struct_name.as_str() {
701        "0x1::string::String" | "0x1::ascii::String" => {
702            if let Some(MoveValue::Vector(bytes)) = values.remove("bytes") {
703                return to_bytearray(bytes)
704                    .and_then(|bytes| String::from_utf8(bytes).ok())
705                    .map(SuiMoveValue::String);
706            }
707        }
708        "0x2::url::Url" => {
709            return values.remove("url").cloned().map(SuiMoveValue::from);
710        }
711        "0x2::object::ID" => {
712            return values.remove("bytes").cloned().map(SuiMoveValue::from);
713        }
714        "0x2::object::UID" => {
715            let id = values.remove("id").cloned().map(SuiMoveValue::from);
716            if let Some(SuiMoveValue::Address(address)) = id {
717                return Some(SuiMoveValue::UID {
718                    id: ObjectID::from(address),
719                });
720            }
721        }
722        "0x2::balance::Balance" => {
723            return values.remove("value").cloned().map(SuiMoveValue::from);
724        }
725        "0x1::option::Option" => {
726            if let Some(MoveValue::Vector(values)) = values.remove("vec") {
727                return Some(SuiMoveValue::Option(Box::new(
728                    // in Move option is modeled as vec of 1 element
729                    values.first().cloned().map(SuiMoveValue::from),
730                )));
731            }
732        }
733        _ => return None,
734    }
735    warn!(
736        fields =? fields,
737        "Failed to convert {struct_name} to SuiMoveValue"
738    );
739    None
740}
741
742impl From<MoveStruct> for SuiMoveStruct {
743    fn from(move_struct: MoveStruct) -> Self {
744        SuiMoveStruct::WithTypes {
745            type_: move_struct.type_,
746            fields: move_struct
747                .fields
748                .into_iter()
749                .map(|(id, value)| (id.into_string(), value.into()))
750                .collect(),
751        }
752    }
753}
754
755#[test]
756fn enum_size() {
757    assert_eq!(std::mem::size_of::<SuiMoveNormalizedType>(), 16);
758}