From ffa438e73fcf621f85bef9c6cccc9d9d7358af43 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:13:15 +0000 Subject: [PATCH 1/3] Initial plan From 123d8037bf5bd99e5efa00bb5e0e5619643cbdf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:26:43 +0000 Subject: [PATCH 2/3] Add traits and generic functions to reduce code duplication in utils.rs Co-authored-by: scarmuega <653886+scarmuega@users.noreply.github.com> --- pallas-validate/src/utils.rs | 168 +++++++++++++-------- pallas-validate/src/utils/value_ops.rs | 201 +++++++++++++++++++++++++ 2 files changed, 310 insertions(+), 59 deletions(-) create mode 100644 pallas-validate/src/utils/value_ops.rs diff --git a/pallas-validate/src/utils.rs b/pallas-validate/src/utils.rs index b61082044..6c96c6b53 100644 --- a/pallas-validate/src/utils.rs +++ b/pallas-validate/src/utils.rs @@ -2,11 +2,13 @@ pub mod environment; pub mod validation; +pub mod value_ops; pub use environment::*; +pub use value_ops::{MultiassetOps}; use pallas_addresses::{Address, ShelleyAddress, ShelleyPaymentPart}; use pallas_codec::{ - minicbor::encode, + minicbor::{encode, Encode}, utils::{Bytes, Nullable}, }; use pallas_crypto::key::ed25519::{PublicKey, Signature}; @@ -94,34 +96,63 @@ pub type UtxoSet = HashSet; pub type UTxOs<'b> = HashMap, MultiEraOutput<'b>>; -pub fn get_alonzo_comp_tx_size(mtx: &AlonzoTx) -> u32 { - match &mtx.auxiliary_data { - Nullable::Some(aux_data) => { - (aux_data.raw_cbor().len() - + mtx.transaction_body.raw_cbor().len() - + mtx.transaction_witness_set.raw_cbor().len()) as u32 +/// Trait for calculating transaction size +pub trait TxSizeCalculator { + fn calculate_tx_size(&self) -> Option; +} + +impl<'a> TxSizeCalculator for AlonzoTx<'a> { + fn calculate_tx_size(&self) -> Option { + Some(match &self.auxiliary_data { + Nullable::Some(aux_data) => { + (aux_data.raw_cbor().len() + + self.transaction_body.raw_cbor().len() + + self.transaction_witness_set.raw_cbor().len()) as u32 + } + _ => { + (self.transaction_body.raw_cbor().len() + self.transaction_witness_set.raw_cbor().len()) + as u32 + } + }) + } +} + +impl<'a> TxSizeCalculator for BabbageTx<'a> { + fn calculate_tx_size(&self) -> Option { + let mut buff: Vec = Vec::new(); + match encode(self, &mut buff) { + Ok(()) => Some(buff.len() as u32), + Err(_) => None, } - _ => { - (mtx.transaction_body.raw_cbor().len() + mtx.transaction_witness_set.raw_cbor().len()) - as u32 + } +} + +impl<'a> TxSizeCalculator for ConwayTx<'a> { + fn calculate_tx_size(&self) -> Option { + let mut buff: Vec = Vec::new(); + match encode(self, &mut buff) { + Ok(()) => Some(buff.len() as u32), + Err(_) => None, } } } +/// Generic function to calculate transaction size +pub fn get_tx_size(tx: &T) -> Option { + tx.calculate_tx_size() +} + +// Backward compatibility functions +pub fn get_alonzo_comp_tx_size(mtx: &AlonzoTx) -> u32 { + get_tx_size(mtx).unwrap() // Alonzo always returns Some +} + pub fn get_babbage_tx_size(mtx: &BabbageTx) -> Option { - let mut buff: Vec = Vec::new(); - match encode(mtx, &mut buff) { - Ok(()) => Some(buff.len() as u32), - Err(_) => None, - } + get_tx_size(mtx) } pub fn get_conway_tx_size(mtx: &ConwayTx) -> Option { - let mut buff: Vec = Vec::new(); - match encode(mtx, &mut buff) { - Ok(()) => Some(buff.len() as u32), - Err(_) => None, - } + get_tx_size(mtx) } pub fn empty_value() -> Value { @@ -581,6 +612,7 @@ pub fn values_are_equal(first: &Value, second: &Value) -> bool { } } } + pub fn conway_values_are_equal(first: &ConwayValue, second: &ConwayValue) -> bool { match (first, second) { (ConwayValue::Coin(f), ConwayValue::Coin(s)) => f == s, @@ -596,65 +628,47 @@ pub fn conway_values_are_equal(first: &ConwayValue, second: &ConwayValue) -> boo } } +// Generic functions using value_ops traits - these replace the era-specific versions fn find_policy( mary_value: &Multiasset, search_policy: &PolicyId, ) -> Option> { - for (policy, assets) in mary_value.iter() { - if policy == search_policy { - return Some(assets.clone()); - } - } - None + mary_value.find_policy(search_policy) } fn conway_find_policy( mary_value: &ConwayMultiasset, search_policy: &PolicyId, ) -> Option> { - for (policy, assets) in mary_value.iter() { - if policy == search_policy { - return Some(assets.clone()); - } - } - None + mary_value.find_policy(search_policy) } fn find_assets( assets: &std::collections::BTreeMap, asset_name: &AssetName, ) -> Option { - for (an, amount) in assets.iter() { - if an == asset_name { - return Some(*amount); - } - } - None + value_ops::find_assets_generic(assets, asset_name) } + fn conway_find_assets( assets: &std::collections::BTreeMap, asset_name: &AssetName, ) -> Option { - for (an, amount) in assets.iter() { - if an == asset_name { - return Some(*amount); - } - } - None + value_ops::find_assets_generic(assets, asset_name) +} + +/// Generic function to get lovelace from any value using the ValueOps trait +pub fn get_lovelace_from_value(val: &V) -> Coin { + val.get_lovelace() } +// Backward compatibility functions pub fn get_lovelace_from_alonzo_val(val: &Value) -> Coin { - match val { - Value::Coin(res) => *res, - Value::Multiasset(res, _) => *res, - } + get_lovelace_from_value(val) } pub fn get_lovelace_from_conway_val(val: &ConwayValue) -> Coin { - match val { - ConwayValue::Coin(res) => *res, - ConwayValue::Multiasset(res, _) => *res, - } + get_lovelace_from_value(val) } #[deprecated(since = "0.31.0", note = "use `u8::from(...)` instead")] @@ -700,28 +714,64 @@ pub fn is_byron_address(address: &[u8]) -> bool { matches!(Address::from_bytes(address), Ok(Address::Byron(_))) } +/// Trait for accessing auxiliary data from transactions +pub trait AuxDataAccess { + fn get_aux_data(&self) -> Option<&[u8]>; +} + +impl<'a> AuxDataAccess for AlonzoTx<'a> { + fn get_aux_data(&self) -> Option<&[u8]> { + self.auxiliary_data.as_ref().map(|x| x.raw_cbor()).into() + } +} + +impl<'a> AuxDataAccess for BabbageTx<'a> { + fn get_aux_data(&self) -> Option<&[u8]> { + self.auxiliary_data.as_ref().map(|x| x.raw_cbor()).into() + } +} + +impl<'a> AuxDataAccess for ConwayTx<'a> { + fn get_aux_data(&self) -> Option<&[u8]> { + self.auxiliary_data.as_ref().map(|x| x.raw_cbor()).into() + } +} + +/// Generic function to get auxiliary data from any transaction +pub fn get_aux_data_from_tx(tx: &T) -> Option<&[u8]> { + tx.get_aux_data() +} + +// Backward compatibility functions - can be removed later pub fn aux_data_from_alonzo_tx<'a>(mtx: &'a AlonzoTx) -> Option<&'a [u8]> { - mtx.auxiliary_data.as_ref().map(|x| x.raw_cbor()).into() + get_aux_data_from_tx(mtx) } pub fn aux_data_from_babbage_tx<'a>(mtx: &'a BabbageTx) -> Option<&'a [u8]> { - mtx.auxiliary_data.as_ref().map(|x| x.raw_cbor()).into() + get_aux_data_from_tx(mtx) } pub fn aux_data_from_conway_tx<'a>(mtx: &'a ConwayTx) -> Option<&'a [u8]> { - mtx.auxiliary_data.as_ref().map(|x| x.raw_cbor()).into() + get_aux_data_from_tx(mtx) } -pub fn get_val_size_in_words(val: &Value) -> u64 { +/// Generic function to calculate value size in words +pub fn get_val_size_in_words_generic(val: &T) -> u64 +where + T: for<'a> Encode<()>, +{ let mut tx_buf: Vec = Vec::new(); let _ = encode(val, &mut tx_buf); (tx_buf.len() as u64).div_ceil(8) // ceiling of the result of dividing } +// Backward compatibility functions +pub fn get_val_size_in_words(val: &Value) -> u64 { + get_val_size_in_words_generic(val) +} + pub fn conway_get_val_size_in_words(val: &ConwayValue) -> u64 { - let mut tx_buf: Vec = Vec::new(); - let _ = encode(val, &mut tx_buf); - (tx_buf.len() as u64).div_ceil(8) // ceiling of the result of dividing + get_val_size_in_words_generic(val) } pub fn compute_native_script_hash(script: &NativeScript) -> PolicyId { diff --git a/pallas-validate/src/utils/value_ops.rs b/pallas-validate/src/utils/value_ops.rs new file mode 100644 index 000000000..43a3dd345 --- /dev/null +++ b/pallas-validate/src/utils/value_ops.rs @@ -0,0 +1,201 @@ +//! Generic value operations to avoid code duplication between eras + +use pallas_primitives::{ + alonzo::{Multiasset, Value}, + conway::{Multiasset as ConwayMultiasset, Value as ConwayValue}, + AssetName, Coin, PolicyId, PositiveCoin, +}; +use std::collections::BTreeMap; + +use crate::utils::ValidationError; + +/// Trait for abstracting value operations across different eras +pub trait ValueOps { + type CoinType: Copy + Clone; + type MultiassetType: Clone; + + fn get_lovelace(&self) -> Coin; + fn is_coin_only(&self) -> bool; + fn make_coin(amount: Coin) -> Self; + fn make_multiasset(coin: Coin, multiasset: Self::MultiassetType) -> Self; + fn get_multiasset(&self) -> Option<&Self::MultiassetType>; +} + +/// Implementation for Alonzo Value +impl ValueOps for Value { + type CoinType = Coin; + type MultiassetType = Multiasset; + + fn get_lovelace(&self) -> Coin { + match self { + Value::Coin(amount) => *amount, + Value::Multiasset(amount, _) => *amount, + } + } + + fn is_coin_only(&self) -> bool { + matches!(self, Value::Coin(_)) + } + + fn make_coin(amount: Coin) -> Self { + Value::Coin(amount) + } + + fn make_multiasset(coin: Coin, multiasset: Self::MultiassetType) -> Self { + Value::Multiasset(coin, multiasset) + } + + fn get_multiasset(&self) -> Option<&Self::MultiassetType> { + match self { + Value::Coin(_) => None, + Value::Multiasset(_, multiasset) => Some(multiasset), + } + } +} + +/// Implementation for Conway Value +impl ValueOps for ConwayValue { + type CoinType = PositiveCoin; + type MultiassetType = ConwayMultiasset; + + fn get_lovelace(&self) -> Coin { + match self { + ConwayValue::Coin(amount) => *amount, + ConwayValue::Multiasset(amount, _) => *amount, + } + } + + fn is_coin_only(&self) -> bool { + matches!(self, ConwayValue::Coin(_)) + } + + fn make_coin(amount: Coin) -> Self { + ConwayValue::Coin(amount) + } + + fn make_multiasset(coin: Coin, multiasset: Self::MultiassetType) -> Self { + ConwayValue::Multiasset(coin, multiasset) + } + + fn get_multiasset(&self) -> Option<&Self::MultiassetType> { + match self { + ConwayValue::Coin(_) => None, + ConwayValue::Multiasset(_, multiasset) => Some(multiasset), + } + } +} + +/// Trait for abstracting multiasset operations +pub trait MultiassetOps { + type CoinType: Copy + Clone; + + fn is_empty(&self) -> bool; + fn find_policy(&self, policy_id: &PolicyId) -> Option>; + fn iter(&self) -> impl Iterator)>; +} + +/// Implementation for Alonzo Multiasset +impl MultiassetOps for Multiasset { + type CoinType = Coin; + + fn is_empty(&self) -> bool { + self.is_empty() + } + + fn find_policy(&self, policy_id: &PolicyId) -> Option> { + for (policy, assets) in self.iter() { + if policy == policy_id { + return Some(assets.clone()); + } + } + None + } + + fn iter(&self) -> impl Iterator)> { + self.iter() + } +} + +/// Implementation for Conway Multiasset +impl MultiassetOps for ConwayMultiasset { + type CoinType = PositiveCoin; + + fn is_empty(&self) -> bool { + self.is_empty() + } + + fn find_policy(&self, policy_id: &PolicyId) -> Option> { + for (policy, assets) in self.iter() { + if policy == policy_id { + return Some(assets.clone()); + } + } + None + } + + fn iter(&self) -> impl Iterator)> { + self.iter() + } +} + +/// Generic function to find assets in a BTreeMap +pub fn find_assets_generic( + assets: &BTreeMap, + asset_name: &AssetName, +) -> Option { + assets.get(asset_name).copied() +} + +/// Generic function to check if two values are equal +pub fn values_are_equal_generic(first: &V, second: &V) -> bool +where + V: ValueOps, + V::MultiassetType: PartialEq, +{ + if first.get_lovelace() != second.get_lovelace() { + return false; + } + + match (first.get_multiasset(), second.get_multiasset()) { + (None, None) => true, + (Some(fma), Some(sma)) => fma == sma, + _ => false, + } +} + +/// Generic function to calculate lovelace difference or fail +pub fn lovelace_diff_or_fail_generic( + first: &V, + second: &V, + err: &ValidationError, +) -> Result +where + V: ValueOps, + V::MultiassetType: PartialEq + MultiassetOps, +{ + let first_lovelace = first.get_lovelace(); + let second_lovelace = second.get_lovelace(); + + if first_lovelace < second_lovelace { + return Err(err.clone()); + } + + match (first.get_multiasset(), second.get_multiasset()) { + (None, None) => Ok(first_lovelace - second_lovelace), + (Some(fma), None) => { + if fma.is_empty() { + Ok(first_lovelace - second_lovelace) + } else { + Err(err.clone()) + } + } + (None, Some(_)) => Err(err.clone()), + (Some(fma), Some(sma)) => { + if fma == sma { + Ok(first_lovelace - second_lovelace) + } else { + Err(err.clone()) + } + } + } +} \ No newline at end of file From b7c4fd51083b928f8255bddb90a7521fa7cbbb91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:32:57 +0000 Subject: [PATCH 3/3] Complete refactoring of utils.rs with generic functions and reduced duplication Co-authored-by: scarmuega <653886+scarmuega@users.noreply.github.com> --- pallas-validate/src/utils.rs | 135 ++++++++++++------------- pallas-validate/src/utils/value_ops.rs | 89 +++++++++++++++- 2 files changed, 150 insertions(+), 74 deletions(-) diff --git a/pallas-validate/src/utils.rs b/pallas-validate/src/utils.rs index 6c96c6b53..6f010c2ce 100644 --- a/pallas-validate/src/utils.rs +++ b/pallas-validate/src/utils.rs @@ -23,7 +23,7 @@ use pallas_primitives::{ use pallas_traverse::{time::Slot, Era, MultiEraInput, MultiEraOutput, MultiEraUpdate}; use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::ops::Deref; pub use validation::*; @@ -385,76 +385,83 @@ pub fn conway_add_minted_non_zero( } } +// Generic coercion functions using functional approach to reduce duplication fn coerce_to_i64(value: &Multiasset) -> Multiasset { - let mut res: Vec<(PolicyId, _)> = Vec::new(); - for (policy, assets) in value.iter() { - let mut aa: Vec<(AssetName, i64)> = Vec::new(); - for (asset_name, amount) in assets.iter() { - aa.push((asset_name.clone(), *amount as i64)); - } - res.push((*policy, aa.into_iter().collect())); - } - res.into_iter().collect() + value + .iter() + .map(|(policy, assets)| { + let converted_assets: BTreeMap = assets + .iter() + .map(|(asset_name, amount)| (asset_name.clone(), *amount as i64)) + .collect(); + (*policy, converted_assets) + }) + .collect() } fn coerce_to_u64(value: &ConwayMultiasset) -> ConwayMultiasset { - let mut res: Vec<(PolicyId, _)> = Vec::new(); - for (policy, assets) in value.iter() { - let mut aa: Vec<(AssetName, u64)> = Vec::new(); - for (asset_name, amount) in assets.iter() { - aa.push((asset_name.clone(), (*amount).into())); - } - res.push((*policy, aa.into_iter().collect())); - } - res.into_iter().collect() + value + .iter() + .map(|(policy, assets)| { + let converted_assets: BTreeMap = assets + .iter() + .map(|(asset_name, amount)| (asset_name.clone(), (*amount).into())) + .collect(); + (*policy, converted_assets) + }) + .collect() } +// Simplified coercion functions - keeping some duplication for type safety fn coerce_to_coin( value: &Multiasset, _err: &ValidationError, ) -> Result, ValidationError> { - let mut res: Vec<(PolicyId, _)> = Vec::new(); - for (policy, assets) in value.iter() { - let mut aa: Vec<(AssetName, Coin)> = Vec::new(); - for (asset_name, amount) in assets.iter() { - aa.push((asset_name.clone(), *amount as u64)); - } - res.push((*policy, aa.into_iter().collect())); - } - Ok(res.into_iter().collect()) + let result: Vec<(PolicyId, BTreeMap)> = value + .iter() + .map(|(policy, assets)| { + let converted_assets: BTreeMap = assets + .iter() + .map(|(asset_name, amount)| (asset_name.clone(), *amount as u64)) + .collect(); + (*policy, converted_assets) + }) + .collect(); + Ok(result.into_iter().collect()) } fn conway_coerce_to_coin( value: &ConwayMultiasset, _err: &ValidationError, ) -> Result, ValidationError> { - let mut res: Vec<(PolicyId, _)> = Vec::new(); - for (policy, assets) in value.iter() { - let mut aa: Vec<(AssetName, PositiveCoin)> = Vec::new(); - for (asset_name, amount) in assets.iter() { - aa.push((asset_name.clone(), PositiveCoin::try_from(*amount).unwrap())); - } - res.push((*policy, aa.into_iter().collect())); - } - Ok(res.into_iter().collect()) + let result: Vec<(PolicyId, BTreeMap)> = value + .iter() + .map(|(policy, assets)| { + let converted_assets: BTreeMap = assets + .iter() + .map(|(asset_name, amount)| (asset_name.clone(), PositiveCoin::try_from(*amount).unwrap())) + .collect(); + (*policy, converted_assets) + }) + .collect(); + Ok(result.into_iter().collect()) } fn conway_coerce_to_non_zero_coin( value: &ConwayMultiasset, _err: &ValidationError, ) -> Result, ValidationError> { - let mut res: Vec<(PolicyId, _)> = Vec::new(); - for (policy, assets) in value.iter() { - let mut aa: Vec<(AssetName, PositiveCoin)> = Vec::new(); - for (asset_name, amount) in assets.iter() { - aa.push(( - asset_name.clone(), - PositiveCoin::try_from(i64::from(amount) as u64).unwrap(), - )); - } - res.push((*policy, aa.into_iter().collect())); - } - Ok(res.into_iter().collect()) + let result: Vec<(PolicyId, BTreeMap)> = value + .iter() + .map(|(policy, assets)| { + let converted_assets: BTreeMap = assets + .iter() + .map(|(asset_name, amount)| (asset_name.clone(), PositiveCoin::try_from(i64::from(amount) as u64).unwrap())) + .collect(); + (*policy, converted_assets) + }) + .collect(); + Ok(result.into_iter().collect()) } fn add_multiasset_values(first: &Multiasset, second: &Multiasset) -> Multiasset { @@ -538,31 +545,19 @@ fn conway_add_multiasset_non_zero_values( conway_wrap_multiasset(res) } +// Generic functions using value_ops - replacing era-specific versions fn add_same_policy_assets( old_assets: &HashMap, new_assets: &std::collections::BTreeMap, ) -> HashMap { - let mut res: HashMap = old_assets.clone(); - for (asset_name, new_amount) in new_assets.iter() { - match res.get(asset_name) { - Some(old_amount) => res.insert(asset_name.clone(), old_amount + *new_amount), - None => res.insert(asset_name.clone(), *new_amount), - }; - } - res + value_ops::add_same_policy_assets_generic(old_assets, new_assets) } + fn conway_add_same_policy_assets( old_assets: &HashMap, new_assets: &std::collections::BTreeMap, ) -> HashMap { - let mut res: HashMap = old_assets.clone(); - for (asset_name, new_amount) in new_assets.iter() { - match res.get(asset_name) { - Some(old_amount) => res.insert(asset_name.clone(), old_amount + *new_amount), - None => res.insert(asset_name.clone(), *new_amount), - }; - } - res + value_ops::add_same_policy_assets_generic(old_assets, new_assets) } fn conway_add_same_non_zero_policy_assets( @@ -583,19 +578,13 @@ fn conway_add_same_non_zero_policy_assets( } fn wrap_multiasset(input: HashMap>) -> Multiasset { - input - .into_iter() - .map(|(policy, assets)| (policy, assets.into_iter().collect())) - .collect() + value_ops::wrap_multiasset_generic(input) } fn conway_wrap_multiasset( input: HashMap>, ) -> ConwayMultiasset { - input - .into_iter() - .map(|(policy, assets)| (policy, assets.into_iter().collect())) - .collect() + value_ops::wrap_multiasset_generic(input) } pub fn values_are_equal(first: &Value, second: &Value) -> bool { diff --git a/pallas-validate/src/utils/value_ops.rs b/pallas-validate/src/utils/value_ops.rs index 43a3dd345..834357f24 100644 --- a/pallas-validate/src/utils/value_ops.rs +++ b/pallas-validate/src/utils/value_ops.rs @@ -5,7 +5,7 @@ use pallas_primitives::{ conway::{Multiasset as ConwayMultiasset, Value as ConwayValue}, AssetName, Coin, PolicyId, PositiveCoin, }; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use crate::utils::ValidationError; @@ -146,6 +146,93 @@ pub fn find_assets_generic( assets.get(asset_name).copied() } +/// Generic function to add same policy assets +pub fn add_same_policy_assets_generic( + old_assets: &HashMap, + new_assets: &BTreeMap, +) -> HashMap +where + T: Copy + std::ops::Add, +{ + let mut res: HashMap = old_assets.clone(); + for (asset_name, new_amount) in new_assets.iter() { + match res.get(asset_name) { + Some(old_amount) => res.insert(asset_name.clone(), *old_amount + *new_amount), + None => res.insert(asset_name.clone(), *new_amount), + }; + } + res +} + +/// Generic function to wrap multiasset from HashMap to BTreeMap structure +pub fn wrap_multiasset_generic( + input: HashMap> +) -> BTreeMap> +where + T: Clone +{ + input + .into_iter() + .map(|(policy, assets)| (policy, assets.into_iter().collect())) + .collect() +} + +/// Generic function to add two multiassets together +pub fn add_multiasset_values_generic( + first: &M, + second: &M, +) -> BTreeMap> +where + T: Copy + std::ops::Add + Default, + M: MultiassetOps, +{ + let mut res: HashMap> = HashMap::new(); + + // Add assets from first multiasset + for (policy, assets) in first.iter() { + let assets_map: HashMap = assets.iter().map(|(k, v)| (k.clone(), *v)).collect(); + res.insert(*policy, assets_map); + } + + // Add assets from second multiasset + for (policy, new_assets) in second.iter() { + match res.get(policy) { + Some(old_assets) => { + let combined = add_same_policy_assets_generic(old_assets, new_assets); + res.insert(*policy, combined); + }, + None => { + let assets_map: HashMap = new_assets.iter().map(|(k, v)| (k.clone(), *v)).collect(); + res.insert(*policy, assets_map); + }, + } + } + + wrap_multiasset_generic(res) +} + +/// Generic function to coerce multiasset types +pub fn coerce_multiasset_generic( + value: &MFrom, + converter: impl Fn(&TFrom) -> TTo, +) -> MTo +where + MFrom: MultiassetOps, + MTo: FromIterator<(PolicyId, BTreeMap)>, + TFrom: Copy, +{ + value + .iter() + .map(|(policy, assets)| { + let converted_assets: BTreeMap = assets + .iter() + .map(|(asset_name, amount)| (asset_name.clone(), converter(amount))) + .collect(); + (*policy, converted_assets) + }) + .collect() +} + /// Generic function to check if two values are equal pub fn values_are_equal_generic(first: &V, second: &V) -> bool where