use move_core_types::{
annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout},
ident_str,
identifier::IdentStr,
language_storage::{StructTag, TypeTag},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
IOTA_FRAMEWORK_ADDRESS,
balance::{Balance, Supply},
base_types::ObjectID,
error::{ExecutionError, ExecutionErrorKind, IotaError},
id::UID,
object::{Data, Object},
};
pub const COIN_MODULE_NAME: &IdentStr = ident_str!("coin");
pub const COIN_STRUCT_NAME: &IdentStr = ident_str!("Coin");
pub const COIN_METADATA_STRUCT_NAME: &IdentStr = ident_str!("CoinMetadata");
pub const COIN_TREASURE_CAP_NAME: &IdentStr = ident_str!("TreasuryCap");
pub const COIN_JOIN_FUNC_NAME: &IdentStr = ident_str!("join");
pub const PAY_MODULE_NAME: &IdentStr = ident_str!("pay");
pub const PAY_SPLIT_N_FUNC_NAME: &IdentStr = ident_str!("divide_and_keep");
pub const PAY_SPLIT_VEC_FUNC_NAME: &IdentStr = ident_str!("split_vec");
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Eq, PartialEq)]
pub struct Coin {
pub id: UID,
pub balance: Balance,
}
impl Coin {
pub fn new(id: UID, value: u64) -> Self {
Self {
id,
balance: Balance::new(value),
}
}
pub fn type_(type_param: TypeTag) -> StructTag {
StructTag {
address: IOTA_FRAMEWORK_ADDRESS,
name: COIN_STRUCT_NAME.to_owned(),
module: COIN_MODULE_NAME.to_owned(),
type_params: vec![type_param],
}
}
pub fn is_coin(other: &StructTag) -> bool {
other.address == IOTA_FRAMEWORK_ADDRESS
&& other.module.as_ident_str() == COIN_MODULE_NAME
&& other.name.as_ident_str() == COIN_STRUCT_NAME
}
pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, bcs::Error> {
bcs::from_bytes(content)
}
pub fn extract_balance_if_coin(object: &Object) -> Result<Option<u64>, bcs::Error> {
match &object.data {
Data::Move(move_obj) => {
if !move_obj.is_coin() {
return Ok(None);
}
let coin = Self::from_bcs_bytes(move_obj.contents())?;
Ok(Some(coin.value()))
}
_ => Ok(None), }
}
pub fn id(&self) -> &ObjectID {
self.id.object_id()
}
pub fn value(&self) -> u64 {
self.balance.value()
}
pub fn to_bcs_bytes(&self) -> Vec<u8> {
bcs::to_bytes(&self).unwrap()
}
pub fn layout(type_param: TypeTag) -> MoveStructLayout {
MoveStructLayout {
type_: Self::type_(type_param.clone()),
fields: vec![
MoveFieldLayout::new(
ident_str!("id").to_owned(),
MoveTypeLayout::Struct(UID::layout()),
),
MoveFieldLayout::new(
ident_str!("balance").to_owned(),
MoveTypeLayout::Struct(Balance::layout(type_param)),
),
],
}
}
pub fn add(&mut self, balance: Balance) -> Result<(), ExecutionError> {
let Some(new_value) = self.value().checked_add(balance.value()) else {
return Err(ExecutionError::from_kind(
ExecutionErrorKind::CoinBalanceOverflow,
));
};
self.balance = Balance::new(new_value);
Ok(())
}
pub fn split(&mut self, amount: u64, new_coin_id: UID) -> Result<Coin, ExecutionError> {
self.balance.withdraw(amount)?;
Ok(Coin::new(new_coin_id, amount))
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
pub struct TreasuryCap {
pub id: UID,
pub total_supply: Supply,
}
impl TreasuryCap {
pub fn is_treasury_type(other: &StructTag) -> bool {
other.address == IOTA_FRAMEWORK_ADDRESS
&& other.module.as_ident_str() == COIN_MODULE_NAME
&& other.name.as_ident_str() == COIN_TREASURE_CAP_NAME
}
pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
error: format!("Unable to deserialize TreasuryCap object: {}", err),
})
}
pub fn type_(type_param: StructTag) -> StructTag {
StructTag {
address: IOTA_FRAMEWORK_ADDRESS,
name: COIN_TREASURE_CAP_NAME.to_owned(),
module: COIN_MODULE_NAME.to_owned(),
type_params: vec![TypeTag::Struct(Box::new(type_param))],
}
}
pub fn is_treasury_with_coin_type(other: &StructTag) -> Option<&StructTag> {
if Self::is_treasury_type(other) && other.type_params.len() == 1 {
match other.type_params.first() {
Some(TypeTag::Struct(coin_type)) => Some(coin_type),
_ => None,
}
} else {
None
}
}
}
impl TryFrom<Object> for TreasuryCap {
type Error = IotaError;
fn try_from(object: Object) -> Result<Self, Self::Error> {
match &object.data {
Data::Move(o) => {
if o.type_().is_treasury_cap() {
return TreasuryCap::from_bcs_bytes(o.contents());
}
}
Data::Package(_) => {}
}
Err(IotaError::Type {
error: format!("Object type is not a TreasuryCap: {:?}", object),
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Eq, PartialEq)]
pub struct CoinMetadata {
pub id: UID,
pub decimals: u8,
pub name: String,
pub symbol: String,
pub description: String,
pub icon_url: Option<String>,
}
impl CoinMetadata {
pub fn is_coin_metadata(other: &StructTag) -> bool {
other.address == IOTA_FRAMEWORK_ADDRESS
&& other.module.as_ident_str() == COIN_MODULE_NAME
&& other.name.as_ident_str() == COIN_METADATA_STRUCT_NAME
}
pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
error: format!("Unable to deserialize CoinMetadata object: {}", err),
})
}
pub fn type_(type_param: StructTag) -> StructTag {
StructTag {
address: IOTA_FRAMEWORK_ADDRESS,
name: COIN_METADATA_STRUCT_NAME.to_owned(),
module: COIN_MODULE_NAME.to_owned(),
type_params: vec![TypeTag::Struct(Box::new(type_param))],
}
}
pub fn is_coin_metadata_with_coin_type(other: &StructTag) -> Option<&StructTag> {
if Self::is_coin_metadata(other) && other.type_params.len() == 1 {
match other.type_params.first() {
Some(TypeTag::Struct(coin_type)) => Some(coin_type),
_ => None,
}
} else {
None
}
}
}
impl TryFrom<Object> for CoinMetadata {
type Error = IotaError;
fn try_from(object: Object) -> Result<Self, Self::Error> {
TryFrom::try_from(&object)
}
}
impl TryFrom<&Object> for CoinMetadata {
type Error = IotaError;
fn try_from(object: &Object) -> Result<Self, Self::Error> {
match &object.data {
Data::Move(o) => {
if o.type_().is_coin_metadata() {
return CoinMetadata::from_bcs_bytes(o.contents());
}
}
Data::Package(_) => {}
}
Err(IotaError::Type {
error: format!("Object type is not a CoinMetadata: {:?}", object),
})
}
}