Skip to main content

iota_json_rpc_types/
iota_move.rs

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