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