use anyhow::{Context, Result, bail};
use hex;
use pallas_crypto::hash::Hash;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Hash, Clone)]
pub struct Asset {
pub policy_id: Hash<28>,
pub token_name: Vec<u8>,
pub amount: u64,
}
impl Asset {
pub fn new(policy_id: String, token_name: String, amount: u64) -> Result<Self> {
Ok(Self {
policy_id: Hash::new(
hex::decode(policy_id)?
.as_slice()
.try_into()
.map_err(|e| anyhow::anyhow!("{e}"))?,
),
token_name: hex::decode(token_name).context("Can't Decode Token Name")?,
amount,
})
}
pub fn add(&self, other: &Asset) -> Result<Self> {
if self.policy_id != other.policy_id || self.token_name != other.token_name {
bail!("Assets must have the same policy_id and token_name to be subtracted")
}
Ok(Self {
policy_id: self.policy_id,
token_name: self.token_name.clone(),
amount: self.amount + other.amount,
})
}
pub fn sub(&self, other: &Asset) -> Result<Self> {
if self.policy_id != other.policy_id || self.token_name != other.token_name {
bail!("Assets must have the same policy_id and token_name to be subtracted")
}
Ok(Self {
policy_id: self.policy_id,
token_name: self.token_name.clone(),
amount: self.amount - other.amount,
})
}
pub fn compare(&self, other: Asset) -> bool {
if self.policy_id != other.policy_id || self.token_name != other.token_name {
false
} else {
self.amount >= other.amount
}
}
pub fn quantity_of(&self, policy_id: String, token_name: String) -> Result<Option<u64>> {
let pid = Hash::new(
hex::decode(policy_id)?
.as_slice()
.try_into()
.map_err(|e| anyhow::anyhow!("{e}"))?,
);
let tkn = hex::decode(token_name).context("Can't Decode Token Name")?;
if self.policy_id == pid && self.token_name == tkn {
Ok(Some(self.amount))
} else {
Ok(None)
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Hash, Clone)]
pub struct Assets {
pub items: Vec<Asset>,
}
impl Default for Assets {
fn default() -> Self {
Self::new()
}
}
impl Assets {
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn add(&self, other: Asset) -> Result<Self> {
let mut new_items: Vec<Asset> = self.items.clone();
if let Some(existing) = new_items.iter_mut().find(|existing| {
existing.policy_id == other.policy_id && existing.token_name == other.token_name
}) {
*existing = existing.add(&other).context("Can't Add Asset To Assets")?;
} else {
new_items.push(other);
}
Ok(Self { items: new_items })
}
pub fn sub(&self, other: Asset) -> Result<Self> {
let mut new_items: Vec<Asset> = self.items.clone();
if let Some(existing) = new_items.iter_mut().find(|existing| {
existing.policy_id == other.policy_id && existing.token_name == other.token_name
}) {
*existing = existing
.sub(&other)
.context("Can't Subtract Asset From Assets")?;
} else {
new_items.push(other);
}
Ok(Self { items: new_items }.remove_zero_amounts())
}
pub fn remove_zero_amounts(&self) -> Self {
let filtered_items: Vec<Asset> = self
.items
.iter()
.filter(|asset| asset.amount > 0)
.cloned()
.collect();
Self {
items: filtered_items,
}
}
pub fn contains(&self, other: Assets) -> bool {
for other_token in other.items {
let mut found = false;
for token in self.items.clone() {
if token.compare(other_token.clone()) {
found = true;
break;
}
}
if !found {
return false;
}
}
true
}
pub fn quantity_of(&self, policy_id: String, token_name: String) -> Result<Option<u64>> {
for this_asset in &self.items {
match Asset::quantity_of(this_asset, policy_id.clone(), token_name.clone()) {
Ok(Some(amount)) => return Ok(Some(amount)),
_ => continue,
}
}
Ok(None)
}
pub fn any(&self, other: Assets) -> bool {
if other.items.is_empty() {
return true;
}
for other_token in other.items {
for token in self.items.clone() {
if token.policy_id == other_token.policy_id
&& token.token_name == other_token.token_name
{
return true;
}
}
}
false
}
pub fn merge(&self, other: Assets) -> Result<Self> {
let mut merged: Assets = self.clone();
for other_asset in other.items {
merged = merged.add(other_asset).context("Can't Merge Assets")?; }
Ok(merged)
}
pub fn separate(&self, other: Assets) -> Result<Self> {
let mut separated: Assets = self.clone();
for other_asset in other.items {
separated = separated
.sub(other_asset)
.context("Can't Separate Assets")?; }
Ok(separated)
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn len(&self) -> u64 {
self.items.len() as u64
}
pub fn split(&self, k: usize) -> Vec<Self> {
self.items
.chunks(k) .map(|chunk| Assets {
items: chunk.to_vec(),
}) .collect()
}
}
pub fn string_to_u64(input: String) -> Result<u64> {
input.parse::<u64>().context("Failed To Parse u64")
}
pub fn asset_id_to_asset(asset_id: String) -> Result<Asset> {
Asset::new(asset_id[..56].to_string(), asset_id[56..].to_string(), 1)
}