diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index 65104c5793..4eaa4833b2 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -6,9 +6,8 @@ description = "An abstract and configurable XCM message executor." version = "0.8.22" [dependencies] -codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.1.3" - +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } xcm = { path = "..", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -21,10 +20,11 @@ frame-support = { git = "https://github.com/paritytech/substrate", branch = "mas default = ["std"] std = [ "codec/std", - "frame-support/std", + "xcm/std", "sp-std/std", "sp-io/std", "sp-arithmetic/std", "sp-core/std", "sp-runtime/std", + "frame-support/std", ] diff --git a/polkadot/xcm/xcm-executor/src/assets.rs b/polkadot/xcm/xcm-executor/src/assets.rs index 79b67425d8..1f37c28d9b 100644 --- a/polkadot/xcm/xcm-executor/src/assets.rs +++ b/polkadot/xcm/xcm-executor/src/assets.rs @@ -14,10 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use sp_std::{prelude::*, mem::swap, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; +use sp_std::{prelude::*, mem, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; use xcm::v0::{MultiAsset, MultiLocation, AssetInstance}; use sp_runtime::RuntimeDebug; +/// Classification of an asset being concrete or abstract. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] pub enum AssetId { Concrete(MultiLocation), @@ -25,6 +26,7 @@ pub enum AssetId { } impl AssetId { + /// Prepend a MultiLocation to a concrete asset, giving it a new root location. pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { if let AssetId::Concrete(ref mut l) = self { l.prepend_with(prepend.clone()).map_err(|_| ())?; @@ -33,6 +35,7 @@ impl AssetId { } } +/// List of concretely identified fungible and non-fungible assets. #[derive(Default, Clone, RuntimeDebug)] pub struct Assets { pub fungible: BTreeMap, @@ -56,6 +59,25 @@ impl From for Vec { } impl Assets { + /// An iterator over the fungible assets. + pub fn fungible_assets_iter<'a>(&'a self) -> impl Iterator + 'a { + self.fungible.iter() + .map(|(id, &amount)| match id.clone() { + AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, + AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, + }) + } + + /// An iterator over the non-fungible assets. + pub fn non_fungible_assets_iter<'a>(&'a self) -> impl Iterator + 'a { + self.non_fungible.iter() + .map(|&(ref class, ref instance)| match class.clone() { + AssetId::Concrete(class) => MultiAsset::ConcreteNonFungible { class, instance: instance.clone() }, + AssetId::Abstract(class) => MultiAsset::AbstractNonFungible { class, instance: instance.clone() }, + }) + } + + /// An iterator over all assets. pub fn into_assets_iter(self) -> impl Iterator { let fungible = self.fungible.into_iter() .map(|(id, amount)| match id { @@ -70,45 +92,35 @@ impl Assets { fungible.chain(non_fungible) } + /// An iterator over all assets. pub fn assets_iter<'a>(&'a self) -> impl Iterator + 'a { - let fungible = self.fungible.iter() - .map(|(id, &amount)| match id.clone() { - AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, - AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, - }); - let non_fungible = self.non_fungible.iter() - .map(|&(ref class, ref instance)| match class.clone() { - AssetId::Concrete(class) => MultiAsset::ConcreteNonFungible { class, instance: instance.clone() }, - AssetId::Abstract(class) => MultiAsset::AbstractNonFungible { class, instance: instance.clone() }, - }); + let fungible = self.fungible_assets_iter(); + let non_fungible = self.non_fungible_assets_iter(); fungible.chain(non_fungible) } - /// Modify `self` to include `MultiAsset`, saturating if necessary. + /// Modify `self` to include a `MultiAsset`, saturating if necessary. + /// Only works on concretely identified assets; wildcards will be swallowed without error. pub fn saturating_subsume(&mut self, asset: MultiAsset) { match asset { MultiAsset::ConcreteFungible { id, amount } => { - self.fungible - .entry(AssetId::Concrete(id)) - .and_modify(|e| *e = e.saturating_add(amount)) - .or_insert(amount); + self.saturating_subsume_fungible(AssetId::Concrete(id), amount); } MultiAsset::AbstractFungible { id, amount } => { - self.fungible - .entry(AssetId::Abstract(id)) - .and_modify(|e| *e = e.saturating_add(amount)) - .or_insert(amount); + self.saturating_subsume_fungible(AssetId::Abstract(id), amount); } MultiAsset::ConcreteNonFungible { class, instance} => { - self.non_fungible.insert((AssetId::Concrete(class), instance)); + self.saturating_subsume_non_fungible(AssetId::Concrete(class), instance); } MultiAsset::AbstractNonFungible { class, instance} => { - self.non_fungible.insert((AssetId::Abstract(class), instance)); + self.saturating_subsume_non_fungible(AssetId::Abstract(class), instance); } _ => (), } } + /// Modify `self` to include a new fungible asset by `id` and `amount`, + /// saturating if necessary. pub fn saturating_subsume_fungible(&mut self, id: AssetId, amount: u128) { self.fungible .entry(id) @@ -116,6 +128,7 @@ impl Assets { .or_insert(amount); } + /// Modify `self` to include a new non-fungible asset by `class` and `instance`. pub fn saturating_subsume_non_fungible(&mut self, class: AssetId, instance: AssetInstance) { self.non_fungible.insert((class, instance)); } @@ -126,12 +139,12 @@ impl Assets { /// ensure that any internal asset IDs are able to be prepended without overflow. pub fn reanchor(&mut self, prepend: &MultiLocation) { let mut fungible = Default::default(); - sp_std::mem::swap(&mut self.fungible, &mut fungible); + mem::swap(&mut self.fungible, &mut fungible); self.fungible = fungible.into_iter() .map(|(mut id, amount)| { let _ = id.reanchor(prepend); (id, amount) }) .collect(); let mut non_fungible = Default::default(); - sp_std::mem::swap(&mut self.non_fungible, &mut non_fungible); + mem::swap(&mut self.non_fungible, &mut non_fungible); self.non_fungible = non_fungible.into_iter() .map(|(mut class, inst)| { let _ = class.reanchor(prepend); (class, inst) }) .collect(); @@ -140,12 +153,93 @@ impl Assets { /// Return the assets in `self`, but (asset-wise) of no greater value than `assets`. /// /// Result is undefined if `assets` includes elements which match to the same asset more than once. - pub fn min<'a, I: Iterator>(&self, assets: I) -> Self { + /// + /// Example: + /// + /// ``` + /// use xcm_executor::Assets; + /// use xcm::v0::{MultiAsset, MultiLocation}; + /// let assets_i_have: Assets = vec![ + /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, + /// MultiAsset::AbstractFungible { id: vec![0], amount: 100 }, + /// ].into(); + /// let assets_they_want: Assets = vec![ + /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 200 }, + /// MultiAsset::AbstractFungible { id: vec![0], amount: 50 }, + /// ].into(); + /// + /// let assets_we_can_trade: Assets = assets_i_have.min(assets_they_want.assets_iter()); + /// assert_eq!(assets_we_can_trade.into_assets_iter().collect::>(), vec![ + /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, + /// MultiAsset::AbstractFungible { id: vec![0], amount: 50 }, + /// ]); + /// ``` + pub fn min<'a, M, I>(&self, assets: I) -> Self + where + M: 'a + sp_std::borrow::Borrow, + I: IntoIterator, + { let mut result = Assets::default(); for asset in assets.into_iter() { - match asset { + match asset.borrow() { MultiAsset::None => (), MultiAsset::All => return self.clone(), + MultiAsset::AllFungible => { + // Replace `result.fungible` with all fungible assets, + // keeping `result.non_fungible` the same. + result = Assets { + fungible: self.fungible.clone(), + non_fungible: result.non_fungible, + } + }, + MultiAsset::AllNonFungible => { + // Replace `result.non_fungible` with all non-fungible assets, + // keeping `result.fungible` the same. + result = Assets { + fungible: result.fungible, + non_fungible: self.non_fungible.clone(), + } + }, + MultiAsset::AllAbstractFungible { id } => { + for asset in self.fungible_assets_iter() { + match &asset { + MultiAsset::AbstractFungible { id: identifier, .. } => { + if id == identifier { result.saturating_subsume(asset) } + }, + _ => (), + } + } + }, + MultiAsset::AllAbstractNonFungible { class } => { + for asset in self.non_fungible_assets_iter() { + match &asset { + MultiAsset::AbstractNonFungible { class: c, .. } => { + if class == c { result.saturating_subsume(asset) } + }, + _ => (), + } + } + } + MultiAsset::AllConcreteFungible { id } => { + for asset in self.fungible_assets_iter() { + match &asset { + MultiAsset::ConcreteFungible { id: identifier, .. } => { + if id == identifier { result.saturating_subsume(asset) } + }, + _ => (), + } + } + }, + MultiAsset::AllConcreteNonFungible { class } => { + for asset in self.non_fungible_assets_iter() { + match &asset { + MultiAsset::ConcreteNonFungible { class: c, .. } => { + if class == c { result.saturating_subsume(asset) } + }, + _ => (), + } + } + } x @ MultiAsset::ConcreteFungible { .. } | x @ MultiAsset::AbstractFungible { .. } => { let (id, amount) = match x { MultiAsset::ConcreteFungible { id, amount } => (AssetId::Concrete(id.clone()), *amount), @@ -153,9 +247,9 @@ impl Assets { _ => unreachable!(), }; if let Some(v) = self.fungible.get(&id) { - result.saturating_subsume_fungible(id, amount.max(*v)); + result.saturating_subsume_fungible(id, amount.min(*v)); } - } + }, x @ MultiAsset::ConcreteNonFungible { .. } | x @ MultiAsset::AbstractNonFungible { .. } => { let (class, instance) = match x { MultiAsset::ConcreteNonFungible { class, instance } => (AssetId::Concrete(class.clone()), instance.clone()), @@ -167,14 +261,6 @@ impl Assets { result.non_fungible.insert(item); } } - // TODO: implement partial wildcards. - _ => (), - // MultiAsset::AllFungible - // | MultiAsset::AllNonFungible - // | MultiAsset::AllAbstractFungible { id } - // | MultiAsset::AllAbstractNonFungible { class } - // | MultiAsset::AllConcreteFungible { id } - // | MultiAsset::AllConcreteNonFungible { class } => (), } } result @@ -184,12 +270,87 @@ impl Assets { /// assets taken. /// /// Wildcards work. - pub fn saturating_take(&mut self, assets: Vec) -> Assets { + /// + /// Example: + /// + /// ``` + /// use xcm_executor::Assets; + /// use xcm::v0::{MultiAsset, MultiLocation}; + /// let mut assets_i_have: Assets = vec![ + /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, + /// MultiAsset::AbstractFungible { id: vec![0], amount: 100 }, + /// ].into(); + /// let assets_they_want = vec![ + /// MultiAsset::AllAbstractFungible { id: vec![0] }, + /// ]; + /// + /// let assets_they_took: Assets = assets_i_have.saturating_take(assets_they_want); + /// assert_eq!(assets_they_took.into_assets_iter().collect::>(), vec![ + /// MultiAsset::AbstractFungible { id: vec![0], amount: 100 }, + /// ]); + /// assert_eq!(assets_i_have.into_assets_iter().collect::>(), vec![ + /// MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount: 100 }, + /// ]); + /// ``` + pub fn saturating_take(&mut self, assets: I) -> Assets + where + I: IntoIterator, + { let mut result = Assets::default(); for asset in assets.into_iter() { match asset { MultiAsset::None => (), MultiAsset::All => return self.swapped(Assets::default()), + MultiAsset::AllFungible => { + // Remove all fungible assets, and copy them into `result`. + let fungible = mem::replace(&mut self.fungible, Default::default()); + fungible.into_iter().for_each(|(id, amount)| { + result.saturating_subsume_fungible(id, amount); + }) + }, + MultiAsset::AllNonFungible => { + // Remove all non-fungible assets, and copy them into `result`. + let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); + non_fungible.into_iter().for_each(|(class, instance)| { + result.saturating_subsume_non_fungible(class, instance); + }); + }, + x @ MultiAsset::AllAbstractFungible { .. } | x @ MultiAsset::AllConcreteFungible { .. } => { + let id = match x { + MultiAsset::AllConcreteFungible { id } => AssetId::Concrete(id), + MultiAsset::AllAbstractFungible { id } => AssetId::Abstract(id), + _ => unreachable!(), + }; + // At the end of this block, we will be left with only the non-matching fungibles. + let mut non_matching_fungibles = BTreeMap::::new(); + let fungible = mem::replace(&mut self.fungible, Default::default()); + fungible.into_iter().for_each(|(iden, amount)| { + if iden == id { + result.saturating_subsume_fungible(iden, amount); + } else { + non_matching_fungibles.insert(iden, amount); + } + }); + self.fungible = non_matching_fungibles; + }, + x @ MultiAsset::AllAbstractNonFungible { .. } | x @ MultiAsset::AllConcreteNonFungible { .. } => { + let class = match x { + MultiAsset::AllConcreteNonFungible { class } => AssetId::Concrete(class), + MultiAsset::AllAbstractNonFungible { class } => AssetId::Abstract(class), + _ => unreachable!(), + }; + // At the end of this block, we will be left with only the non-matching non-fungibles. + let mut non_matching_non_fungibles = BTreeSet::<(AssetId, AssetInstance)>::new(); + let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); + non_fungible.into_iter().for_each(|(c, instance)| { + if class == c { + result.saturating_subsume_non_fungible(c, instance); + } else { + non_matching_non_fungibles.insert((c, instance)); + } + }); + self.non_fungible = non_matching_non_fungibles; + }, x @ MultiAsset::ConcreteFungible {..} | x @ MultiAsset::AbstractFungible {..} => { let (id, amount) = match x { MultiAsset::ConcreteFungible { id, amount } => (AssetId::Concrete(id), amount), @@ -198,14 +359,16 @@ impl Assets { }; // remove the maxmimum possible up to id/amount from self, add the removed onto // result - self.fungible.entry(id.clone()) - .and_modify(|e| if *e >= amount { + let maybe_value = self.fungible.get(&id); + if let Some(&e) = maybe_value { + if e > amount { + self.fungible.insert(id.clone(), e - amount); result.saturating_subsume_fungible(id, amount); - *e = *e - amount; } else { - result.saturating_subsume_fungible(id, *e); - *e = 0 - }); + self.fungible.remove(&id); + result.saturating_subsume_fungible(id, e.clone()); + } + } } x @ MultiAsset::ConcreteNonFungible {..} | x @ MultiAsset::AbstractNonFungible {..} => { let (class, instance) = match x { @@ -216,26 +379,244 @@ impl Assets { // remove the maxmimum possible up to id/amount from self, add the removed onto // result if let Some(entry) = self.non_fungible.take(&(class, instance)) { - self.non_fungible.insert(entry); + result.non_fungible.insert(entry); } } - // TODO: implement partial wildcards. - _ => { - Default::default() - } - // MultiAsset::AllFungible - // | MultiAsset::AllNonFungible - // | MultiAsset::AllAbstractFungible { id } - // | MultiAsset::AllAbstractNonFungible { class } - // | MultiAsset::AllConcreteFungible { id } - // | MultiAsset::AllConcreteNonFungible { class } => (), } } result } + /// Swaps two mutable Assets, without deinitializing either one. pub fn swapped(&mut self, mut with: Assets) -> Self { - swap(&mut *self, &mut with); + mem::swap(&mut *self, &mut with); with } } + +#[cfg(test)] +mod tests { + use super::*; + #[allow(non_snake_case)] + fn AF(id: u8, amount: u128) -> MultiAsset { + MultiAsset::AbstractFungible { id: vec![id], amount } + } + #[allow(non_snake_case)] + fn ANF(class: u8, instance_id: u128) -> MultiAsset { + MultiAsset::AbstractNonFungible { class: vec![class], instance: AssetInstance::Index { id: instance_id } } + } + #[allow(non_snake_case)] + fn CF(amount: u128) -> MultiAsset { + MultiAsset::ConcreteFungible { id: MultiLocation::Null, amount } + } + #[allow(non_snake_case)] + fn CNF(instance_id: u128) -> MultiAsset { + MultiAsset::ConcreteNonFungible { class: MultiLocation::Null, instance: AssetInstance::Index { id: instance_id } } + } + + fn test_assets() -> Assets { + let mut assets_vec: Vec = Vec::new(); + assets_vec.push(AF(1, 100)); + assets_vec.push(ANF(2, 200)); + assets_vec.push(CF(300)); + assets_vec.push(CNF(400)); + assets_vec.into() + } + + #[test] + fn into_assets_iter_works() { + let assets = test_assets(); + let mut iter = assets.into_assets_iter(); + // Order defined by implementation: CF, AF, CNF, ANF + assert_eq!(Some(CF(300)), iter.next()); + assert_eq!(Some(AF(1, 100)), iter.next()); + assert_eq!(Some(CNF(400)), iter.next()); + assert_eq!(Some(ANF(2, 200)), iter.next()); + assert_eq!(None, iter.next()); + } + + #[test] + fn assets_into_works() { + let mut assets_vec: Vec = Vec::new(); + assets_vec.push(AF(1, 100)); + assets_vec.push(ANF(2, 200)); + assets_vec.push(CF(300)); + assets_vec.push(CNF(400)); + // Push same group of tokens again + assets_vec.push(AF(1, 100)); + assets_vec.push(ANF(2, 200)); + assets_vec.push(CF(300)); + assets_vec.push(CNF(400)); + + let assets: Assets = assets_vec.into(); + let mut iter = assets.into_assets_iter(); + // Fungibles add + assert_eq!(Some(CF(600)), iter.next()); + assert_eq!(Some(AF(1, 200)), iter.next()); + // Non-fungibles collapse + assert_eq!(Some(CNF(400)), iter.next()); + assert_eq!(Some(ANF(2, 200)), iter.next()); + assert_eq!(None, iter.next()); + } + + #[test] + fn min_all_and_none_works() { + let assets = test_assets(); + let none = vec![MultiAsset::None]; + let all = vec![MultiAsset::All]; + + let none_min = assets.min(none.iter()); + assert_eq!(None, none_min.assets_iter().next()); + let all_min = assets.min(all.iter()); + assert!(all_min.assets_iter().eq(assets.assets_iter())); + } + + #[test] + fn min_all_fungible_and_all_non_fungible_works() { + let assets = test_assets(); + let fungible = vec![MultiAsset::AllFungible]; + let non_fungible = vec![MultiAsset::AllNonFungible]; + + let fungible = assets.min(fungible.iter()); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![CF(300), AF(1, 100)]); + let non_fungible = assets.min(non_fungible.iter()); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![CNF(400), ANF(2, 200)]); + } + + #[test] + fn min_all_abstract_works() { + let assets = test_assets(); + let fungible = vec![MultiAsset::AllAbstractFungible { id: vec![1] }]; + let non_fungible = vec![MultiAsset::AllAbstractNonFungible { class: vec![2] }]; + + let fungible = assets.min(fungible.iter()); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![AF(1, 100)]); + let non_fungible = assets.min(non_fungible.iter()); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![ANF(2, 200)]); + } + + #[test] + fn min_all_concrete_works() { + let assets = test_assets(); + let fungible = vec![MultiAsset::AllConcreteFungible { id: MultiLocation::Null }]; + let non_fungible = vec![MultiAsset::AllConcreteNonFungible { class: MultiLocation::Null }]; + + let fungible = assets.min(fungible.iter()); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![CF(300)]); + let non_fungible = assets.min(non_fungible.iter()); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![CNF(400)]); + } + + #[test] + fn min_basic_works() { + let assets1 = test_assets(); + + let mut assets2_vec: Vec = Vec::new(); + // This is less than 100, so it will decrease to 50 + assets2_vec.push(AF(1, 50)); + // This asset does not exist, so not included + assets2_vec.push(ANF(2, 400)); + // This is more then 300, so it should stay at 300 + assets2_vec.push(CF(600)); + // This asset should be included + assets2_vec.push(CNF(400)); + let assets2: Assets = assets2_vec.into(); + + let assets_min = assets1.min(assets2.assets_iter()); + let assets_min = assets_min.into_assets_iter().collect::>(); + assert_eq!(assets_min, vec![CF(300), AF(1, 50), CNF(400)]); + } + + #[test] + fn saturating_take_all_and_none_works() { + let mut assets = test_assets(); + let none = vec![MultiAsset::None]; + let all = vec![MultiAsset::All]; + + let taken_none = assets.saturating_take(none); + assert_eq!(None, taken_none.assets_iter().next()); + let taken_all = assets.saturating_take(all); + // Everything taken + assert_eq!(None, assets.assets_iter().next()); + let all_iter = taken_all.assets_iter(); + assert!(all_iter.eq(test_assets().assets_iter())); + } + + #[test] + fn saturating_take_all_fungible_and_all_non_fungible_works() { + let mut assets = test_assets(); + let fungible = vec![MultiAsset::AllFungible]; + let non_fungible = vec![MultiAsset::AllNonFungible]; + + let fungible = assets.saturating_take(fungible); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![CF(300), AF(1, 100)]); + let non_fungible = assets.saturating_take(non_fungible); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, [CNF(400), ANF(2, 200)]); + // Assets completely drained + assert_eq!(None, assets.assets_iter().next()); + } + + #[test] + fn saturating_take_all_abstract_works() { + let mut assets = test_assets(); + let fungible = vec![MultiAsset::AllAbstractFungible { id: vec![1] }]; + let non_fungible = vec![MultiAsset::AllAbstractNonFungible { class: vec![2] }]; + + let fungible = assets.saturating_take(fungible); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![AF(1, 100)]); + let non_fungible = assets.saturating_take(non_fungible); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![ANF(2, 200)]); + // Assets drained of abstract + let final_assets = assets.assets_iter().collect::>(); + assert_eq!(final_assets, vec![CF(300), CNF(400)]); + } + + #[test] + fn saturating_take_all_concrete_works() { + let mut assets = test_assets(); + let fungible = vec![MultiAsset::AllConcreteFungible { id: MultiLocation::Null }]; + let non_fungible = vec![MultiAsset::AllConcreteNonFungible { class: MultiLocation::Null }]; + + let fungible = assets.saturating_take(fungible); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![CF(300)]); + let non_fungible = assets.saturating_take(non_fungible); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![CNF(400)]); + // Assets drained of concrete + let assets = assets.assets_iter().collect::>(); + assert_eq!(assets, vec![AF(1, 100), ANF(2, 200)]); + } + + #[test] + fn saturating_take_basic_works() { + let mut assets1 = test_assets(); + + let mut assets2_vec: Vec = Vec::new(); + // We should take 50 + assets2_vec.push(AF(1, 50)); + // This asset should not be taken + assets2_vec.push(ANF(2, 400)); + // This is more then 300, so it takes everything + assets2_vec.push(CF(600)); + // This asset should be taken + assets2_vec.push(CNF(400)); + + let taken = assets1.saturating_take(assets2_vec); + let taken = taken.into_assets_iter().collect::>(); + assert_eq!(taken, vec![CF(300), AF(1, 50), CNF(400)]); + + let assets = assets1.into_assets_iter().collect::>(); + assert_eq!(assets, vec![AF(1, 50), ANF(2, 200)]); + } +}