1use 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 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 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 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 = "DisplayFromStr")]
258 type_: StructTag,
259 fields: BTreeMap<String, SuiMoveValue>,
260 },
261 WithFields(BTreeMap<String, SuiMoveValue>),
262}
263
264impl SuiMoveStruct {
265 pub fn to_json_value(self) -> Value {
267 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 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}