referrerpolicy=no-referrer-when-downgrade

assets_common/
fungible_conversion.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Runtime API definition for assets.
17
18use crate::runtime_api::FungiblesAccessError;
19use alloc::vec::Vec;
20use core::borrow::Borrow;
21use frame_support::traits::Contains;
22use sp_runtime::traits::MaybeEquivalence;
23use xcm::latest::{Asset, Location};
24use xcm_builder::{ConvertedConcreteId, MatchedConvertedConcreteId};
25use xcm_executor::traits::MatchesFungibles;
26
27/// Converting any [`(AssetId, Balance)`] to [`Asset`]
28pub trait AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>:
29	MatchesFungibles<AssetId, Balance>
30where
31	AssetId: Clone,
32	Balance: Clone,
33	ConvertAssetId: MaybeEquivalence<Location, AssetId>,
34	ConvertBalance: MaybeEquivalence<u128, Balance>,
35{
36	fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result<Asset, FungiblesAccessError>;
37}
38
39/// Checks for `Location`.
40pub trait MatchesLocation<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>:
41	MatchesFungibles<AssetId, Balance>
42where
43	AssetId: Clone,
44	Balance: Clone,
45	MatchAssetId: Contains<Location>,
46	ConvertAssetId: MaybeEquivalence<Location, AssetId>,
47	ConvertBalance: MaybeEquivalence<u128, Balance>,
48{
49	fn contains(location: &Location) -> bool;
50}
51
52impl<
53		AssetId: Clone,
54		Balance: Clone,
55		ConvertAssetId: MaybeEquivalence<Location, AssetId>,
56		ConvertBalance: MaybeEquivalence<u128, Balance>,
57	> AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>
58	for ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertBalance>
59{
60	fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result<Asset, FungiblesAccessError> {
61		let (asset_id, balance) = value.borrow();
62		match ConvertAssetId::convert_back(asset_id) {
63			Some(asset_id_as_location) => match ConvertBalance::convert_back(balance) {
64				Some(amount) => Ok((asset_id_as_location, amount).into()),
65				None => Err(FungiblesAccessError::AmountToBalanceConversionFailed),
66			},
67			None => Err(FungiblesAccessError::AssetIdConversionFailed),
68		}
69	}
70}
71
72impl<
73		AssetId: Clone,
74		Balance: Clone,
75		MatchAssetId: Contains<Location>,
76		ConvertAssetId: MaybeEquivalence<Location, AssetId>,
77		ConvertBalance: MaybeEquivalence<u128, Balance>,
78	> AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>
79	for MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
80{
81	fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result<Asset, FungiblesAccessError> {
82		let (asset_id, balance) = value.borrow();
83		match ConvertAssetId::convert_back(asset_id) {
84			Some(asset_id_as_location) => match ConvertBalance::convert_back(balance) {
85				Some(amount) => Ok((asset_id_as_location, amount).into()),
86				None => Err(FungiblesAccessError::AmountToBalanceConversionFailed),
87			},
88			None => Err(FungiblesAccessError::AssetIdConversionFailed),
89		}
90	}
91}
92
93impl<
94		AssetId: Clone,
95		Balance: Clone,
96		MatchAssetId: Contains<Location>,
97		ConvertAssetId: MaybeEquivalence<Location, AssetId>,
98		ConvertBalance: MaybeEquivalence<u128, Balance>,
99	> MatchesLocation<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
100	for MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
101{
102	fn contains(location: &Location) -> bool {
103		MatchAssetId::contains(location)
104	}
105}
106
107#[impl_trait_for_tuples::impl_for_tuples(30)]
108impl<
109		AssetId: Clone,
110		Balance: Clone,
111		MatchAssetId: Contains<Location>,
112		ConvertAssetId: MaybeEquivalence<Location, AssetId>,
113		ConvertBalance: MaybeEquivalence<u128, Balance>,
114	> MatchesLocation<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance> for Tuple
115{
116	fn contains(location: &Location) -> bool {
117		for_tuples!( #(
118			match Tuple::contains(location) { o @ true => return o, _ => () }
119		)* );
120		tracing::trace!(target: "xcm::contains", ?location, "MatchesLocation: no match");
121		false
122	}
123}
124
125/// Helper function to convert collections with [`(AssetId, Balance)`] to [`Asset`]
126pub fn convert<'a, AssetId, Balance, ConvertAssetId, ConvertBalance, Converter>(
127	items: impl Iterator<Item = &'a (AssetId, Balance)>,
128) -> Result<Vec<Asset>, FungiblesAccessError>
129where
130	AssetId: Clone + 'a,
131	Balance: Clone + 'a,
132	ConvertAssetId: MaybeEquivalence<Location, AssetId>,
133	ConvertBalance: MaybeEquivalence<u128, Balance>,
134	Converter: AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>,
135{
136	items.map(Converter::convert_ref).collect()
137}
138
139/// Helper function to convert `Balance` with Location` to `Asset`
140pub fn convert_balance<T: frame_support::pallet_prelude::Get<Location>, Balance: TryInto<u128>>(
141	balance: Balance,
142) -> Result<Asset, FungiblesAccessError> {
143	match balance.try_into() {
144		Ok(balance) => Ok((T::get(), balance).into()),
145		Err(_) => Err(FungiblesAccessError::AmountToBalanceConversionFailed),
146	}
147}
148
149#[cfg(test)]
150mod tests {
151	use super::*;
152	use frame_support::traits::Everything;
153
154	use xcm::latest::prelude::*;
155	use xcm_executor::traits::{Identity, JustTry};
156
157	type Converter = MatchedConvertedConcreteId<Location, u64, Everything, Identity, JustTry>;
158
159	#[test]
160	fn converted_concrete_id_fungible_multi_asset_conversion_roundtrip_works() {
161		let location = Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))]);
162		let amount = 123456_u64;
163		let expected_multi_asset = Asset {
164			id: AssetId(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))])),
165			fun: Fungible(123456_u128),
166		};
167
168		assert_eq!(
169			Converter::matches_fungibles(&expected_multi_asset).map_err(|_| ()),
170			Ok((location.clone(), amount))
171		);
172
173		assert_eq!(Converter::convert_ref((location, amount)), Ok(expected_multi_asset));
174	}
175
176	#[test]
177	fn converted_concrete_id_fungible_multi_asset_conversion_collection_works() {
178		let data = vec![
179			(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))]), 123456_u64),
180			(Location::new(1, [GlobalConsensus(ByGenesis([1; 32]))]), 654321_u64),
181		];
182
183		let expected_data = vec![
184			Asset {
185				id: AssetId(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))])),
186				fun: Fungible(123456_u128),
187			},
188			Asset {
189				id: AssetId(Location::new(1, [GlobalConsensus(ByGenesis([1; 32]))])),
190				fun: Fungible(654321_u128),
191			},
192		];
193
194		assert_eq!(convert::<_, _, _, _, Converter>(data.iter()), Ok(expected_data));
195	}
196}