use alloc::borrow::Cow;
use core::convert::TryFrom;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_with::skip_serializing_none;
use strum_macros::{AsRefStr, Display, EnumIter};
use crate::_serde::opt_lgr_obj_flags;
use crate::models::{
ledger::objects::LedgerEntryType, FlagCollection, Model, XRPLModelException, XRPLModelResult,
};
use super::{CommonFields, LedgerObject};
#[derive(
Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter,
)]
#[repr(u32)]
pub enum MPTokenIssuanceFlag {
LsfMPTLocked = 0x00000001,
LsfMPTCanLock = 0x00000002,
LsfMPTRequireAuth = 0x00000004,
LsfMPTCanEscrow = 0x00000008,
LsfMPTCanTrade = 0x00000010,
LsfMPTCanTransfer = 0x00000020,
LsfMPTCanClawback = 0x00000040,
}
impl TryFrom<u32> for MPTokenIssuanceFlag {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0x00000001 => Ok(MPTokenIssuanceFlag::LsfMPTLocked),
0x00000002 => Ok(MPTokenIssuanceFlag::LsfMPTCanLock),
0x00000004 => Ok(MPTokenIssuanceFlag::LsfMPTRequireAuth),
0x00000008 => Ok(MPTokenIssuanceFlag::LsfMPTCanEscrow),
0x00000010 => Ok(MPTokenIssuanceFlag::LsfMPTCanTrade),
0x00000020 => Ok(MPTokenIssuanceFlag::LsfMPTCanTransfer),
0x00000040 => Ok(MPTokenIssuanceFlag::LsfMPTCanClawback),
_ => Err(()),
}
}
}
#[derive(
Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter,
)]
#[repr(u32)]
pub enum MPTokenIssuanceMutableFlag {
LsmfMPTCanMutateCanLock = 0x00000002,
LsmfMPTCanMutateRequireAuth = 0x00000004,
LsmfMPTCanMutateCanEscrow = 0x00000008,
LsmfMPTCanMutateCanTrade = 0x00000010,
LsmfMPTCanMutateCanTransfer = 0x00000020,
LsmfMPTCanMutateCanClawback = 0x00000040,
LsmfMPTCanMutateMetadata = 0x00010000,
LsmfMPTCanMutateTransferFee = 0x00020000,
}
impl TryFrom<u32> for MPTokenIssuanceMutableFlag {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0x00000002 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateCanLock),
0x00000004 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateRequireAuth),
0x00000008 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateCanEscrow),
0x00000010 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateCanTrade),
0x00000020 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateCanTransfer),
0x00000040 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateCanClawback),
0x00010000 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateMetadata),
0x00020000 => Ok(MPTokenIssuanceMutableFlag::LsmfMPTCanMutateTransferFee),
_ => Err(()),
}
}
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct MPTokenIssuance<'a> {
#[serde(flatten)]
pub common_fields: CommonFields<'a, MPTokenIssuanceFlag>,
pub issuer: Cow<'a, str>,
pub asset_scale: Option<u8>,
pub maximum_amount: Option<Cow<'a, str>>,
pub outstanding_amount: Cow<'a, str>,
pub transfer_fee: Option<u16>,
#[serde(rename = "MPTokenMetadata")]
pub mptoken_metadata: Option<Cow<'a, str>>,
pub sequence: u32,
pub owner_node: Option<Cow<'a, str>>,
#[serde(rename = "PreviousTxnID")]
pub previous_txn_id: Cow<'a, str>,
pub previous_txn_lgr_seq: u32,
#[serde(
default,
with = "opt_lgr_obj_flags",
skip_serializing_if = "Option::is_none"
)]
pub mutable_flags: Option<FlagCollection<MPTokenIssuanceMutableFlag>>,
pub locked_amount: Option<Cow<'a, str>>,
}
impl<'a> Model for MPTokenIssuance<'a> {
fn get_errors(&self) -> XRPLModelResult<()> {
if self.common_fields.index.is_none() && self.common_fields.ledger_index.is_none() {
return Err(XRPLModelException::MissingField(
"index or ledger_index".into(),
));
}
Ok(())
}
}
impl<'a> LedgerObject<MPTokenIssuanceFlag> for MPTokenIssuance<'a> {
fn get_ledger_entry_type(&self) -> LedgerEntryType {
self.common_fields.get_ledger_entry_type()
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::Cow;
use alloc::vec;
use crate::models::FlagCollection;
use super::*;
use crate::utils::testing::test_constants::*;
#[test]
fn test_serde() {
let issuance = MPTokenIssuance {
common_fields: CommonFields {
flags: FlagCollection(vec![MPTokenIssuanceFlag::LsfMPTCanTransfer]),
ledger_entry_type: LedgerEntryType::MPTokenIssuance,
index: Some(Cow::from(
"BFA9BE27383FA315651E26FDE1FA30815C5A5D0544EE10EC33D3E92532993769",
)),
ledger_index: Some(Cow::from("87654321")),
},
issuer: ACCOUNT_ISSUER.into(),
asset_scale: Some(2),
maximum_amount: Some("1000000".into()),
outstanding_amount: "500000".into(),
transfer_fee: Some(314),
mptoken_metadata: Some("CAFEBABE".into()),
sequence: 42,
owner_node: Some("0".into()),
previous_txn_id: "E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879"
.into(),
previous_txn_lgr_seq: 654321,
mutable_flags: Some(FlagCollection(vec![
MPTokenIssuanceMutableFlag::LsmfMPTCanMutateTransferFee,
])),
locked_amount: None,
};
let serialized = serde_json::to_string(&issuance).unwrap();
assert!(
serialized.contains("\"MutableFlags\":131072"),
"MutableFlags should serialize as integer 131072, got: {serialized}"
);
let deserialized: MPTokenIssuance = serde_json::from_str(&serialized).unwrap();
assert_eq!(issuance, deserialized);
}
#[test]
fn test_ledger_entry_type() {
let issuance = MPTokenIssuance {
common_fields: CommonFields {
flags: FlagCollection(vec![]),
ledger_entry_type: LedgerEntryType::MPTokenIssuance,
index: Some(Cow::from(
"A44128B79CAB60A1C97A72F5A4B0F43F04ABBE65B8B1C6AC24CF27E6DEA3B2A",
)),
ledger_index: Some(Cow::from("1000000")),
},
issuer: ACCOUNT_ISSUER.into(),
asset_scale: None,
maximum_amount: None,
outstanding_amount: "0".into(),
transfer_fee: None,
mptoken_metadata: None,
sequence: 1,
owner_node: None,
previous_txn_id: "E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879"
.into(),
previous_txn_lgr_seq: 100,
mutable_flags: None,
locked_amount: None,
};
assert_eq!(
issuance.get_ledger_entry_type(),
LedgerEntryType::MPTokenIssuance
);
}
#[test]
fn test_minimal_issuance() {
let issuance = MPTokenIssuance {
common_fields: CommonFields {
flags: FlagCollection(vec![]),
ledger_entry_type: LedgerEntryType::MPTokenIssuance,
index: None,
ledger_index: Some(Cow::from("1000000")),
},
issuer: ACCOUNT_ISSUER.into(),
asset_scale: None,
maximum_amount: None,
outstanding_amount: "0".into(),
transfer_fee: None,
mptoken_metadata: None,
sequence: 1,
owner_node: None,
previous_txn_id: "0000000000000000000000000000000000000000000000000000000000000000"
.into(),
previous_txn_lgr_seq: 0,
mutable_flags: None,
locked_amount: None,
};
assert!(issuance.validate().is_ok());
}
#[test]
fn test_missing_index_and_ledger_index_error() {
let issuance = MPTokenIssuance {
common_fields: CommonFields {
flags: FlagCollection(vec![]),
ledger_entry_type: LedgerEntryType::MPTokenIssuance,
index: None,
ledger_index: None,
},
issuer: ACCOUNT_ISSUER.into(),
asset_scale: None,
maximum_amount: None,
outstanding_amount: "0".into(),
transfer_fee: None,
mptoken_metadata: None,
sequence: 1,
owner_node: None,
previous_txn_id: "0000000000000000000000000000000000000000000000000000000000000000"
.into(),
previous_txn_lgr_seq: 0,
mutable_flags: None,
locked_amount: None,
};
assert!(issuance.validate().is_err());
}
#[test]
fn test_mutable_flag_variants() {
assert!(
MPTokenIssuanceMutableFlag::try_from(0x00020000).is_ok(),
"LsmfMPTCanMutateTransferFee should parse"
);
assert!(
MPTokenIssuanceMutableFlag::try_from(0x00010000).is_ok(),
"LsmfMPTCanMutateMetadata should parse"
);
assert!(MPTokenIssuanceMutableFlag::try_from(0x00000001).is_err());
assert!(MPTokenIssuanceMutableFlag::try_from(0x00000002).is_ok());
assert!(MPTokenIssuanceMutableFlag::try_from(0x00000004).is_ok());
assert!(MPTokenIssuanceMutableFlag::try_from(0x00000008).is_ok());
assert!(MPTokenIssuanceMutableFlag::try_from(0x00000010).is_ok());
assert!(MPTokenIssuanceMutableFlag::try_from(0x00000020).is_ok());
assert!(MPTokenIssuanceMutableFlag::try_from(0x00000040).is_ok());
}
#[test]
fn test_issuance_flag_try_from() {
assert!(MPTokenIssuanceFlag::try_from(0x00000001).is_ok());
assert!(MPTokenIssuanceFlag::try_from(0x00000002).is_ok());
assert!(MPTokenIssuanceFlag::try_from(0x00000004).is_ok());
assert!(MPTokenIssuanceFlag::try_from(0x00000008).is_ok());
assert!(MPTokenIssuanceFlag::try_from(0x00000010).is_ok());
assert!(MPTokenIssuanceFlag::try_from(0x00000020).is_ok());
assert!(MPTokenIssuanceFlag::try_from(0x00000040).is_ok());
assert!(MPTokenIssuanceFlag::try_from(0x00000080).is_err());
}
#[test]
fn test_deserialize_without_mutable_flags() {
let json = r#"{
"Flags": 0,
"Issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"MPTokenIssuanceID": "00000001A407AF5856CEFBF81F3D4A0000000000A407AF58",
"OutstandingAmount": "0",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
"PreviousTxnLgrSeq": 1,
"Sequence": 1,
"LedgerEntryType": "MPTokenIssuance"
}"#;
let obj: MPTokenIssuance =
serde_json::from_str(json).expect("must deserialize without MutableFlags key");
assert!(
obj.mutable_flags.is_none(),
"absent MutableFlags should deserialize as None"
);
}
}