diff --git a/pallas-traverse/src/assets.rs b/pallas-traverse/src/assets.rs new file mode 100644 index 00000000..8e8b8896 --- /dev/null +++ b/pallas-traverse/src/assets.rs @@ -0,0 +1,110 @@ +use std::ops::Deref; + +use pallas_codec::utils::{Bytes, KeyValuePairs}; +use pallas_crypto::hash::Hash; +use pallas_primitives::{alonzo, babbage}; + +use crate::{Asset, MultiEraOutput}; + +fn iter_policy_assets<'b>( + policy: &'b Hash<28>, + assets: &'b KeyValuePairs, +) -> impl Iterator + 'b { + assets + .iter() + .map(|(name, amount)| Asset::NativeAsset(*policy, Vec::::clone(name), *amount)) +} + +fn collect_multiassets(multiassets: &alonzo::Multiasset) -> Vec { + multiassets + .iter() + .flat_map(|(p, a)| iter_policy_assets(p, a)) + .collect::>() +} + +impl Asset { + pub fn subject(&self) -> String { + match self { + Self::Ada(_) => String::from("ada"), + Self::NativeAsset(p, n, _) => format!("{p}.{}", hex::encode(n)), + } + } + + pub fn ascii_name(&self) -> Option { + match self { + Self::Ada(_) => None, + Self::NativeAsset(_, n, _) => String::from_utf8(n.clone()).ok(), + } + } + + pub fn policy_hex(&self) -> Option { + match self { + Asset::Ada(_) => None, + Asset::NativeAsset(p, _, _) => Some(p.to_string()), + } + } +} + +impl<'b> MultiEraOutput<'b> { + /// The amount of ADA asset expressed in Lovelace unit + /// + /// The value returned provides the amount of the ADA in a particular + /// output. The value is expressed in 'lovelace' (1 ADA = 1,000,000 + /// lovelace). + pub fn lovelace_amount(&self) -> u64 { + match self { + MultiEraOutput::Byron(x) => x.amount, + MultiEraOutput::Babbage(x) => match x.deref().deref() { + babbage::TransactionOutput::Legacy(x) => match x.amount { + babbage::Value::Coin(c) => c, + babbage::Value::Multiasset(c, _) => c, + }, + babbage::TransactionOutput::PostAlonzo(x) => match x.value { + babbage::Value::Coin(c) => c, + babbage::Value::Multiasset(c, _) => c, + }, + }, + MultiEraOutput::AlonzoCompatible(x) => match x.amount { + alonzo::Value::Coin(c) => c, + alonzo::Value::Multiasset(c, _) => c, + }, + } + } + + /// List of native assets in the output + /// + /// Returns a list of Asset structs where each one represent a native asset + /// present in the output of the tx. ADA assets are not included in this + /// list. + pub fn non_ada_assets(&self) -> Vec { + match self { + MultiEraOutput::Byron(_) => vec![], + MultiEraOutput::Babbage(x) => match x.deref().deref() { + babbage::TransactionOutput::Legacy(x) => match &x.amount { + babbage::Value::Coin(_) => vec![], + babbage::Value::Multiasset(_, x) => collect_multiassets(x), + }, + babbage::TransactionOutput::PostAlonzo(x) => match &x.value { + babbage::Value::Coin(_) => vec![], + babbage::Value::Multiasset(_, x) => collect_multiassets(x), + }, + }, + MultiEraOutput::AlonzoCompatible(x) => match &x.amount { + alonzo::Value::Coin(_) => vec![], + alonzo::Value::Multiasset(_, x) => collect_multiassets(x), + }, + } + } + + /// List of all assets in the output + /// + /// Returns a list of Asset structs where each one represent either ADA or a + /// native asset present in the output of the tx. + pub fn assets(&self) -> Vec { + [ + vec![Asset::Ada(self.lovelace_amount())], + self.non_ada_assets(), + ] + .concat() + } +} diff --git a/pallas-traverse/src/lib.rs b/pallas-traverse/src/lib.rs index bd873424..d20fc3ae 100644 --- a/pallas-traverse/src/lib.rs +++ b/pallas-traverse/src/lib.rs @@ -7,12 +7,9 @@ use thiserror::Error; use pallas_codec::utils::KeepRaw; use pallas_crypto::hash::Hash; -use pallas_primitives::{ - alonzo, - babbage::{self, AssetName, PolicyId}, - byron, -}; +use pallas_primitives::{alonzo, babbage, byron}; +pub mod assets; pub mod block; pub mod cert; pub mod era; @@ -137,24 +134,9 @@ pub enum MultiEraSigners<'b> { pub struct OutputRef(Hash<32>, u64); #[derive(Debug, Clone)] -pub struct Asset { - pub subject: Subject, - pub quantity: u64, -} - -#[derive(Debug, Clone)] -pub enum Subject { - Lovelace, - NativeAsset(PolicyId, AssetName), -} - -impl ToString for Subject { - fn to_string(&self) -> String { - match self { - Self::Lovelace => String::from("lovelace"), - Self::NativeAsset(p, n) => format!("{p}.{n}"), - } - } +pub enum Asset { + Ada(u64), + NativeAsset(Hash<28>, Vec, u64), } #[derive(Debug, Error)] diff --git a/pallas-traverse/src/output.rs b/pallas-traverse/src/output.rs index 483d58cf..46c8207d 100644 --- a/pallas-traverse/src/output.rs +++ b/pallas-traverse/src/output.rs @@ -2,13 +2,9 @@ use std::{borrow::Cow, ops::Deref}; use pallas_addresses::{Address, ByronAddress, Error as AddressError}; use pallas_codec::minicbor; -use pallas_primitives::{ - alonzo, - babbage::{self, Coin, DatumOption, ScriptRef}, - byron, -}; +use pallas_primitives::{alonzo, babbage, byron}; -use crate::{Asset, Era, MultiEraOutput, Subject}; +use crate::{Era, MultiEraOutput}; impl<'b> MultiEraOutput<'b> { pub fn from_byron(output: &'b byron::TxOut) -> Self { @@ -23,18 +19,20 @@ impl<'b> MultiEraOutput<'b> { Self::Babbage(Box::new(Cow::Borrowed(output))) } - pub fn datum(&self) -> Option { + pub fn datum(&self) -> Option { match self { - MultiEraOutput::AlonzoCompatible(x) => x.datum_hash.map(DatumOption::Hash), + MultiEraOutput::AlonzoCompatible(x) => x.datum_hash.map(babbage::DatumOption::Hash), MultiEraOutput::Babbage(x) => match x.deref().deref() { - babbage::TransactionOutput::Legacy(x) => x.datum_hash.map(DatumOption::Hash), + babbage::TransactionOutput::Legacy(x) => { + x.datum_hash.map(babbage::DatumOption::Hash) + } babbage::TransactionOutput::PostAlonzo(x) => x.datum_option.clone(), }, _ => None, } } - pub fn script_ref(&self) -> Option<&ScriptRef> { + pub fn script_ref(&self) -> Option<&babbage::ScriptRef> { match &self { MultiEraOutput::Babbage(x) => match x.deref().deref() { babbage::TransactionOutput::Legacy(_) => None, @@ -57,70 +55,6 @@ impl<'b> MultiEraOutput<'b> { } } - pub fn ada_amount(&self) -> u64 { - match self { - MultiEraOutput::Byron(x) => x.amount, - MultiEraOutput::Babbage(x) => match x.deref().deref() { - babbage::TransactionOutput::Legacy(x) => match x.amount { - babbage::Value::Coin(c) => c, - babbage::Value::Multiasset(c, _) => c, - }, - babbage::TransactionOutput::PostAlonzo(x) => match x.value { - babbage::Value::Coin(c) => c, - babbage::Value::Multiasset(c, _) => c, - }, - }, - MultiEraOutput::AlonzoCompatible(x) => match x.amount { - alonzo::Value::Coin(c) => c, - alonzo::Value::Multiasset(c, _) => c, - }, - } - } - - pub fn assets(&self) -> Vec { - let mut assets = Vec::new(); - - match self { - MultiEraOutput::Byron(x) => { - push_lovelace(&mut assets, x.amount); - } - MultiEraOutput::Babbage(x) => match x.deref().deref() { - babbage::TransactionOutput::Legacy(x) => match &x.amount { - babbage::Value::Coin(c) => { - push_lovelace(&mut assets, *c); - } - babbage::Value::Multiasset(c, multi_asset) => { - push_lovelace(&mut assets, *c); - - push_native_asset(&mut assets, multi_asset); - } - }, - babbage::TransactionOutput::PostAlonzo(x) => match &x.value { - babbage::Value::Coin(c) => { - push_lovelace(&mut assets, *c); - } - babbage::Value::Multiasset(c, multi_asset) => { - push_lovelace(&mut assets, *c); - - push_native_asset(&mut assets, multi_asset); - } - }, - }, - MultiEraOutput::AlonzoCompatible(x) => match &x.amount { - alonzo::Value::Coin(c) => { - push_lovelace(&mut assets, *c); - } - alonzo::Value::Multiasset(c, multi_asset) => { - push_lovelace(&mut assets, *c); - - push_native_asset(&mut assets, multi_asset); - } - }, - }; - - assets - } - pub fn as_babbage(&self) -> Option<&babbage::TransactionOutput> { match self { MultiEraOutput::AlonzoCompatible(_) => None, @@ -174,41 +108,3 @@ impl<'b> MultiEraOutput<'b> { } } } - -fn push_lovelace(assets: &mut Vec, quantity: u64) { - assets.push(Asset { - subject: Subject::Lovelace, - quantity, - }) -} - -fn push_native_asset(assets: &mut Vec, multi_asset: &alonzo::Multiasset) { - for (policy_id, names) in multi_asset.iter() { - for (asset_name, quantity) in names.iter() { - assets.push(Asset { - subject: Subject::NativeAsset(*policy_id, asset_name.clone()), - quantity: *quantity, - }); - } - } -} - -#[cfg(test)] -mod tests { - use crate::MultiEraBlock; - - #[test] - fn traverse_block_with_varied_outputs() { - let str = include_str!("../../test_data/alonzo24.block"); - let bytes = hex::decode(str).unwrap(); - let block = MultiEraBlock::decode(&bytes).unwrap(); - - for tx in block.txs() { - for output in tx.outputs() { - assert_ne!(output.assets()[0].quantity, 0); - assert_ne!(output.ada_amount(), 0); - assert!(matches!(output.address(), Ok(_))); - } - } - } -}