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