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