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