use std::collections::BTreeMap;
use std::fmt;
use std::fmt::{Display, Formatter, Write};
use colored::Colorize;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use serde_with::{DisplayFromStr, serde_as};
use sui_sdk_types::{Address, StructTag};
pub type SuiMoveTypeParameterIndex = u16;
#[derive(Serialize, Deserialize, Debug)]
pub enum SuiMoveAbility {
Copy,
Drop,
Store,
Key,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SuiMoveAbilitySet {
pub abilities: Vec<SuiMoveAbility>,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum SuiMoveVisibility {
Private,
Public,
Friend,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SuiMoveStructTypeParameter {
pub constraints: SuiMoveAbilitySet,
pub is_phantom: bool,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SuiMoveNormalizedField {
pub name: String,
#[serde(rename = "type")]
pub type_: SuiMoveNormalizedType,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SuiMoveNormalizedStruct {
pub abilities: SuiMoveAbilitySet,
pub type_parameters: Vec<SuiMoveStructTypeParameter>,
pub fields: Vec<SuiMoveNormalizedField>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SuiMoveNormalizedEnum {
pub abilities: SuiMoveAbilitySet,
pub type_parameters: Vec<SuiMoveStructTypeParameter>,
pub variants: BTreeMap<String, Vec<SuiMoveNormalizedField>>,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum SuiMoveNormalizedType {
Bool,
U8,
U16,
U32,
U64,
U128,
U256,
Address,
Signer,
#[serde(rename_all = "camelCase")]
Struct {
address: String,
module: String,
name: String,
type_arguments: Vec<SuiMoveNormalizedType>,
},
Vector(Box<SuiMoveNormalizedType>),
TypeParameter(SuiMoveTypeParameterIndex),
Reference(Box<SuiMoveNormalizedType>),
MutableReference(Box<SuiMoveNormalizedType>),
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SuiMoveNormalizedFunction {
pub visibility: SuiMoveVisibility,
pub is_entry: bool,
pub type_parameters: Vec<SuiMoveAbilitySet>,
pub parameters: Vec<SuiMoveNormalizedType>,
pub return_: Vec<SuiMoveNormalizedType>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SuiMoveModuleId {
address: String,
name: String,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SuiMoveNormalizedModule {
pub file_format_version: u32,
pub address: String,
pub name: String,
pub friends: Vec<SuiMoveModuleId>,
pub structs: BTreeMap<String, SuiMoveNormalizedStruct>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub enums: BTreeMap<String, SuiMoveNormalizedEnum>,
pub exposed_functions: BTreeMap<String, SuiMoveNormalizedFunction>,
}
impl PartialEq for SuiMoveNormalizedModule {
fn eq(&self, other: &Self) -> bool {
self.file_format_version == other.file_format_version
&& self.address == other.address
&& self.name == other.name
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum ObjectValueKind {
ByImmutableReference,
ByMutableReference,
ByValue,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum MoveFunctionArgType {
Pure,
Object(ObjectValueKind),
}
#[serde_as]
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
#[serde(untagged, rename = "MoveValue")]
pub enum SuiMoveValue {
Number(u32),
Bool(bool),
Address(Address),
Vector(Vec<SuiMoveValue>),
String(String),
UID { id: Address },
Struct(SuiMoveStruct),
Option(Box<Option<SuiMoveValue>>),
Variant(SuiMoveVariant),
}
impl SuiMoveValue {
pub fn to_json_value(self) -> Value {
match self {
SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(),
SuiMoveValue::Vector(values) => SuiMoveStruct::Runtime(values).to_json_value(),
SuiMoveValue::Number(v) => json!(v),
SuiMoveValue::Bool(v) => json!(v),
SuiMoveValue::Address(v) => json!(v),
SuiMoveValue::String(v) => json!(v),
SuiMoveValue::UID { id } => json!({ "id": id }),
SuiMoveValue::Option(v) => json!(v),
SuiMoveValue::Variant(v) => v.to_json_value(),
}
}
}
impl Display for SuiMoveValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut writer = String::new();
match self {
SuiMoveValue::Number(value) => write!(writer, "{}", value)?,
SuiMoveValue::Bool(value) => write!(writer, "{}", value)?,
SuiMoveValue::Address(value) => write!(writer, "{}", value)?,
SuiMoveValue::String(value) => write!(writer, "{}", value)?,
SuiMoveValue::UID { id } => write!(writer, "{id}")?,
SuiMoveValue::Struct(value) => write!(writer, "{}", value)?,
SuiMoveValue::Option(value) => write!(writer, "{:?}", value)?,
SuiMoveValue::Vector(vec) => {
write!(
writer,
"{}",
vec.iter().map(|value| format!("{value}")).join(",\n")
)?;
}
SuiMoveValue::Variant(value) => write!(writer, "{}", value)?,
}
write!(f, "{}", writer.trim_end_matches('\n'))
}
}
#[serde_as]
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
#[serde(rename = "MoveVariant")]
pub struct SuiMoveVariant {
#[serde(rename = "type")]
#[serde_as(as = "DisplayFromStr")]
pub type_: StructTag,
pub variant: String,
pub fields: BTreeMap<String, SuiMoveValue>,
}
impl SuiMoveVariant {
pub fn to_json_value(self) -> Value {
let fields = self
.fields
.into_iter()
.map(|(key, value)| (key, value.to_json_value()))
.collect::<BTreeMap<_, _>>();
json!({
"variant": self.variant,
"fields": fields,
})
}
}
impl Display for SuiMoveVariant {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut writer = String::new();
let SuiMoveVariant {
type_,
variant,
fields,
} = self;
writeln!(writer)?;
writeln!(writer, " {}: {type_}", "type".bold().bright_black())?;
writeln!(writer, " {}: {variant}", "variant".bold().bright_black())?;
for (name, value) in fields {
let value = format!("{}", value);
let value = if value.starts_with('\n') {
indent(&value, 2)
} else {
value
};
writeln!(writer, " {}: {value}", name.bold().bright_black())?;
}
write!(f, "{}", writer.trim_end_matches('\n'))
}
}
#[serde_as]
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
#[serde(untagged, rename = "MoveStruct")]
pub enum SuiMoveStruct {
Runtime(Vec<SuiMoveValue>),
WithTypes {
#[serde(rename = "type")]
#[serde_as(as = "DisplayFromStr")]
type_: StructTag,
fields: BTreeMap<String, SuiMoveValue>,
},
WithFields(BTreeMap<String, SuiMoveValue>),
}
impl SuiMoveStruct {
pub fn to_json_value(self) -> Value {
match self {
SuiMoveStruct::Runtime(values) => {
let values = values
.into_iter()
.map(|value| value.to_json_value())
.collect::<Vec<_>>();
json!(values)
}
SuiMoveStruct::WithTypes { type_: _, fields } | SuiMoveStruct::WithFields(fields) => {
let fields = fields
.into_iter()
.map(|(key, value)| (key, value.to_json_value()))
.collect::<BTreeMap<_, _>>();
json!(fields)
}
}
}
pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<SuiMoveValue> {
match self {
SuiMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
SuiMoveStruct::WithTypes { type_: _, fields } => fields.get(field_name).cloned(),
_ => None,
}
}
}
impl Display for SuiMoveStruct {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut writer = String::new();
match self {
SuiMoveStruct::Runtime(_) => {}
SuiMoveStruct::WithFields(fields) => {
for (name, value) in fields {
writeln!(writer, "{}: {value}", name.bold().bright_black())?;
}
}
SuiMoveStruct::WithTypes { type_, fields } => {
writeln!(writer)?;
writeln!(writer, " {}: {type_}", "type".bold().bright_black())?;
for (name, value) in fields {
let value = format!("{}", value);
let value = if value.starts_with('\n') {
indent(&value, 2)
} else {
value
};
writeln!(writer, " {}: {value}", name.bold().bright_black())?;
}
}
}
write!(f, "{}", writer.trim_end_matches('\n'))
}
}
fn indent<T: Display>(d: &T, indent: usize) -> String {
d.to_string()
.lines()
.map(|line| format!("{:indent$}{}", "", line))
.join("\n")
}