referrerpolicy=no-referrer-when-downgrade

staging_xcm_executor/
assets.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use alloc::{
18	boxed::Box,
19	collections::{
20		btree_map::{self, BTreeMap},
21		btree_set::BTreeSet,
22	},
23	vec::Vec,
24};
25use core::{fmt::Formatter, mem};
26use frame_support::traits::tokens::imbalance::ImbalanceAccounting;
27use xcm::latest::{
28	Asset, AssetFilter, AssetId, AssetInstance, Assets,
29	Fungibility::{Fungible, NonFungible},
30	InteriorLocation, Location, Reanchorable,
31	WildAsset::{All, AllCounted, AllOf, AllOfCounted},
32	WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible},
33};
34
35/// An error emitted by `take` operations.
36#[derive(Debug)]
37pub enum TakeError {
38	/// There was an attempt to take an asset without saturating (enough of) which did not exist.
39	AssetUnderflow(Asset),
40}
41
42/// Helper struct for creating a backup of assets in holding in a safe way.
43///
44/// Duplicating holding involves unsafe cloning of any imbalances, but this type makes sure that
45/// either the backup or the original are dropped without resolving any duplicated imbalances.
46pub struct BackupAssetsInHolding {
47	// private inner holding safely managed by the wrapper
48	inner: AssetsInHolding,
49}
50
51impl BackupAssetsInHolding {
52	/// Clones `other` and keeps it in this safe wrapper that will safely drop duplicated
53	/// imbalances.
54	pub fn safe_backup(other: &AssetsInHolding) -> Self {
55		Self {
56			inner: AssetsInHolding {
57				fungible: other
58					.fungible
59					.iter()
60					.map(|(id, accounting)| (id.clone(), accounting.unsafe_clone()))
61					.collect(),
62				non_fungible: other.non_fungible.clone(),
63			},
64		}
65	}
66
67	/// Replace `target` with the backup held within `self`. It is basically a mem swap so that the
68	/// original holdings of `target` will be dropped without resolving inner imbalances.
69	pub fn restore_into(&mut self, target: &mut AssetsInHolding) {
70		core::mem::swap(target, &mut self.inner);
71	}
72
73	/// This object holds an unsafe clone of `inner` and needs to drop it without resolving its held
74	/// imbalances.
75	pub fn safe_drop(&mut self) {
76		// set amount to 0 so that no accounting is done on imbalance Drop
77		self.inner.fungible.iter_mut().for_each(|(_, accounting)| {
78			accounting.forget_imbalance();
79		});
80	}
81}
82
83impl Drop for BackupAssetsInHolding {
84	fn drop(&mut self) {
85		self.safe_drop();
86	}
87}
88
89/// Map of non-wildcard fungible and non-fungible assets held in the holding register.
90pub struct AssetsInHolding {
91	/// The fungible assets.
92	pub fungible: BTreeMap<AssetId, Box<dyn ImbalanceAccounting<u128>>>,
93	/// The non-fungible assets.
94	// TODO: Consider BTreeMap<AssetId, BTreeSet<AssetInstance>>
95	//   or even BTreeMap<AssetId, SortedVec<AssetInstance>>
96	pub non_fungible: BTreeSet<(AssetId, AssetInstance)>,
97}
98
99impl PartialEq for AssetsInHolding {
100	fn eq(&self, other: &Self) -> bool {
101		if self.non_fungible != other.non_fungible {
102			return false;
103		}
104		if self.fungible.len() != other.fungible.len() {
105			return false;
106		}
107		if !self
108			.fungible
109			.iter()
110			.zip(other.fungible.iter())
111			.all(|(left, right)| left.0 == right.0 && left.1.amount() == right.1.amount())
112		{
113			return false;
114		}
115		true
116	}
117}
118
119impl core::fmt::Debug for AssetsInHolding {
120	fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
121		let fungibles: BTreeMap<&AssetId, u128> =
122			self.fungible.iter().map(|(id, accounting)| (id, accounting.amount())).collect();
123		f.debug_struct("AssetsInHolding")
124			.field("fungible", &fungibles)
125			.field("non_fungible", &self.non_fungible)
126			.finish()
127	}
128}
129
130impl AssetsInHolding {
131	/// New value, containing no assets.
132	pub fn new() -> Self {
133		AssetsInHolding { fungible: BTreeMap::new(), non_fungible: BTreeSet::new() }
134	}
135
136	/// New holding containing a single fungible imbalance.
137	pub fn new_from_fungible_credit(
138		asset: AssetId,
139		credit: Box<dyn ImbalanceAccounting<u128>>,
140	) -> Self {
141		let mut new = AssetsInHolding { fungible: BTreeMap::new(), non_fungible: BTreeSet::new() };
142		new.fungible.insert(asset, credit);
143		new
144	}
145
146	/// New holding containing a single non fungible.
147	pub fn new_from_non_fungible(class: AssetId, instance: AssetInstance) -> Self {
148		let mut new = AssetsInHolding { fungible: BTreeMap::new(), non_fungible: BTreeSet::new() };
149		new.non_fungible.insert((class, instance));
150		new
151	}
152
153	/// Total number of distinct assets.
154	pub fn len(&self) -> usize {
155		self.fungible.len() + self.non_fungible.len()
156	}
157
158	/// Returns `true` if `self` contains no assets.
159	pub fn is_empty(&self) -> bool {
160		self.fungible.is_empty() && self.non_fungible.is_empty()
161	}
162
163	/// A borrowing iterator over the fungible assets.
164	pub fn fungible_assets_iter(&self) -> impl Iterator<Item = Asset> + '_ {
165		self.fungible
166			.iter()
167			.map(|(id, accounting)| Asset { fun: Fungible(accounting.amount()), id: id.clone() })
168	}
169
170	/// A borrowing iterator over the non-fungible assets.
171	pub fn non_fungible_assets_iter(&self) -> impl Iterator<Item = Asset> + '_ {
172		self.non_fungible
173			.iter()
174			.map(|(id, instance)| Asset { fun: NonFungible(*instance), id: id.clone() })
175	}
176
177	/// A consuming iterator over all assets.
178	pub fn into_assets_iter(self) -> impl Iterator<Item = Asset> {
179		self.fungible
180			.into_iter()
181			.map(|(id, accounting)| Asset { fun: Fungible(accounting.amount()), id })
182			.chain(
183				self.non_fungible
184					.into_iter()
185					.map(|(id, instance)| Asset { fun: NonFungible(instance), id }),
186			)
187	}
188
189	/// A consuming iterator that yields one [`AssetsInHolding`] per individual asset —
190	/// each fungible imbalance and each non-fungible instance — by transferring ownership
191	/// out of the underlying maps. No `AssetId` clones and no `BTreeMap` lookups, unlike
192	/// the borrowing-iterator + `try_take` pattern.
193	///
194	/// Iteration order matches [`Self::assets_iter`] and [`Self::into_assets_iter`]:
195	/// fungibles in sorted-by-`AssetId` order, then non-fungibles in sorted order.
196	pub fn into_per_asset_holdings(self) -> impl Iterator<Item = AssetsInHolding> {
197		let fungibles = self.fungible.into_iter().map(|(asset_id, accounting)| {
198			AssetsInHolding::new_from_fungible_credit(asset_id, accounting)
199		});
200		let non_fungibles = self
201			.non_fungible
202			.into_iter()
203			.map(|(class, instance)| AssetsInHolding::new_from_non_fungible(class, instance));
204		fungibles.chain(non_fungibles)
205	}
206
207	/// A borrowing iterator over all assets.
208	pub fn assets_iter(&self) -> impl Iterator<Item = Asset> + '_ {
209		self.fungible_assets_iter().chain(self.non_fungible_assets_iter())
210	}
211
212	/// Mutate `self` to contain all given `assets`, saturating if necessary.
213	///
214	/// NOTE: [`AssetsInHolding`] are always sorted
215	pub fn subsume_assets(&mut self, assets: AssetsInHolding) {
216		// for fungibles, find matching fungibles and sum their amounts so we end-up having just
217		// single such fungible but with increased amount inside
218		for (asset_id, accounting) in assets.fungible.into_iter() {
219			match self.fungible.entry(asset_id) {
220				btree_map::Entry::Occupied(mut e) => {
221					e.get_mut().saturating_subsume(accounting);
222				},
223				btree_map::Entry::Vacant(e) => {
224					e.insert(accounting);
225				},
226			}
227		}
228		// for non-fungibles, every entry is unique so there is no notion of amount to sum-up
229		// together if there is the same non-fungible in both holdings (same instance_id) these
230		// will be collapsed into just single one
231		let mut non_fungible = assets.non_fungible;
232		self.non_fungible.append(&mut non_fungible);
233	}
234
235	/// Swaps two mutable AssetsInHolding, without deinitializing either one.
236	pub fn swapped(&mut self, mut with: AssetsInHolding) -> Self {
237		mem::swap(&mut *self, &mut with);
238		with
239	}
240
241	/// Consume `self` and return `Assets` as assets interpreted from the perspective of a `target`
242	/// chain. The local chain's `context` is provided.
243	///
244	/// Any assets which were unable to be reanchored are introduced into `failed_bin` instead.
245	///
246	/// WARNING: this will drop/resolve any inner imbalances for the reanchored assets. Meant to be
247	/// used in crosschain operations where the asset is consumed (imbalance dropped/resolved)
248	/// locally, and a reanchored version of it is to be minted on a remote location.
249	pub fn reanchor_and_burn_local(
250		self,
251		target: &Location,
252		context: &InteriorLocation,
253		failed_bin: &mut Self,
254	) -> Assets {
255		let mut assets: Vec<Asset> = self
256			.fungible
257			.into_iter()
258			.filter_map(|(mut id, accounting)| match id.reanchor(target, context) {
259				Ok(()) => Some(Asset::from((id, Fungible(accounting.amount())))),
260				Err(()) => {
261					failed_bin.fungible.insert(id, accounting);
262					None
263				},
264			})
265			.chain(self.non_fungible.into_iter().filter_map(|(mut class, inst)| {
266				match class.reanchor(target, context) {
267					Ok(()) => Some(Asset::from((class, inst))),
268					Err(()) => {
269						failed_bin.non_fungible.insert((class, inst));
270						None
271					},
272				}
273			}))
274			.collect();
275		assets.sort();
276		assets.into()
277	}
278
279	/// Return all inner assets, but interpreted from the perspective of a `target` chain. The local
280	/// chain's `context` is provided.
281	///
282	/// **Warning**: This method returns `Assets` which only contains amounts (not imbalances).
283	/// The returned `Assets` is suitable for cross-chain messaging but does not preserve the
284	/// imbalance accounting semantics of the original `AssetsInHolding`. Do not use the returned
285	/// value for local balance operations that require imbalance tracking.
286	pub fn reanchored_assets(&self, target: &Location, context: &InteriorLocation) -> Assets {
287		let mut assets: Vec<Asset> = self
288			.fungible
289			.iter()
290			.filter_map(|(id, accounting)| match id.clone().reanchored(target, context) {
291				Ok(new_id) => Some(Asset::from((new_id, Fungible(accounting.amount())))),
292				Err(()) => None,
293			})
294			.chain(self.non_fungible.iter().filter_map(|(class, inst)| {
295				match class.clone().reanchored(target, context) {
296					Ok(new_class) => Some(Asset::from((new_class, *inst))),
297					Err(()) => None,
298				}
299			}))
300			.collect();
301		assets.sort();
302		assets.into()
303	}
304
305	/// Returns `true` if `asset` is contained within `self`.
306	pub fn contains_asset(&self, asset: &Asset) -> bool {
307		match asset {
308			Asset { fun: Fungible(amount), id } => {
309				self.fungible.get(id).map_or(false, |a| a.amount() >= *amount)
310			},
311			Asset { fun: NonFungible(instance), id } => {
312				self.non_fungible.contains(&(id.clone(), *instance))
313			},
314		}
315	}
316
317	/// Returns `true` if all `assets` are contained within `self`.
318	pub fn contains_assets(&self, assets: &Assets) -> bool {
319		assets.inner().iter().all(|a| self.contains_asset(a))
320	}
321
322	/// Returns an error unless all `assets` are contained in `self`.
323	pub fn ensure_contains(&self, assets: &Assets) -> Result<(), TakeError> {
324		for asset in assets.inner().iter() {
325			match asset {
326				Asset { fun: Fungible(amount), id } => {
327					if self.fungible.get(id).map_or(true, |a| a.amount() < *amount) {
328						return Err(TakeError::AssetUnderflow((id.clone(), *amount).into()));
329					}
330				},
331				Asset { fun: NonFungible(instance), id } => {
332					let id_instance = (id.clone(), *instance);
333					if !self.non_fungible.contains(&id_instance) {
334						return Err(TakeError::AssetUnderflow(id_instance.into()));
335					}
336				},
337			}
338		}
339		return Ok(());
340	}
341
342	/// Mutates `self` to its original value less `mask` and returns assets that were removed.
343	///
344	/// If `saturate` is `true`, then `self` is considered to be masked by `mask`, thereby avoiding
345	/// any attempt at reducing it by assets it does not contain. In this case, the function is
346	/// infallible. If `saturate` is `false` and `mask` references a definite asset which `self`
347	/// does not contain then an error is returned.
348	///
349	/// The number of unique assets which are removed will respect the `count` parameter in the
350	/// counted wildcard variants.
351	///
352	/// Returns `Ok` with the definite assets token from `self` and mutates `self` to its value
353	/// minus `mask`. Returns `Err` in the non-saturating case where `self` did not contain (enough
354	/// of) a definite asset to be removed.
355	fn general_take(
356		&mut self,
357		mask: AssetFilter,
358		saturate: bool,
359	) -> Result<AssetsInHolding, TakeError> {
360		let mut taken = AssetsInHolding::new();
361		let maybe_limit = mask.limit().map(|x| x as usize);
362		match mask {
363			AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => match maybe_limit {
364				None => return Ok(self.swapped(AssetsInHolding::new())),
365				Some(limit) if self.len() <= limit => {
366					return Ok(self.swapped(AssetsInHolding::new()))
367				},
368				Some(0) => return Ok(AssetsInHolding::new()),
369				Some(limit) => {
370					let fungible = mem::replace(&mut self.fungible, Default::default());
371					fungible.into_iter().for_each(|(c, amount)| {
372						if taken.len() < limit {
373							taken.fungible.insert(c, amount);
374						} else {
375							self.fungible.insert(c, amount);
376						}
377					});
378					let non_fungible = mem::replace(&mut self.non_fungible, Default::default());
379					non_fungible.into_iter().for_each(|(c, instance)| {
380						if taken.len() < limit {
381							taken.non_fungible.insert((c, instance));
382						} else {
383							self.non_fungible.insert((c, instance));
384						}
385					});
386				},
387			},
388			AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) |
389			AssetFilter::Wild(AllOf { fun: WildFungible, id }) => {
390				if maybe_limit.map_or(true, |l| l >= 1) {
391					if let Some((id, amount)) = self.fungible.remove_entry(&id) {
392						taken.fungible.insert(id, amount);
393					}
394				}
395			},
396			AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) |
397			AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => {
398				let non_fungible = mem::replace(&mut self.non_fungible, Default::default());
399				non_fungible.into_iter().for_each(|(c, instance)| {
400					if c == id && maybe_limit.map_or(true, |l| taken.len() < l) {
401						taken.non_fungible.insert((c, instance));
402					} else {
403						self.non_fungible.insert((c, instance));
404					}
405				});
406			},
407			AssetFilter::Definite(assets) => {
408				if !saturate {
409					self.ensure_contains(&assets)?;
410				}
411				for asset in assets.into_inner().into_iter() {
412					match asset {
413						Asset { fun: Fungible(amount), id } => {
414							let (remove, balance) = match self.fungible.get_mut(&id) {
415								Some(self_amount) => {
416									// Ok to use `saturating_take()` because we checked with
417									// `self.ensure_contains()` above against `saturate` flag
418									let balance = self_amount.saturating_take(amount);
419									(self_amount.amount() == 0, Some(balance))
420								},
421								None => (false, None),
422							};
423							if remove {
424								self.fungible.remove(&id);
425							}
426							if let Some(balance) = balance {
427								let other = Self::new_from_fungible_credit(id, balance);
428								taken.subsume_assets(other);
429							}
430						},
431						Asset { fun: NonFungible(instance), id } => {
432							let id_instance = (id, instance);
433							if self.non_fungible.remove(&id_instance) {
434								taken.non_fungible.insert((id_instance.0, id_instance.1));
435							}
436						},
437					}
438				}
439			},
440		}
441		Ok(taken)
442	}
443
444	/// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least
445	/// `mask`.
446	///
447	/// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its
448	/// value minus `mask` if `self` contains `asset`, and return `Err` otherwise.
449	pub fn saturating_take(&mut self, asset: AssetFilter) -> Self {
450		self.general_take(asset, true)
451			.expect("general_take never results in error when saturating")
452	}
453
454	/// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least
455	/// `mask`.
456	///
457	/// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its
458	/// value minus `asset` if `self` contains `asset`, and return `Err` otherwise.
459	pub fn try_take(&mut self, mask: AssetFilter) -> Result<Self, TakeError> {
460		self.general_take(mask, false)
461	}
462
463	/// Return the assets in `self`, but (asset-wise) of no greater value than `mask`.
464	///
465	/// The number of unique assets which are returned will respect the `count` parameter in the
466	/// counted wildcard variants of `mask`.
467	///
468	/// Example:
469	///
470	/// ```
471	/// use staging_xcm_executor::AssetsInHolding;
472	/// use xcm::latest::prelude::*;
473	/// // Note: In real usage, AssetsInHolding is created through TransactAsset operations
474	/// // For this example, we use Assets type instead to demonstrate the min() output
475	/// let assets_i_have: Assets = vec![ (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 100).into() ].into();
476	/// let assets_they_want: AssetFilter = vec![ (Here, 200).into(), (Junctions::from([GeneralIndex(0)]), 50).into() ].into();
477	///
478	/// // Normally you would call this on AssetsInHolding, but for documentation purposes:
479	/// // let assets_we_can_trade: Assets = assets_i_have.min(&assets_they_want);
480	/// // assert_eq!(assets_we_can_trade.inner(), &vec![
481	/// // 	(Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 50).into(),
482	/// // ]);
483	/// ```
484	pub fn min(&self, mask: &AssetFilter) -> Assets {
485		let mut masked = Assets::new();
486		let maybe_limit = mask.limit().map(|x| x as usize);
487		if maybe_limit.map_or(false, |l| l == 0) {
488			return masked;
489		}
490		match mask {
491			AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => {
492				if maybe_limit.map_or(true, |l| self.len() <= l) {
493					return self.assets_iter().collect::<Vec<Asset>>().into();
494				} else {
495					for (c, accounting) in self.fungible.iter() {
496						masked.push((c.clone(), accounting.amount()).into());
497						if maybe_limit.map_or(false, |l| masked.len() >= l) {
498							return masked;
499						}
500					}
501					for (c, instance) in self.non_fungible.iter() {
502						masked.push((c.clone(), *instance).into());
503						if maybe_limit.map_or(false, |l| masked.len() >= l) {
504							return masked;
505						}
506					}
507				}
508			},
509			AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) |
510			AssetFilter::Wild(AllOf { fun: WildFungible, id }) => {
511				if let Some(accounting) = self.fungible.get(&id) {
512					masked.push((id.clone(), accounting.amount()).into());
513				}
514			},
515			AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) |
516			AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => {
517				for (c, instance) in self.non_fungible.iter() {
518					if c == id {
519						masked.push((c.clone(), *instance).into());
520						if maybe_limit.map_or(false, |l| masked.len() >= l) {
521							return masked;
522						}
523					}
524				}
525			},
526			AssetFilter::Definite(assets) => {
527				for asset in assets.inner().iter() {
528					match asset {
529						Asset { fun: Fungible(amount), id } => {
530							if let Some(m) = self.fungible.get(id) {
531								masked
532									.push((id.clone(), Fungible(*amount.min(&m.amount()))).into());
533							}
534						},
535						Asset { fun: NonFungible(instance), id } => {
536							let id_instance = (id.clone(), *instance);
537							if self.non_fungible.contains(&id_instance) {
538								masked.push(id_instance.into());
539							}
540						},
541					}
542				}
543			},
544		}
545		masked
546	}
547
548	/// Clone this holding for testing purposes only.
549	///
550	/// This uses `unsafe_clone()` on the imbalance accounting trait objects,
551	/// which may not maintain proper accounting invariants. Only use in tests.
552	#[cfg(feature = "std")]
553	pub fn unsafe_clone_for_tests(&self) -> Self {
554		Self {
555			fungible: self
556				.fungible
557				.iter()
558				.map(|(id, accounting)| (id.clone(), accounting.unsafe_clone()))
559				.collect(),
560			non_fungible: self.non_fungible.clone(),
561		}
562	}
563}
564
565#[cfg(test)]
566mod tests {
567	use super::*;
568	use crate::tests::mock::*;
569	use alloc::vec;
570	use xcm::latest::prelude::*;
571
572	#[allow(non_snake_case)]
573	/// Concrete fungible constructor
574	fn CF(amount: u128) -> Asset {
575		(Here, amount).into()
576	}
577	#[allow(non_snake_case)]
578	/// Concrete fungible constructor with index for GeneralIndex
579	fn CFG(index: u128, amount: u128) -> Asset {
580		(GeneralIndex(index), amount).into()
581	}
582	#[allow(non_snake_case)]
583	/// Concrete fungible constructor (parent=1)
584	fn CFP(amount: u128) -> Asset {
585		(Parent, amount).into()
586	}
587	#[allow(non_snake_case)]
588	/// Concrete fungible constructor (parent=2)
589	fn CFPP(amount: u128) -> Asset {
590		((Parent, Parent), amount).into()
591	}
592	#[allow(non_snake_case)]
593	/// Concrete non-fungible constructor
594	fn CNF(instance_id: u8) -> Asset {
595		(Here, [instance_id; 4]).into()
596	}
597
598	/// Helper to convert a single Asset into AssetsInHolding for tests
599	fn asset_to_holding(asset: Asset) -> AssetsInHolding {
600		// Since we can't directly convert Asset to AssetsInHolding, we create an empty
601		// holding and manually insert the asset
602		let mut holding = AssetsInHolding::new();
603		match asset.fun {
604			Fungible(amount) => {
605				holding.fungible.insert(asset.id, Box::new(MockCredit(amount)));
606			},
607			NonFungible(instance) => {
608				holding.non_fungible.insert((asset.id, instance));
609			},
610		}
611		holding
612	}
613
614	fn test_assets() -> AssetsInHolding {
615		let mut assets = AssetsInHolding::new();
616		assets.subsume_assets(asset_to_holding(CF(300)));
617		assets.subsume_assets(asset_to_holding(CNF(40)));
618		assets
619	}
620
621	#[test]
622	fn assets_in_holding_order_works() {
623		// populate assets in non-ordered fashion
624		let mut assets = AssetsInHolding::new();
625		assets.subsume_assets(asset_to_holding(CFPP(300)));
626		assets.subsume_assets(asset_to_holding(CFP(200)));
627		assets.subsume_assets(asset_to_holding(CNF(2)));
628		assets.subsume_assets(asset_to_holding(CF(100)));
629		assets.subsume_assets(asset_to_holding(CNF(1)));
630		assets.subsume_assets(asset_to_holding(CFG(10, 400)));
631		assets.subsume_assets(asset_to_holding(CFG(15, 500)));
632
633		// following is the order we expect from AssetsInHolding
634		// - fungibles before non-fungibles
635		// - for fungibles, sort by parent first, if parents match, then by other components like
636		//   general index
637		// - for non-fungibles, sort by instance_id
638		let mut iter = assets.unsafe_clone_for_tests().into_assets_iter();
639		// fungible, order by parent, parent=0
640		assert_eq!(Some(CF(100)), iter.next());
641		// fungible, order by parent then by general index, parent=0, general index=10
642		assert_eq!(Some(CFG(10, 400)), iter.next());
643		// fungible, order by parent then by general index, parent=0, general index=15
644		assert_eq!(Some(CFG(15, 500)), iter.next());
645		// fungible, order by parent, parent=1
646		assert_eq!(Some(CFP(200)), iter.next());
647		// fungible, order by parent, parent=2
648		assert_eq!(Some(CFPP(300)), iter.next());
649		// non-fungible, after fungibles, order by instance id, id=1
650		assert_eq!(Some(CNF(1)), iter.next());
651		// non-fungible, after fungibles, order by instance id, id=2
652		assert_eq!(Some(CNF(2)), iter.next());
653		// nothing else in the assets
654		assert_eq!(None, iter.next());
655
656		// lets add copy of the assets to the assets itself, just to check if order stays the same
657		// we also expect 2x amount for every fungible and collapsed non-fungibles
658		let assets_same = assets.unsafe_clone_for_tests();
659		assets.subsume_assets(assets_same);
660
661		let mut iter = assets.into_assets_iter();
662		assert_eq!(Some(CF(200)), iter.next());
663		assert_eq!(Some(CFG(10, 800)), iter.next());
664		assert_eq!(Some(CFG(15, 1000)), iter.next());
665		assert_eq!(Some(CFP(400)), iter.next());
666		assert_eq!(Some(CFPP(600)), iter.next());
667		assert_eq!(Some(CNF(1)), iter.next());
668		assert_eq!(Some(CNF(2)), iter.next());
669		assert_eq!(None, iter.next());
670	}
671
672	#[test]
673	fn subsume_assets_equal_length_holdings() {
674		let mut t1 = test_assets();
675		let mut t2 = AssetsInHolding::new();
676		t2.subsume_assets(asset_to_holding(CF(300)));
677		t2.subsume_assets(asset_to_holding(CNF(50)));
678
679		let t1_clone = t1.unsafe_clone_for_tests();
680		let mut t2_clone = t2.unsafe_clone_for_tests();
681
682		// ensure values for same fungibles are summed up together
683		// and order is also ok (see assets_in_holding_order_works())
684		t1.subsume_assets(t2.unsafe_clone_for_tests());
685		let mut iter = t1.into_assets_iter();
686		assert_eq!(Some(CF(600)), iter.next());
687		assert_eq!(Some(CNF(40)), iter.next());
688		assert_eq!(Some(CNF(50)), iter.next());
689		assert_eq!(None, iter.next());
690
691		// try the same initial holdings but other way around
692		// expecting same exact result as above
693		t2_clone.subsume_assets(t1_clone.unsafe_clone_for_tests());
694		let mut iter = t2_clone.into_assets_iter();
695		assert_eq!(Some(CF(600)), iter.next());
696		assert_eq!(Some(CNF(40)), iter.next());
697		assert_eq!(Some(CNF(50)), iter.next());
698		assert_eq!(None, iter.next());
699	}
700
701	#[test]
702	fn subsume_assets_different_length_holdings() {
703		let mut t1 = AssetsInHolding::new();
704		t1.subsume_assets(asset_to_holding(CFP(400)));
705		t1.subsume_assets(asset_to_holding(CFPP(100)));
706
707		let mut t2 = AssetsInHolding::new();
708		t2.subsume_assets(asset_to_holding(CF(100)));
709		t2.subsume_assets(asset_to_holding(CNF(50)));
710		t2.subsume_assets(asset_to_holding(CNF(40)));
711		t2.subsume_assets(asset_to_holding(CFP(100)));
712		t2.subsume_assets(asset_to_holding(CFPP(100)));
713
714		let t1_clone = t1.unsafe_clone_for_tests();
715		let mut t2_clone = t2.unsafe_clone_for_tests();
716
717		// ensure values for same fungibles are summed up together
718		// and order is also ok (see assets_in_holding_order_works())
719		t1.subsume_assets(t2);
720		let mut iter = t1.into_assets_iter();
721		assert_eq!(Some(CF(100)), iter.next());
722		assert_eq!(Some(CFP(500)), iter.next());
723		assert_eq!(Some(CFPP(200)), iter.next());
724		assert_eq!(Some(CNF(40)), iter.next());
725		assert_eq!(Some(CNF(50)), iter.next());
726		assert_eq!(None, iter.next());
727
728		// try the same initial holdings but other way around
729		// expecting same exact result as above
730		t2_clone.subsume_assets(t1_clone);
731		let mut iter = t2_clone.into_assets_iter();
732		assert_eq!(Some(CF(100)), iter.next());
733		assert_eq!(Some(CFP(500)), iter.next());
734		assert_eq!(Some(CFPP(200)), iter.next());
735		assert_eq!(Some(CNF(40)), iter.next());
736		assert_eq!(Some(CNF(50)), iter.next());
737		assert_eq!(None, iter.next());
738	}
739
740	#[test]
741	fn subsume_assets_empty_holding() {
742		let mut t1 = AssetsInHolding::new();
743		let t2 = AssetsInHolding::new();
744		t1.subsume_assets(t2.unsafe_clone_for_tests());
745		let mut iter = t1.unsafe_clone_for_tests().into_assets_iter();
746		assert_eq!(None, iter.next());
747
748		t1.subsume_assets(asset_to_holding(CFP(400)));
749		t1.subsume_assets(asset_to_holding(CNF(40)));
750		t1.subsume_assets(asset_to_holding(CFPP(100)));
751
752		let t1_clone = t1.unsafe_clone_for_tests();
753		let mut t2_clone = t2.unsafe_clone_for_tests();
754
755		// ensure values for same fungibles are summed up together
756		// and order is also ok (see assets_in_holding_order_works())
757		t1.subsume_assets(t2.unsafe_clone_for_tests());
758		let mut iter = t1.into_assets_iter();
759		assert_eq!(Some(CFP(400)), iter.next());
760		assert_eq!(Some(CFPP(100)), iter.next());
761		assert_eq!(Some(CNF(40)), iter.next());
762		assert_eq!(None, iter.next());
763
764		// try the same initial holdings but other way around
765		// expecting same exact result as above
766		t2_clone.subsume_assets(t1_clone.unsafe_clone_for_tests());
767		let mut iter = t2_clone.into_assets_iter();
768		assert_eq!(Some(CFP(400)), iter.next());
769		assert_eq!(Some(CFPP(100)), iter.next());
770		assert_eq!(Some(CNF(40)), iter.next());
771		assert_eq!(None, iter.next());
772	}
773
774	#[test]
775	fn into_assets_iter_works() {
776		let assets = test_assets();
777		let mut iter = assets.into_assets_iter();
778		// Order defined by implementation: CF, CNF
779		assert_eq!(Some(CF(300)), iter.next());
780		assert_eq!(Some(CNF(40)), iter.next());
781		assert_eq!(None, iter.next());
782	}
783
784	#[test]
785	fn assets_into_works() {
786		let mut assets_vec: Vec<Asset> = Vec::new();
787		assets_vec.push(CF(300));
788		assets_vec.push(CNF(40));
789		// Push same group of tokens again
790		assets_vec.push(CF(300));
791		assets_vec.push(CNF(40));
792
793		let mut assets = AssetsInHolding::new();
794		for asset in assets_vec {
795			assets.subsume_assets(asset_to_holding(asset));
796		}
797		let mut iter = assets.into_assets_iter();
798		// Fungibles add
799		assert_eq!(Some(CF(600)), iter.next());
800		// Non-fungibles collapse
801		assert_eq!(Some(CNF(40)), iter.next());
802		assert_eq!(None, iter.next());
803	}
804
805	#[test]
806	fn min_all_and_none_works() {
807		let assets = test_assets();
808		let none = Assets::new().into();
809		let all = All.into();
810
811		let none_min = assets.min(&none);
812		assert_eq!(None, none_min.inner().iter().next());
813		let all_min = assets.min(&all);
814		let all_min_vec: Vec<_> = all_min.inner().iter().cloned().collect();
815		let assets_vec: Vec<_> = assets.assets_iter().collect();
816		assert_eq!(all_min_vec, assets_vec);
817	}
818
819	#[test]
820	fn min_counted_works() {
821		let mut assets = AssetsInHolding::new();
822		assets.subsume_assets(asset_to_holding(CNF(40)));
823		assets.subsume_assets(asset_to_holding(CF(3000)));
824		assets.subsume_assets(asset_to_holding(CNF(80)));
825		let all = WildAsset::AllCounted(6).into();
826
827		let all = assets.min(&all);
828		assert_eq!(all.inner(), &vec![CF(3000), CNF(40), CNF(80)]);
829	}
830
831	#[test]
832	fn min_all_concrete_works() {
833		let assets = test_assets();
834		let fungible = Wild((Here, WildFungible).into());
835		let non_fungible = Wild((Here, WildNonFungible).into());
836
837		let fungible = assets.min(&fungible);
838		assert_eq!(fungible.inner(), &vec![CF(300)]);
839		let non_fungible = assets.min(&non_fungible);
840		assert_eq!(non_fungible.inner(), &vec![CNF(40)]);
841	}
842
843	#[test]
844	fn min_basic_works() {
845		let assets1 = test_assets();
846
847		// Create Assets directly instead of going through AssetsInHolding
848		let assets2: Assets = vec![
849			// This is more then 300, so it should stay at 300
850			CF(600),
851			// This asset should be included
852			CNF(40),
853		]
854		.into();
855
856		let assets_min = assets1.min(&assets2.into());
857		assert_eq!(assets_min.inner(), &vec![CF(300), CNF(40)]);
858	}
859
860	#[test]
861	fn saturating_take_all_and_none_works() {
862		let mut assets = test_assets();
863
864		let taken_none = assets.saturating_take(vec![].into());
865		assert_eq!(None, taken_none.assets_iter().next());
866		let taken_all = assets.saturating_take(All.into());
867		// Everything taken
868		assert_eq!(None, assets.assets_iter().next());
869		let all_iter = taken_all.assets_iter();
870		assert!(all_iter.eq(test_assets().assets_iter()));
871	}
872
873	#[test]
874	fn saturating_take_all_concrete_works() {
875		let mut assets = test_assets();
876		let fungible = Wild((Here, WildFungible).into());
877		let non_fungible = Wild((Here, WildNonFungible).into());
878
879		let fungible = assets.saturating_take(fungible);
880		let fungible = fungible.assets_iter().collect::<Vec<_>>();
881		assert_eq!(fungible, vec![CF(300)]);
882		let non_fungible = assets.saturating_take(non_fungible);
883		let non_fungible = non_fungible.assets_iter().collect::<Vec<_>>();
884		assert_eq!(non_fungible, vec![CNF(40)]);
885	}
886
887	#[test]
888	fn saturating_take_basic_works() {
889		let mut assets1 = test_assets();
890
891		// Create Assets directly instead of going through AssetsInHolding
892		let assets2: Assets = vec![
893			// This is more then 300, so it takes everything
894			CF(600),
895			// This asset should be taken
896			CNF(40),
897		]
898		.into();
899
900		let taken = assets1.saturating_take(assets2.into());
901		let taken_vec: Vec<_> = taken.assets_iter().collect();
902		assert_eq!(taken_vec, vec![CF(300), CNF(40)]);
903	}
904
905	#[test]
906	fn try_take_all_counted_works() {
907		let mut assets = AssetsInHolding::new();
908		assets.subsume_assets(asset_to_holding(CNF(40)));
909		assets.subsume_assets(asset_to_holding(CF(3000)));
910		assets.subsume_assets(asset_to_holding(CNF(80)));
911		let all = assets.try_take(WildAsset::AllCounted(6).into()).unwrap();
912		let all_vec: Vec<_> = all.assets_iter().collect();
913		assert_eq!(all_vec, vec![CF(3000), CNF(40), CNF(80)]);
914	}
915
916	#[test]
917	fn try_take_fungibles_counted_works() {
918		let mut assets = AssetsInHolding::new();
919		assets.subsume_assets(asset_to_holding(CNF(40)));
920		assets.subsume_assets(asset_to_holding(CF(3000)));
921		assets.subsume_assets(asset_to_holding(CNF(80)));
922		let assets_vec: Vec<_> = assets.assets_iter().collect();
923		assert_eq!(assets_vec, vec![CF(3000), CNF(40), CNF(80)]);
924	}
925
926	#[test]
927	fn try_take_non_fungibles_counted_works() {
928		let mut assets = AssetsInHolding::new();
929		assets.subsume_assets(asset_to_holding(CNF(40)));
930		assets.subsume_assets(asset_to_holding(CF(3000)));
931		assets.subsume_assets(asset_to_holding(CNF(80)));
932		let assets_vec: Vec<_> = assets.assets_iter().collect();
933		assert_eq!(assets_vec, vec![CF(3000), CNF(40), CNF(80)]);
934	}
935}