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    str::FromStr,
9};
10
11use colored::Colorize;
12use iota_macros::EnumVariantOrder;
13use iota_sdk_types::{Identifier, ObjectId, StructTag};
14use iota_types::{
15    base_types::IotaAddress,
16    error::{IotaError, UserInputError},
17    iota_sdk_types_conversions::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    IotaAddress as IotaAddressSchema, 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: 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: 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                .iter()
279                .map(|f| IotaMoveNormalizedField::from(&**f))
280                .collect::<Vec<IotaMoveNormalizedField>>(),
281        }
282    }
283}
284
285impl<S: ToString> From<&NormalizedEnum<S>> for IotaMoveNormalizedEnum {
286    fn from(value: &NormalizedEnum<S>) -> Self {
287        Self {
288            abilities: value.abilities.into(),
289            type_parameters: value
290                .type_parameters
291                .iter()
292                .copied()
293                .map(Into::into)
294                .collect(),
295            variants: value
296                .variants
297                .iter()
298                .map(|variant| {
299                    (
300                        variant.name.to_string(),
301                        variant.fields.iter().map(Into::into).collect(),
302                    )
303                })
304                .collect(),
305        }
306    }
307}
308
309impl From<DatatypeTyParameter> for IotaMoveStructTypeParameter {
310    fn from(type_parameter: DatatypeTyParameter) -> Self {
311        Self {
312            constraints: type_parameter.constraints.into(),
313            is_phantom: type_parameter.is_phantom,
314        }
315    }
316}
317
318impl<S: ToString> From<&NormalizedField<S>> for IotaMoveNormalizedField {
319    fn from(normalized_field: &NormalizedField<S>) -> Self {
320        Self {
321            name: normalized_field.name.to_string(),
322            type_: IotaMoveNormalizedType::from(&normalized_field.type_),
323        }
324    }
325}
326
327impl<S: ToString> From<&NormalizedType<S>> for IotaMoveNormalizedType {
328    fn from(type_: &NormalizedType<S>) -> Self {
329        match type_ {
330            NormalizedType::Bool => IotaMoveNormalizedType::Bool,
331            NormalizedType::U8 => IotaMoveNormalizedType::U8,
332            NormalizedType::U16 => IotaMoveNormalizedType::U16,
333            NormalizedType::U32 => IotaMoveNormalizedType::U32,
334            NormalizedType::U64 => IotaMoveNormalizedType::U64,
335            NormalizedType::U128 => IotaMoveNormalizedType::U128,
336            NormalizedType::U256 => IotaMoveNormalizedType::U256,
337            NormalizedType::Address => IotaMoveNormalizedType::Address,
338            NormalizedType::Signer => IotaMoveNormalizedType::Signer,
339            NormalizedType::Datatype(dt) => {
340                let normalized::Datatype {
341                    module,
342                    name,
343                    type_arguments,
344                } = &**dt;
345                IotaMoveNormalizedType::new_struct(
346                    module.address.to_hex_literal(),
347                    module.name.to_string(),
348                    name.to_string(),
349                    type_arguments
350                        .iter()
351                        .map(IotaMoveNormalizedType::from)
352                        .collect::<Vec<IotaMoveNormalizedType>>(),
353                )
354            }
355            NormalizedType::Vector(v) => {
356                IotaMoveNormalizedType::Vector(Box::new(IotaMoveNormalizedType::from(&**v)))
357            }
358            NormalizedType::TypeParameter(t) => IotaMoveNormalizedType::TypeParameter(*t),
359            NormalizedType::Reference(false, r) => {
360                IotaMoveNormalizedType::Reference(Box::new(IotaMoveNormalizedType::from(&**r)))
361            }
362            NormalizedType::Reference(true, mr) => IotaMoveNormalizedType::MutableReference(
363                Box::new(IotaMoveNormalizedType::from(&**mr)),
364            ),
365        }
366    }
367}
368
369impl From<AbilitySet> for IotaMoveAbilitySet {
370    fn from(set: AbilitySet) -> IotaMoveAbilitySet {
371        Self {
372            abilities: set
373                .into_iter()
374                .map(|a| match a {
375                    Ability::Copy => IotaMoveAbility::Copy,
376                    Ability::Drop => IotaMoveAbility::Drop,
377                    Ability::Key => IotaMoveAbility::Key,
378                    Ability::Store => IotaMoveAbility::Store,
379                })
380                .collect::<Vec<IotaMoveAbility>>(),
381        }
382    }
383}
384
385impl IotaMoveNormalizedType {
386    pub fn new_struct(
387        address: String,
388        module: String,
389        name: String,
390        type_arguments: Vec<IotaMoveNormalizedType>,
391    ) -> Self {
392        IotaMoveNormalizedType::Struct {
393            inner: Box::new(IotaMoveNormalizedStructType {
394                address,
395                module,
396                name,
397                type_arguments,
398            }),
399        }
400    }
401}
402
403#[derive(Serialize, Deserialize, Copy, Clone, Debug, JsonSchema, PartialEq)]
404pub enum ObjectValueKind {
405    ByImmutableReference,
406    ByMutableReference,
407    ByValue,
408}
409
410#[derive(Serialize, Deserialize, Copy, Clone, Debug, JsonSchema, PartialEq)]
411pub enum MoveFunctionArgType {
412    Pure,
413    Object(ObjectValueKind),
414}
415
416#[serde_as]
417#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq, EnumVariantOrder)]
418#[serde(untagged, rename = "MoveValue")]
419pub enum IotaMoveValue {
420    // u64 and u128 are converted to String to avoid overflow
421    Number(u32),
422    Bool(bool),
423    Address(
424        #[serde_as(as = "IotaAddressSchema")]
425        #[schemars(with = "IotaAddressSchema")]
426        IotaAddress,
427    ),
428    Vector(Vec<IotaMoveValue>),
429    String(String),
430    UID {
431        #[serde_as(as = "ObjectIdSchema")]
432        #[schemars(with = "ObjectIdSchema")]
433        id: ObjectId,
434    },
435    Struct(IotaMoveStruct),
436    Option(Box<Option<IotaMoveValue>>),
437    Variant(IotaMoveVariant),
438}
439
440impl IotaMoveValue {
441    /// Extract values from MoveValue without type information in json format
442    pub fn to_json_value(self) -> Value {
443        match self {
444            IotaMoveValue::Struct(move_struct) => move_struct.to_json_value(),
445            IotaMoveValue::Vector(values) => IotaMoveStruct::Runtime(values).to_json_value(),
446            IotaMoveValue::Number(v) => json!(v),
447            IotaMoveValue::Bool(v) => json!(v),
448            IotaMoveValue::Address(v) => json!(v),
449            IotaMoveValue::String(v) => json!(v),
450            IotaMoveValue::UID { id } => json!({ "id": id }),
451            IotaMoveValue::Option(v) => json!(v),
452            IotaMoveValue::Variant(v) => v.to_json_value(),
453        }
454    }
455}
456
457impl Display for IotaMoveValue {
458    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
459        let mut writer = String::new();
460        match self {
461            IotaMoveValue::Number(value) => write!(writer, "{value}")?,
462            IotaMoveValue::Bool(value) => write!(writer, "{value}")?,
463            IotaMoveValue::Address(value) => write!(writer, "{value}")?,
464            IotaMoveValue::String(value) => write!(writer, "{value}")?,
465            IotaMoveValue::UID { id } => write!(writer, "{id}")?,
466            IotaMoveValue::Struct(value) => write!(writer, "{value}")?,
467            IotaMoveValue::Option(value) => write!(writer, "{value:?}")?,
468            IotaMoveValue::Vector(vec) => {
469                write!(
470                    writer,
471                    "{}",
472                    vec.iter().map(|value| format!("{value}")).join(",\n")
473                )?;
474            }
475            IotaMoveValue::Variant(value) => write!(writer, "{value}")?,
476        }
477        write!(f, "{}", writer.trim_end_matches('\n'))
478    }
479}
480
481impl From<MoveValue> for IotaMoveValue {
482    fn from(value: MoveValue) -> Self {
483        match value {
484            MoveValue::U8(value) => IotaMoveValue::Number(value.into()),
485            MoveValue::U16(value) => IotaMoveValue::Number(value.into()),
486            MoveValue::U32(value) => IotaMoveValue::Number(value),
487            MoveValue::U64(value) => IotaMoveValue::String(format!("{value}")),
488            MoveValue::U128(value) => IotaMoveValue::String(format!("{value}")),
489            MoveValue::U256(value) => IotaMoveValue::String(format!("{value}")),
490            MoveValue::Bool(value) => IotaMoveValue::Bool(value),
491            MoveValue::Vector(values) => {
492                IotaMoveValue::Vector(values.into_iter().map(|value| value.into()).collect())
493            }
494            MoveValue::Struct(value) => {
495                // Best effort IOTA core type conversion
496                let MoveStruct { type_, fields } = &value;
497                let type_ = struct_tag_core_to_sdk(type_);
498                let fields = fields
499                    .iter()
500                    .map(|(id, value)| (Identifier::new_unchecked(id.as_str()), value.clone()))
501                    .collect::<Vec<_>>();
502                if let Some(value) = try_convert_type(&type_, &fields) {
503                    return value;
504                }
505                IotaMoveValue::Struct(value.into())
506            }
507            MoveValue::Signer(value) | MoveValue::Address(value) => {
508                IotaMoveValue::Address(IotaAddress::new(value.into_bytes()))
509            }
510            MoveValue::Variant(MoveVariant {
511                type_,
512                variant_name,
513                tag: _,
514                fields,
515            }) => IotaMoveValue::Variant(IotaMoveVariant {
516                type_: struct_tag_core_to_sdk(&type_),
517                variant: variant_name.to_string(),
518                fields: fields
519                    .into_iter()
520                    .map(|(id, value)| (id.into_string(), value.into()))
521                    .collect::<BTreeMap<_, _>>(),
522            }),
523        }
524    }
525}
526
527fn to_bytearray(value: &[MoveValue]) -> Option<Vec<u8>> {
528    if value.iter().all(|value| matches!(value, MoveValue::U8(_))) {
529        let bytearray = value
530            .iter()
531            .flat_map(|value| {
532                if let MoveValue::U8(u8) = value {
533                    Some(*u8)
534                } else {
535                    None
536                }
537            })
538            .collect::<Vec<_>>();
539        Some(bytearray)
540    } else {
541        None
542    }
543}
544
545#[serde_as]
546#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
547#[serde(rename = "MoveVariant")]
548pub struct IotaMoveVariant {
549    #[serde(rename = "type")]
550    #[schemars(with = "StructTagSchema")]
551    #[serde_as(as = "StructTagSchema")]
552    pub type_: StructTag,
553    pub variant: String,
554    pub fields: BTreeMap<String, IotaMoveValue>,
555}
556
557impl IotaMoveVariant {
558    pub fn to_json_value(self) -> Value {
559        // We only care about values here, assuming type information is known at the
560        // client side.
561        let fields = self
562            .fields
563            .into_iter()
564            .map(|(key, value)| (key, value.to_json_value()))
565            .collect::<BTreeMap<_, _>>();
566        json!({
567            "variant": self.variant,
568            "fields": fields,
569        })
570    }
571}
572
573impl Display for IotaMoveVariant {
574    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
575        let mut writer = String::new();
576        let IotaMoveVariant {
577            type_,
578            variant,
579            fields,
580        } = self;
581        writeln!(writer)?;
582        writeln!(writer, "  {}: {type_}", "type".bold().bright_black())?;
583        writeln!(writer, "  {}: {variant}", "variant".bold().bright_black())?;
584        for (name, value) in fields {
585            let value = format!("{value}");
586            let value = if value.starts_with('\n') {
587                indent(&value, 2)
588            } else {
589                value
590            };
591            writeln!(writer, "  {}: {value}", name.bold().bright_black())?;
592        }
593
594        write!(f, "{}", writer.trim_end_matches('\n'))
595    }
596}
597
598#[serde_as]
599#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq, EnumVariantOrder)]
600#[serde(untagged, rename = "MoveStruct")]
601pub enum IotaMoveStruct {
602    Runtime(Vec<IotaMoveValue>),
603    WithTypes {
604        #[serde(rename = "type")]
605        #[schemars(with = "StructTagSchema")]
606        #[serde_as(as = "StructTagSchema")]
607        type_: StructTag,
608        fields: BTreeMap<String, IotaMoveValue>,
609    },
610    WithFields(BTreeMap<String, IotaMoveValue>),
611}
612
613impl IotaMoveStruct {
614    /// Extract values from MoveStruct without type information in json format
615    pub fn to_json_value(self) -> Value {
616        // Unwrap MoveStructs
617        match self {
618            IotaMoveStruct::Runtime(values) => {
619                let values = values
620                    .into_iter()
621                    .map(|value| value.to_json_value())
622                    .collect::<Vec<_>>();
623                json!(values)
624            }
625            // We only care about values here, assuming struct type information is known at the
626            // client side.
627            IotaMoveStruct::WithTypes { type_: _, fields } | IotaMoveStruct::WithFields(fields) => {
628                let fields = fields
629                    .into_iter()
630                    .map(|(key, value)| (key, value.to_json_value()))
631                    .collect::<BTreeMap<_, _>>();
632                json!(fields)
633            }
634        }
635    }
636
637    pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<IotaMoveValue> {
638        match self {
639            IotaMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
640            IotaMoveStruct::WithTypes { type_: _, fields } => fields.get(field_name).cloned(),
641            _ => None,
642        }
643    }
644}
645
646impl Display for IotaMoveStruct {
647    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
648        let mut writer = String::new();
649        match self {
650            IotaMoveStruct::Runtime(_) => {}
651            IotaMoveStruct::WithFields(fields) => {
652                for (name, value) in fields {
653                    writeln!(writer, "{}: {value}", name.bold().bright_black())?;
654                }
655            }
656            IotaMoveStruct::WithTypes { type_, fields } => {
657                writeln!(writer)?;
658                writeln!(writer, "  {}: {type_}", "type".bold().bright_black())?;
659                for (name, value) in fields {
660                    let value = format!("{value}");
661                    let value = if value.starts_with('\n') {
662                        indent(&value, 2)
663                    } else {
664                        value
665                    };
666                    writeln!(writer, "  {}: {value}", name.bold().bright_black())?;
667                }
668            }
669        }
670        write!(f, "{}", writer.trim_end_matches('\n'))
671    }
672}
673
674fn indent<T: Display>(d: &T, indent: usize) -> String {
675    d.to_string()
676        .lines()
677        .map(|line| format!("{:indent$}{line}", ""))
678        .join("\n")
679}
680
681fn try_convert_type(
682    type_: &StructTag,
683    fields: &[(Identifier, MoveValue)],
684) -> Option<IotaMoveValue> {
685    let struct_name = format!(
686        "{}::{}::{}",
687        type_.address().to_short_hex(),
688        type_.module(),
689        type_.name()
690    );
691    let mut values = fields
692        .iter()
693        .map(|(id, value)| (id.to_string(), value))
694        .collect::<BTreeMap<_, _>>();
695    match struct_name.as_str() {
696        "0x1::string::String" | "0x1::ascii::String" => {
697            if let Some(MoveValue::Vector(bytes)) = values.remove("bytes") {
698                return to_bytearray(bytes)
699                    .and_then(|bytes| String::from_utf8(bytes).ok())
700                    .map(IotaMoveValue::String);
701            }
702        }
703        "0x2::url::Url" => {
704            return values.remove("url").cloned().map(IotaMoveValue::from);
705        }
706        "0x2::object::ID" => {
707            return values.remove("bytes").cloned().map(IotaMoveValue::from);
708        }
709        "0x2::object::UID" => {
710            let id = values.remove("id").cloned().map(IotaMoveValue::from);
711            if let Some(IotaMoveValue::Address(address)) = id {
712                return Some(IotaMoveValue::UID {
713                    id: ObjectId::from(address),
714                });
715            }
716        }
717        "0x2::balance::Balance" => {
718            return values.remove("value").cloned().map(IotaMoveValue::from);
719        }
720        "0x1::option::Option" => {
721            if let Some(MoveValue::Vector(values)) = values.remove("vec") {
722                return Some(IotaMoveValue::Option(Box::new(
723                    // in Move option is modeled as vec of 1 element
724                    values.first().cloned().map(IotaMoveValue::from),
725                )));
726            }
727        }
728        _ => return None,
729    }
730    warn!(
731        fields =? fields,
732        "failed to convert {struct_name} to IotaMoveValue"
733    );
734    None
735}
736
737impl From<MoveStruct> for IotaMoveStruct {
738    fn from(move_struct: MoveStruct) -> Self {
739        IotaMoveStruct::WithTypes {
740            type_: struct_tag_core_to_sdk(&move_struct.type_),
741            fields: move_struct
742                .fields
743                .into_iter()
744                .map(|(id, value)| (id.into_string(), value.into()))
745                .collect(),
746        }
747    }
748}
749
750#[test]
751fn enum_size() {
752    assert_eq!(std::mem::size_of::<IotaMoveNormalizedType>(), 16);
753}