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 borrowing iterator over all assets.
190	pub fn assets_iter(&self) -> impl Iterator<Item = Asset> + '_ {
191		self.fungible_assets_iter().chain(self.non_fungible_assets_iter())
192	}
193
194	/// Mutate `self` to contain all given `assets`, saturating if necessary.
195	///
196	/// NOTE: [`AssetsInHolding`] are always sorted
197	pub fn subsume_assets(&mut self, assets: AssetsInHolding) {
198		// for fungibles, find matching fungibles and sum their amounts so we end-up having just
199		// single such fungible but with increased amount inside
200		for (asset_id, accounting) in assets.fungible.into_iter() {
201			match self.fungible.entry(asset_id) {
202				btree_map::Entry::Occupied(mut e) => {
203					e.get_mut().saturating_subsume(accounting);
204				},
205				btree_map::Entry::Vacant(e) => {
206					e.insert(accounting);
207				},
208			}
209		}
210		// for non-fungibles, every entry is unique so there is no notion of amount to sum-up
211		// together if there is the same non-fungible in both holdings (same instance_id) these
212		// will be collapsed into just single one
213		let mut non_fungible = assets.non_fungible;
214		self.non_fungible.append(&mut non_fungible);
215	}
216
217	/// Swaps two mutable AssetsInHolding, without deinitializing either one.
218	pub fn swapped(&mut self, mut with: AssetsInHolding) -> Self {
219		mem::swap(&mut *self, &mut with);
220		with
221	}
222
223	/// Consume `self` and return `Assets` as assets interpreted from the perspective of a `target`
224	/// chain. The local chain's `context` is provided.
225	///
226	/// Any assets which were unable to be reanchored are introduced into `failed_bin` instead.
227	///
228	/// WARNING: this will drop/resolve any inner imbalances for the reanchored assets. Meant to be
229	/// used in crosschain operations where the asset is consumed (imbalance dropped/resolved)
230	/// locally, and a reanchored version of it is to be minted on a remote location.
231	pub fn reanchor_and_burn_local(
232		self,
233		target: &Location,
234		context: &InteriorLocation,
235		failed_bin: &mut Self,
236	) -> Assets {
237		let mut assets: Vec<Asset> = self
238			.fungible
239			.into_iter()
240			.filter_map(|(mut id, accounting)| match id.reanchor(target, context) {
241				Ok(()) => Some(Asset::from((id, Fungible(accounting.amount())))),
242				Err(()) => {
243					failed_bin.fungible.insert(id, accounting);
244					None
245				},
246			})
247			.chain(self.non_fungible.into_iter().filter_map(|(mut class, inst)| {
248				match class.reanchor(target, context) {
249					Ok(()) => Some(Asset::from((class, inst))),
250					Err(()) => {
251						failed_bin.non_fungible.insert((class, inst));
252						None
253					},
254				}
255			}))
256			.collect();
257		assets.sort();
258		assets.into()
259	}
260
261	/// Return all inner assets, but interpreted from the perspective of a `target` chain. The local
262	/// chain's `context` is provided.
263	///
264	/// **Warning**: This method returns `Assets` which only contains amounts (not imbalances).
265	/// The returned `Assets` is suitable for cross-chain messaging but does not preserve the
266	/// imbalance accounting semantics of the original `AssetsInHolding`. Do not use the returned
267	/// value for local balance operations that require imbalance tracking.
268	pub fn reanchored_assets(&self, target: &Location, context: &InteriorLocation) -> Assets {
269		let mut assets: Vec<Asset> = self
270			.fungible
271			.iter()
272			.filter_map(|(id, accounting)| match id.clone().reanchored(target, context) {
273				Ok(new_id) => Some(Asset::from((new_id, Fungible(accounting.amount())))),
274				Err(()) => None,
275			})
276			.chain(self.non_fungible.iter().filter_map(|(class, inst)| {
277				match class.clone().reanchored(target, context) {
278					Ok(new_class) => Some(Asset::from((new_class, *inst))),
279					Err(()) => None,
280				}
281			}))
282			.collect();
283		assets.sort();
284		assets.into()
285	}
286
287	/// Returns `true` if `asset` is contained within `self`.
288	pub fn contains_asset(&self, asset: &Asset) -> bool {
289		match asset {
290			Asset { fun: Fungible(amount), id } => {
291				self.fungible.get(id).map_or(false, |a| a.amount() >= *amount)
292			},
293			Asset { fun: NonFungible(instance), id } => {
294				self.non_fungible.contains(&(id.clone(), *instance))
295			},
296		}
297	}
298
299	/// Returns `true` if all `assets` are contained within `self`.
300	pub fn contains_assets(&self, assets: &Assets) -> bool {
301		assets.inner().iter().all(|a| self.contains_asset(a))
302	}
303
304	/// Returns an error unless all `assets` are contained in `self`.
305	pub fn ensure_contains(&self, assets: &Assets) -> Result<(), TakeError> {
306		for asset in assets.inner().iter() {
307			match asset {
308				Asset { fun: Fungible(amount), id } => {
309					if self.fungible.get(id).map_or(true, |a| a.amount() < *amount) {
310						return Err(TakeError::AssetUnderflow((id.clone(), *amount).into()));
311					}
312				},
313				Asset { fun: NonFungible(instance), id } => {
314					let id_instance = (id.clone(), *instance);
315					if !self.non_fungible.contains(&id_instance) {
316						return Err(TakeError::AssetUnderflow(id_instance.into()));
317					}
318				},
319			}
320		}
321		return Ok(());
322	}
323
324	/// Mutates `self` to its original value less `mask` and returns assets that were removed.
325	///
326	/// If `saturate` is `true`, then `self` is considered to be masked by `mask`, thereby avoiding
327	/// any attempt at reducing it by assets it does not contain. In this case, the function is
328	/// infallible. If `saturate` is `false` and `mask` references a definite asset which `self`
329	/// does not contain then an error is returned.
330	///
331	/// The number of unique assets which are removed will respect the `count` parameter in the
332	/// counted wildcard variants.
333	///
334	/// Returns `Ok` with the definite assets token from `self` and mutates `self` to its value
335	/// minus `mask`. Returns `Err` in the non-saturating case where `self` did not contain (enough
336	/// of) a definite asset to be removed.
337	fn general_take(
338		&mut self,
339		mask: AssetFilter,
340		saturate: bool,
341	) -> Result<AssetsInHolding, TakeError> {
342		let mut taken = AssetsInHolding::new();
343		let maybe_limit = mask.limit().map(|x| x as usize);
344		match mask {
345			AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => match maybe_limit {
346				None => return Ok(self.swapped(AssetsInHolding::new())),
347				Some(limit) if self.len() <= limit => {
348					return Ok(self.swapped(AssetsInHolding::new()))
349				},
350				Some(0) => return Ok(AssetsInHolding::new()),
351				Some(limit) => {
352					let fungible = mem::replace(&mut self.fungible, Default::default());
353					fungible.into_iter().for_each(|(c, amount)| {
354						if taken.len() < limit {
355							taken.fungible.insert(c, amount);
356						} else {
357							self.fungible.insert(c, amount);
358						}
359					});
360					let non_fungible = mem::replace(&mut self.non_fungible, Default::default());
361					non_fungible.into_iter().for_each(|(c, instance)| {
362						if taken.len() < limit {
363							taken.non_fungible.insert((c, instance));
364						} else {
365							self.non_fungible.insert((c, instance));
366						}
367					});
368				},
369			},
370			AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) |
371			AssetFilter::Wild(AllOf { fun: WildFungible, id }) => {
372				if maybe_limit.map_or(true, |l| l >= 1) {
373					if let Some((id, amount)) = self.fungible.remove_entry(&id) {
374						taken.fungible.insert(id, amount);
375					}
376				}
377			},
378			AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) |
379			AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => {
380				let non_fungible = mem::replace(&mut self.non_fungible, Default::default());
381				non_fungible.into_iter().for_each(|(c, instance)| {
382					if c == id && maybe_limit.map_or(true, |l| taken.len() < l) {
383						taken.non_fungible.insert((c, instance));
384					} else {
385						self.non_fungible.insert((c, instance));
386					}
387				});
388			},
389			AssetFilter::Definite(assets) => {
390				if !saturate {
391					self.ensure_contains(&assets)?;
392				}
393				for asset in assets.into_inner().into_iter() {
394					match asset {
395						Asset { fun: Fungible(amount), id } => {
396							let (remove, balance) = match self.fungible.get_mut(&id) {
397								Some(self_amount) => {
398									// Ok to use `saturating_take()` because we checked with
399									// `self.ensure_contains()` above against `saturate` flag
400									let balance = self_amount.saturating_take(amount);
401									(self_amount.amount() == 0, Some(balance))
402								},
403								None => (false, None),
404							};
405							if remove {
406								self.fungible.remove(&id);
407							}
408							if let Some(balance) = balance {
409								let other = Self::new_from_fungible_credit(id, balance);
410								taken.subsume_assets(other);
411							}
412						},
413						Asset { fun: NonFungible(instance), id } => {
414							let id_instance = (id, instance);
415							if self.non_fungible.remove(&id_instance) {
416								taken.non_fungible.insert((id_instance.0, id_instance.1));
417							}
418						},
419					}
420				}
421			},
422		}
423		Ok(taken)
424	}
425
426	/// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least
427	/// `mask`.
428	///
429	/// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its
430	/// value minus `mask` if `self` contains `asset`, and return `Err` otherwise.
431	pub fn saturating_take(&mut self, asset: AssetFilter) -> Self {
432		self.general_take(asset, true)
433			.expect("general_take never results in error when saturating")
434	}
435
436	/// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least
437	/// `mask`.
438	///
439	/// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its
440	/// value minus `asset` if `self` contains `asset`, and return `Err` otherwise.
441	pub fn try_take(&mut self, mask: AssetFilter) -> Result<Self, TakeError> {
442		self.general_take(mask, false)
443	}
444
445	/// Return the assets in `self`, but (asset-wise) of no greater value than `mask`.
446	///
447	/// The number of unique assets which are returned will respect the `count` parameter in the
448	/// counted wildcard variants of `mask`.
449	///
450	/// Example:
451	///
452	/// ```
453	/// use staging_xcm_executor::AssetsInHolding;
454	/// use xcm::latest::prelude::*;
455	/// // Note: In real usage, AssetsInHolding is created through TransactAsset operations
456	/// // For this example, we use Assets type instead to demonstrate the min() output
457	/// let assets_i_have: Assets = vec![ (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 100).into() ].into();
458	/// let assets_they_want: AssetFilter = vec![ (Here, 200).into(), (Junctions::from([GeneralIndex(0)]), 50).into() ].into();
459	///
460	/// // Normally you would call this on AssetsInHolding, but for documentation purposes:
461	/// // let assets_we_can_trade: Assets = assets_i_have.min(&assets_they_want);
462	/// // assert_eq!(assets_we_can_trade.inner(), &vec![
463	/// // 	(Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 50).into(),
464	/// // ]);
465	/// ```
466	pub fn min(&self, mask: &AssetFilter) -> Assets {
467		let mut masked = Assets::new();
468		let maybe_limit = mask.limit().map(|x| x as usize);
469		if maybe_limit.map_or(false, |l| l == 0) {
470			return masked;
471		}
472		match mask {
473			AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => {
474				if maybe_limit.map_or(true, |l| self.len() <= l) {
475					return self.assets_iter().collect::<Vec<Asset>>().into();
476				} else {
477					for (c, accounting) in self.fungible.iter() {
478						masked.push((c.clone(), accounting.amount()).into());
479						if maybe_limit.map_or(false, |l| masked.len() >= l) {
480							return masked;
481						}
482					}
483					for (c, instance) in self.non_fungible.iter() {
484						masked.push((c.clone(), *instance).into());
485						if maybe_limit.map_or(false, |l| masked.len() >= l) {
486							return masked;
487						}
488					}
489				}
490			},
491			AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) |
492			AssetFilter::Wild(AllOf { fun: WildFungible, id }) => {
493				if let Some(accounting) = self.fungible.get(&id) {
494					masked.push((id.clone(), accounting.amount()).into());
495				}
496			},
497			AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) |
498			AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => {
499				for (c, instance) in self.non_fungible.iter() {
500					if c == id {
501						masked.push((c.clone(), *instance).into());
502						if maybe_limit.map_or(false, |l| masked.len() >= l) {
503							return masked;
504						}
505					}
506				}
507			},
508			AssetFilter::Definite(assets) => {
509				for asset in assets.inner().iter() {
510					match asset {
511						Asset { fun: Fungible(amount), id } => {
512							if let Some(m) = self.fungible.get(id) {
513								masked
514									.push((id.clone(), Fungible(*amount.min(&m.amount()))).into());
515							}
516						},
517						Asset { fun: NonFungible(instance), id } => {
518							let id_instance = (id.clone(), *instance);
519							if self.non_fungible.contains(&id_instance) {
520								masked.push(id_instance.into());
521							}
522						},
523					}
524				}
525			},
526		}
527		masked
528	}
529
530	/// Clone this holding for testing purposes only.
531	///
532	/// This uses `unsafe_clone()` on the imbalance accounting trait objects,
533	/// which may not maintain proper accounting invariants. Only use in tests.
534	#[cfg(feature = "std")]
535	pub fn unsafe_clone_for_tests(&self) -> Self {
536		Self {
537			fungible: self
538				.fungible
539				.iter()
540				.map(|(id, accounting)| (id.clone(), accounting.unsafe_clone()))
541				.collect(),
542			non_fungible: self.non_fungible.clone(),
543		}
544	}
545}
546
547#[cfg(test)]
548mod tests {
549	use super::*;
550	use crate::tests::mock::*;
551	use alloc::vec;
552	use xcm::latest::prelude::*;
553
554	#[allow(non_snake_case)]
555	/// Concrete fungible constructor
556	fn CF(amount: u128) -> Asset {
557		(Here, amount).into()
558	}
559	#[allow(non_snake_case)]
560	/// Concrete fungible constructor with index for GeneralIndex
561	fn CFG(index: u128, amount: u128) -> Asset {
562		(GeneralIndex(index), amount).into()
563	}
564	#[allow(non_snake_case)]
565	/// Concrete fungible constructor (parent=1)
566	fn CFP(amount: u128) -> Asset {
567		(Parent, amount).into()
568	}
569	#[allow(non_snake_case)]
570	/// Concrete fungible constructor (parent=2)
571	fn CFPP(amount: u128) -> Asset {
572		((Parent, Parent), amount).into()
573	}
574	#[allow(non_snake_case)]
575	/// Concrete non-fungible constructor
576	fn CNF(instance_id: u8) -> Asset {
577		(Here, [instance_id; 4]).into()
578	}
579
580	/// Helper to convert a single Asset into AssetsInHolding for tests
581	fn asset_to_holding(asset: Asset) -> AssetsInHolding {
582		// Since we can't directly convert Asset to AssetsInHolding, we create an empty
583		// holding and manually insert the asset
584		let mut holding = AssetsInHolding::new();
585		match asset.fun {
586			Fungible(amount) => {
587				holding.fungible.insert(asset.id, Box::new(MockCredit(amount)));
588			},
589			NonFungible(instance) => {
590				holding.non_fungible.insert((asset.id, instance));
591			},
592		}
593		holding
594	}
595
596	fn test_assets() -> AssetsInHolding {
597		let mut assets = AssetsInHolding::new();
598		assets.subsume_assets(asset_to_holding(CF(300)));
599		assets.subsume_assets(asset_to_holding(CNF(40)));
600		assets
601	}
602
603	#[test]
604	fn assets_in_holding_order_works() {
605		// populate assets in non-ordered fashion
606		let mut assets = AssetsInHolding::new();
607		assets.subsume_assets(asset_to_holding(CFPP(300)));
608		assets.subsume_assets(asset_to_holding(CFP(200)));
609		assets.subsume_assets(asset_to_holding(CNF(2)));
610		assets.subsume_assets(asset_to_holding(CF(100)));
611		assets.subsume_assets(asset_to_holding(CNF(1)));
612		assets.subsume_assets(asset_to_holding(CFG(10, 400)));
613		assets.subsume_assets(asset_to_holding(CFG(15, 500)));
614
615		// following is the order we expect from AssetsInHolding
616		// - fungibles before non-fungibles
617		// - for fungibles, sort by parent first, if parents match, then by other components like
618		//   general index
619		// - for non-fungibles, sort by instance_id
620		let mut iter = assets.unsafe_clone_for_tests().into_assets_iter();
621		// fungible, order by parent, parent=0
622		assert_eq!(Some(CF(100)), iter.next());
623		// fungible, order by parent then by general index, parent=0, general index=10
624		assert_eq!(Some(CFG(10, 400)), iter.next());
625		// fungible, order by parent then by general index, parent=0, general index=15
626		assert_eq!(Some(CFG(15, 500)), iter.next());
627		// fungible, order by parent, parent=1
628		assert_eq!(Some(CFP(200)), iter.next());
629		// fungible, order by parent, parent=2
630		assert_eq!(Some(CFPP(300)), iter.next());
631		// non-fungible, after fungibles, order by instance id, id=1
632		assert_eq!(Some(CNF(1)), iter.next());
633		// non-fungible, after fungibles, order by instance id, id=2
634		assert_eq!(Some(CNF(2)), iter.next());
635		// nothing else in the assets
636		assert_eq!(None, iter.next());
637
638		// lets add copy of the assets to the assets itself, just to check if order stays the same
639		// we also expect 2x amount for every fungible and collapsed non-fungibles
640		let assets_same = assets.unsafe_clone_for_tests();
641		assets.subsume_assets(assets_same);
642
643		let mut iter = assets.into_assets_iter();
644		assert_eq!(Some(CF(200)), iter.next());
645		assert_eq!(Some(CFG(10, 800)), iter.next());
646		assert_eq!(Some(CFG(15, 1000)), iter.next());
647		assert_eq!(Some(CFP(400)), iter.next());
648		assert_eq!(Some(CFPP(600)), iter.next());
649		assert_eq!(Some(CNF(1)), iter.next());
650		assert_eq!(Some(CNF(2)), iter.next());
651		assert_eq!(None, iter.next());
652	}
653
654	#[test]
655	fn subsume_assets_equal_length_holdings() {
656		let mut t1 = test_assets();
657		let mut t2 = AssetsInHolding::new();
658		t2.subsume_assets(asset_to_holding(CF(300)));
659		t2.subsume_assets(asset_to_holding(CNF(50)));
660
661		let t1_clone = t1.unsafe_clone_for_tests();
662		let mut t2_clone = t2.unsafe_clone_for_tests();
663
664		// ensure values for same fungibles are summed up together
665		// and order is also ok (see assets_in_holding_order_works())
666		t1.subsume_assets(t2.unsafe_clone_for_tests());
667		let mut iter = t1.into_assets_iter();
668		assert_eq!(Some(CF(600)), iter.next());
669		assert_eq!(Some(CNF(40)), iter.next());
670		assert_eq!(Some(CNF(50)), iter.next());
671		assert_eq!(None, iter.next());
672
673		// try the same initial holdings but other way around
674		// expecting same exact result as above
675		t2_clone.subsume_assets(t1_clone.unsafe_clone_for_tests());
676		let mut iter = t2_clone.into_assets_iter();
677		assert_eq!(Some(CF(600)), iter.next());
678		assert_eq!(Some(CNF(40)), iter.next());
679		assert_eq!(Some(CNF(50)), iter.next());
680		assert_eq!(None, iter.next());
681	}
682
683	#[test]
684	fn subsume_assets_different_length_holdings() {
685		let mut t1 = AssetsInHolding::new();
686		t1.subsume_assets(asset_to_holding(CFP(400)));
687		t1.subsume_assets(asset_to_holding(CFPP(100)));
688
689		let mut t2 = AssetsInHolding::new();
690		t2.subsume_assets(asset_to_holding(CF(100)));
691		t2.subsume_assets(asset_to_holding(CNF(50)));
692		t2.subsume_assets(asset_to_holding(CNF(40)));
693		t2.subsume_assets(asset_to_holding(CFP(100)));
694		t2.subsume_assets(asset_to_holding(CFPP(100)));
695
696		let t1_clone = t1.unsafe_clone_for_tests();
697		let mut t2_clone = t2.unsafe_clone_for_tests();
698
699		// ensure values for same fungibles are summed up together
700		// and order is also ok (see assets_in_holding_order_works())
701		t1.subsume_assets(t2);
702		let mut iter = t1.into_assets_iter();
703		assert_eq!(Some(CF(100)), iter.next());
704		assert_eq!(Some(CFP(500)), iter.next());
705		assert_eq!(Some(CFPP(200)), iter.next());
706		assert_eq!(Some(CNF(40)), iter.next());
707		assert_eq!(Some(CNF(50)), iter.next());
708		assert_eq!(None, iter.next());
709
710		// try the same initial holdings but other way around
711		// expecting same exact result as above
712		t2_clone.subsume_assets(t1_clone);
713		let mut iter = t2_clone.into_assets_iter();
714		assert_eq!(Some(CF(100)), iter.next());
715		assert_eq!(Some(CFP(500)), iter.next());
716		assert_eq!(Some(CFPP(200)), iter.next());
717		assert_eq!(Some(CNF(40)), iter.next());
718		assert_eq!(Some(CNF(50)), iter.next());
719		assert_eq!(None, iter.next());
720	}
721
722	#[test]
723	fn subsume_assets_empty_holding() {
724		let mut t1 = AssetsInHolding::new();
725		let t2 = AssetsInHolding::new();
726		t1.subsume_assets(t2.unsafe_clone_for_tests());
727		let mut iter = t1.unsafe_clone_for_tests().into_assets_iter();
728		assert_eq!(None, iter.next());
729
730		t1.subsume_assets(asset_to_holding(CFP(400)));
731		t1.subsume_assets(asset_to_holding(CNF(40)));
732		t1.subsume_assets(asset_to_holding(CFPP(100)));
733
734		let t1_clone = t1.unsafe_clone_for_tests();
735		let mut t2_clone = t2.unsafe_clone_for_tests();
736
737		// ensure values for same fungibles are summed up together
738		// and order is also ok (see assets_in_holding_order_works())
739		t1.subsume_assets(t2.unsafe_clone_for_tests());
740		let mut iter = t1.into_assets_iter();
741		assert_eq!(Some(CFP(400)), iter.next());
742		assert_eq!(Some(CFPP(100)), iter.next());
743		assert_eq!(Some(CNF(40)), iter.next());
744		assert_eq!(None, iter.next());
745
746		// try the same initial holdings but other way around
747		// expecting same exact result as above
748		t2_clone.subsume_assets(t1_clone.unsafe_clone_for_tests());
749		let mut iter = t2_clone.into_assets_iter();
750		assert_eq!(Some(CFP(400)), iter.next());
751		assert_eq!(Some(CFPP(100)), iter.next());
752		assert_eq!(Some(CNF(40)), iter.next());
753		assert_eq!(None, iter.next());
754	}
755
756	#[test]
757	fn into_assets_iter_works() {
758		let assets = test_assets();
759		let mut iter = assets.into_assets_iter();
760		// Order defined by implementation: CF, CNF
761		assert_eq!(Some(CF(300)), iter.next());
762		assert_eq!(Some(CNF(40)), iter.next());
763		assert_eq!(None, iter.next());
764	}
765
766	#[test]
767	fn assets_into_works() {
768		let mut assets_vec: Vec<Asset> = Vec::new();
769		assets_vec.push(CF(300));
770		assets_vec.push(CNF(40));
771		// Push same group of tokens again
772		assets_vec.push(CF(300));
773		assets_vec.push(CNF(40));
774
775		let mut assets = AssetsInHolding::new();
776		for asset in assets_vec {
777			assets.subsume_assets(asset_to_holding(asset));
778		}
779		let mut iter = assets.into_assets_iter();
780		// Fungibles add
781		assert_eq!(Some(CF(600)), iter.next());
782		// Non-fungibles collapse
783		assert_eq!(Some(CNF(40)), iter.next());
784		assert_eq!(None, iter.next());
785	}
786
787	#[test]
788	fn min_all_and_none_works() {
789		let assets = test_assets();
790		let none = Assets::new().into();
791		let all = All.into();
792
793		let none_min = assets.min(&none);
794		assert_eq!(None, none_min.inner().iter().next());
795		let all_min = assets.min(&all);
796		let all_min_vec: Vec<_> = all_min.inner().iter().cloned().collect();
797		let assets_vec: Vec<_> = assets.assets_iter().collect();
798		assert_eq!(all_min_vec, assets_vec);
799	}
800
801	#[test]
802	fn min_counted_works() {
803		let mut assets = AssetsInHolding::new();
804		assets.subsume_assets(asset_to_holding(CNF(40)));
805		assets.subsume_assets(asset_to_holding(CF(3000)));
806		assets.subsume_assets(asset_to_holding(CNF(80)));
807		let all = WildAsset::AllCounted(6).into();
808
809		let all = assets.min(&all);
810		assert_eq!(all.inner(), &vec![CF(3000), CNF(40), CNF(80)]);
811	}
812
813	#[test]
814	fn min_all_concrete_works() {
815		let assets = test_assets();
816		let fungible = Wild((Here, WildFungible).into());
817		let non_fungible = Wild((Here, WildNonFungible).into());
818
819		let fungible = assets.min(&fungible);
820		assert_eq!(fungible.inner(), &vec![CF(300)]);
821		let non_fungible = assets.min(&non_fungible);
822		assert_eq!(non_fungible.inner(), &vec![CNF(40)]);
823	}
824
825	#[test]
826	fn min_basic_works() {
827		let assets1 = test_assets();
828
829		// Create Assets directly instead of going through AssetsInHolding
830		let assets2: Assets = vec![
831			// This is more then 300, so it should stay at 300
832			CF(600),
833			// This asset should be included
834			CNF(40),
835		]
836		.into();
837
838		let assets_min = assets1.min(&assets2.into());
839		assert_eq!(assets_min.inner(), &vec![CF(300), CNF(40)]);
840	}
841
842	#[test]
843	fn saturating_take_all_and_none_works() {
844		let mut assets = test_assets();
845
846		let taken_none = assets.saturating_take(vec![].into());
847		assert_eq!(None, taken_none.assets_iter().next());
848		let taken_all = assets.saturating_take(All.into());
849		// Everything taken
850		assert_eq!(None, assets.assets_iter().next());
851		let all_iter = taken_all.assets_iter();
852		assert!(all_iter.eq(test_assets().assets_iter()));
853	}
854
855	#[test]
856	fn saturating_take_all_concrete_works() {
857		let mut assets = test_assets();
858		let fungible = Wild((Here, WildFungible).into());
859		let non_fungible = Wild((Here, WildNonFungible).into());
860
861		let fungible = assets.saturating_take(fungible);
862		let fungible = fungible.assets_iter().collect::<Vec<_>>();
863		assert_eq!(fungible, vec![CF(300)]);
864		let non_fungible = assets.saturating_take(non_fungible);
865		let non_fungible = non_fungible.assets_iter().collect::<Vec<_>>();
866		assert_eq!(non_fungible, vec![CNF(40)]);
867	}
868
869	#[test]
870	fn saturating_take_basic_works() {
871		let mut assets1 = test_assets();
872
873		// Create Assets directly instead of going through AssetsInHolding
874		let assets2: Assets = vec![
875			// This is more then 300, so it takes everything
876			CF(600),
877			// This asset should be taken
878			CNF(40),
879		]
880		.into();
881
882		let taken = assets1.saturating_take(assets2.into());
883		let taken_vec: Vec<_> = taken.assets_iter().collect();
884		assert_eq!(taken_vec, vec![CF(300), CNF(40)]);
885	}
886
887	#[test]
888	fn try_take_all_counted_works() {
889		let mut assets = AssetsInHolding::new();
890		assets.subsume_assets(asset_to_holding(CNF(40)));
891		assets.subsume_assets(asset_to_holding(CF(3000)));
892		assets.subsume_assets(asset_to_holding(CNF(80)));
893		let all = assets.try_take(WildAsset::AllCounted(6).into()).unwrap();
894		let all_vec: Vec<_> = all.assets_iter().collect();
895		assert_eq!(all_vec, vec![CF(3000), CNF(40), CNF(80)]);
896	}
897
898	#[test]
899	fn try_take_fungibles_counted_works() {
900		let mut assets = AssetsInHolding::new();
901		assets.subsume_assets(asset_to_holding(CNF(40)));
902		assets.subsume_assets(asset_to_holding(CF(3000)));
903		assets.subsume_assets(asset_to_holding(CNF(80)));
904		let assets_vec: Vec<_> = assets.assets_iter().collect();
905		assert_eq!(assets_vec, vec![CF(3000), CNF(40), CNF(80)]);
906	}
907
908	#[test]
909	fn try_take_non_fungibles_counted_works() {
910		let mut assets = AssetsInHolding::new();
911		assets.subsume_assets(asset_to_holding(CNF(40)));
912		assets.subsume_assets(asset_to_holding(CF(3000)));
913		assets.subsume_assets(asset_to_holding(CNF(80)));
914		let assets_vec: Vec<_> = assets.assets_iter().collect();
915		assert_eq!(assets_vec, vec![CF(3000), CNF(40), CNF(80)]);
916	}
917}