sui_jsonrpc/msgs/
sui_move.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::BTreeMap;
5use std::fmt;
6use std::fmt::{Display, Formatter, Write};
7
8use af_sui_types::{Address as SuiAddress, ObjectId, StructTag};
9use colored::Colorize;
10use itertools::Itertools;
11use serde::{Deserialize, Serialize};
12use serde_json::{Value, json};
13use serde_with::{DisplayFromStr, serde_as};
14
15pub type SuiMoveTypeParameterIndex = u16;
16
17#[derive(Serialize, Deserialize, Debug)]
18pub enum SuiMoveAbility {
19    Copy,
20    Drop,
21    Store,
22    Key,
23}
24
25#[derive(Serialize, Deserialize, Debug)]
26pub struct SuiMoveAbilitySet {
27    pub abilities: Vec<SuiMoveAbility>,
28}
29
30#[derive(Serialize, Deserialize, Debug)]
31pub enum SuiMoveVisibility {
32    Private,
33    Public,
34    Friend,
35}
36
37#[derive(Serialize, Deserialize, Debug)]
38#[serde(rename_all = "camelCase")]
39pub struct SuiMoveStructTypeParameter {
40    pub constraints: SuiMoveAbilitySet,
41    pub is_phantom: bool,
42}
43
44#[derive(Serialize, Deserialize, Debug)]
45pub struct SuiMoveNormalizedField {
46    pub name: String,
47    #[serde(rename = "type")]
48    pub type_: SuiMoveNormalizedType,
49}
50
51#[derive(Serialize, Deserialize, Debug)]
52#[serde(rename_all = "camelCase")]
53pub struct SuiMoveNormalizedStruct {
54    pub abilities: SuiMoveAbilitySet,
55    pub type_parameters: Vec<SuiMoveStructTypeParameter>,
56    pub fields: Vec<SuiMoveNormalizedField>,
57}
58
59#[derive(Serialize, Deserialize, Debug)]
60#[serde(rename_all = "camelCase")]
61pub struct SuiMoveNormalizedEnum {
62    pub abilities: SuiMoveAbilitySet,
63    pub type_parameters: Vec<SuiMoveStructTypeParameter>,
64    pub variants: BTreeMap<String, Vec<SuiMoveNormalizedField>>,
65}
66
67#[derive(Serialize, Deserialize, Debug)]
68pub enum SuiMoveNormalizedType {
69    Bool,
70    U8,
71    U16,
72    U32,
73    U64,
74    U128,
75    U256,
76    Address,
77    Signer,
78    #[serde(rename_all = "camelCase")]
79    Struct {
80        address: String,
81        module: String,
82        name: String,
83        type_arguments: Vec<SuiMoveNormalizedType>,
84    },
85    Vector(Box<SuiMoveNormalizedType>),
86    TypeParameter(SuiMoveTypeParameterIndex),
87    Reference(Box<SuiMoveNormalizedType>),
88    MutableReference(Box<SuiMoveNormalizedType>),
89}
90
91#[derive(Serialize, Deserialize, Debug)]
92#[serde(rename_all = "camelCase")]
93pub struct SuiMoveNormalizedFunction {
94    pub visibility: SuiMoveVisibility,
95    pub is_entry: bool,
96    pub type_parameters: Vec<SuiMoveAbilitySet>,
97    pub parameters: Vec<SuiMoveNormalizedType>,
98    pub return_: Vec<SuiMoveNormalizedType>,
99}
100
101#[derive(Serialize, Deserialize, Debug)]
102pub struct SuiMoveModuleId {
103    address: String,
104    name: String,
105}
106
107#[derive(Serialize, Deserialize, Debug)]
108#[serde(rename_all = "camelCase")]
109pub struct SuiMoveNormalizedModule {
110    pub file_format_version: u32,
111    pub address: String,
112    pub name: String,
113    pub friends: Vec<SuiMoveModuleId>,
114    pub structs: BTreeMap<String, SuiMoveNormalizedStruct>,
115    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
116    pub enums: BTreeMap<String, SuiMoveNormalizedEnum>,
117    pub exposed_functions: BTreeMap<String, SuiMoveNormalizedFunction>,
118}
119
120impl PartialEq for SuiMoveNormalizedModule {
121    fn eq(&self, other: &Self) -> bool {
122        self.file_format_version == other.file_format_version
123            && self.address == other.address
124            && self.name == other.name
125    }
126}
127
128#[derive(Serialize, Deserialize, Debug)]
129pub enum ObjectValueKind {
130    ByImmutableReference,
131    ByMutableReference,
132    ByValue,
133}
134
135#[derive(Serialize, Deserialize, Debug)]
136pub enum MoveFunctionArgType {
137    Pure,
138    Object(ObjectValueKind),
139}
140
141#[serde_as]
142#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
143#[serde(untagged, rename = "MoveValue")]
144pub enum SuiMoveValue {
145    // u64 and u128 are converted to String to avoid overflow
146    Number(u32),
147    Bool(bool),
148    Address(SuiAddress),
149    Vector(Vec<SuiMoveValue>),
150    String(String),
151    UID { id: ObjectId },
152    Struct(SuiMoveStruct),
153    Option(Box<Option<SuiMoveValue>>),
154    Variant(SuiMoveVariant),
155}
156
157impl SuiMoveValue {
158    /// Extract values from MoveValue without type information in json format
159    pub fn to_json_value(self) -> Value {
160        match self {
161            SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(),
162            SuiMoveValue::Vector(values) => SuiMoveStruct::Runtime(values).to_json_value(),
163            SuiMoveValue::Number(v) => json!(v),
164            SuiMoveValue::Bool(v) => json!(v),
165            SuiMoveValue::Address(v) => json!(v),
166            SuiMoveValue::String(v) => json!(v),
167            SuiMoveValue::UID { id } => json!({ "id": id }),
168            SuiMoveValue::Option(v) => json!(v),
169            SuiMoveValue::Variant(v) => v.to_json_value(),
170        }
171    }
172}
173
174impl Display for SuiMoveValue {
175    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
176        let mut writer = String::new();
177        match self {
178            SuiMoveValue::Number(value) => write!(writer, "{}", value)?,
179            SuiMoveValue::Bool(value) => write!(writer, "{}", value)?,
180            SuiMoveValue::Address(value) => write!(writer, "{}", value)?,
181            SuiMoveValue::String(value) => write!(writer, "{}", value)?,
182            SuiMoveValue::UID { id } => write!(writer, "{id}")?,
183            SuiMoveValue::Struct(value) => write!(writer, "{}", value)?,
184            SuiMoveValue::Option(value) => write!(writer, "{:?}", value)?,
185            SuiMoveValue::Vector(vec) => {
186                write!(
187                    writer,
188                    "{}",
189                    vec.iter().map(|value| format!("{value}")).join(",\n")
190                )?;
191            }
192            SuiMoveValue::Variant(value) => write!(writer, "{}", value)?,
193        }
194        write!(f, "{}", writer.trim_end_matches('\n'))
195    }
196}
197
198#[serde_as]
199#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
200#[serde(rename = "MoveVariant")]
201pub struct SuiMoveVariant {
202    #[serde(rename = "type")]
203    #[serde_as(as = "DisplayFromStr")]
204    pub type_: StructTag,
205    pub variant: String,
206    pub fields: BTreeMap<String, SuiMoveValue>,
207}
208
209impl SuiMoveVariant {
210    pub fn to_json_value(self) -> Value {
211        // We only care about values here, assuming type information is known at the client side.
212        let fields = self
213            .fields
214            .into_iter()
215            .map(|(key, value)| (key, value.to_json_value()))
216            .collect::<BTreeMap<_, _>>();
217        json!({
218            "variant": self.variant,
219            "fields": fields,
220        })
221    }
222}
223
224impl Display for SuiMoveVariant {
225    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
226        let mut writer = String::new();
227        let SuiMoveVariant {
228            type_,
229            variant,
230            fields,
231        } = self;
232        writeln!(writer)?;
233        writeln!(writer, "  {}: {type_}", "type".bold().bright_black())?;
234        writeln!(writer, "  {}: {variant}", "variant".bold().bright_black())?;
235        for (name, value) in fields {
236            let value = format!("{}", value);
237            let value = if value.starts_with('\n') {
238                indent(&value, 2)
239            } else {
240                value
241            };
242            writeln!(writer, "  {}: {value}", name.bold().bright_black())?;
243        }
244
245        write!(f, "{}", writer.trim_end_matches('\n'))
246    }
247}
248
249#[serde_as]
250#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
251#[serde(untagged, rename = "MoveStruct")]
252pub enum SuiMoveStruct {
253    Runtime(Vec<SuiMoveValue>),
254    WithTypes {
255        #[serde(rename = "type")]
256        // #[serde_as(as = "SuiStructTag")]
257        #[serde_as(as = "DisplayFromStr")]
258        type_: StructTag,
259        fields: BTreeMap<String, SuiMoveValue>,
260    },
261    WithFields(BTreeMap<String, SuiMoveValue>),
262}
263
264impl SuiMoveStruct {
265    /// Extract values from MoveStruct without type information in json format
266    pub fn to_json_value(self) -> Value {
267        // Unwrap MoveStructs
268        match self {
269            SuiMoveStruct::Runtime(values) => {
270                let values = values
271                    .into_iter()
272                    .map(|value| value.to_json_value())
273                    .collect::<Vec<_>>();
274                json!(values)
275            }
276            // We only care about values here, assuming struct type information is known at the client side.
277            SuiMoveStruct::WithTypes { type_: _, fields } | SuiMoveStruct::WithFields(fields) => {
278                let fields = fields
279                    .into_iter()
280                    .map(|(key, value)| (key, value.to_json_value()))
281                    .collect::<BTreeMap<_, _>>();
282                json!(fields)
283            }
284        }
285    }
286
287    pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<SuiMoveValue> {
288        match self {
289            SuiMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
290            SuiMoveStruct::WithTypes { type_: _, fields } => fields.get(field_name).cloned(),
291            _ => None,
292        }
293    }
294}
295
296impl Display for SuiMoveStruct {
297    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
298        let mut writer = String::new();
299        match self {
300            SuiMoveStruct::Runtime(_) => {}
301            SuiMoveStruct::WithFields(fields) => {
302                for (name, value) in fields {
303                    writeln!(writer, "{}: {value}", name.bold().bright_black())?;
304                }
305            }
306            SuiMoveStruct::WithTypes { type_, fields } => {
307                writeln!(writer)?;
308                writeln!(writer, "  {}: {type_}", "type".bold().bright_black())?;
309                for (name, value) in fields {
310                    let value = format!("{}", value);
311                    let value = if value.starts_with('\n') {
312                        indent(&value, 2)
313                    } else {
314                        value
315                    };
316                    writeln!(writer, "  {}: {value}", name.bold().bright_black())?;
317                }
318            }
319        }
320        write!(f, "{}", writer.trim_end_matches('\n'))
321    }
322}
323
324fn indent<T: Display>(d: &T, indent: usize) -> String {
325    d.to_string()
326        .lines()
327        .map(|line| format!("{:indent$}{}", "", line))
328        .join("\n")
329}