referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
fungible_adapter.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
17//! Adapters to work with [`frame_support::traits::fungible`] through XCM.
18
19use super::MintLocation;
20use core::{fmt::Debug, marker::PhantomData, result};
21use frame_support::traits::{
22	tokens::{
23		fungible, Fortitude::Polite, Precision::Exact, Preservation::Expendable, Provenance::Minted,
24	},
25	Get,
26};
27use xcm::latest::prelude::*;
28use xcm_executor::{
29	traits::{ConvertLocation, Error as MatchError, MatchesFungible, TransactAsset},
30	AssetsInHolding,
31};
32
33/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
34/// handling an asset in the XCM executor.
35/// Only works for transfers.
36pub struct FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>(
37	PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId)>,
38);
39impl<
40		Fungible: fungible::Mutate<AccountId>,
41		Matcher: MatchesFungible<Fungible::Balance>,
42		AccountIdConverter: ConvertLocation<AccountId>,
43		AccountId: Eq + Clone + Debug,
44	> TransactAsset for FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>
45{
46	fn internal_transfer_asset(
47		what: &Asset,
48		from: &Location,
49		to: &Location,
50		_context: &XcmContext,
51	) -> result::Result<AssetsInHolding, XcmError> {
52		tracing::trace!(
53			target: "xcm::fungible_adapter",
54			?what, ?from, ?to,
55			"internal_transfer_asset",
56		);
57		// Check we handle the asset
58		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
59		let source = AccountIdConverter::convert_location(from)
60			.ok_or(MatchError::AccountIdConversionFailed)?;
61		let dest = AccountIdConverter::convert_location(to)
62			.ok_or(MatchError::AccountIdConversionFailed)?;
63		Fungible::transfer(&source, &dest, amount, Expendable).map_err(|error| {
64			tracing::debug!(
65				target: "xcm::fungible_adapter", ?error, ?source, ?dest, ?amount,
66				"Failed to transfer asset",
67			);
68			XcmError::FailedToTransactAsset(error.into())
69		})?;
70		Ok(what.clone().into())
71	}
72}
73
74/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
75/// handling an asset in the XCM executor.
76/// Works for everything but transfers.
77pub struct FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
78	PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
79);
80
81impl<
82		Fungible: fungible::Mutate<AccountId>,
83		Matcher: MatchesFungible<Fungible::Balance>,
84		AccountIdConverter: ConvertLocation<AccountId>,
85		AccountId: Eq + Clone + Debug,
86		CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
87	> FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
88{
89	fn can_accrue_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
90		Fungible::can_deposit(&checking_account, amount, Minted)
91			.into_result()
92			.map_err(|error| {
93				tracing::debug!(
94					target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
95					"Failed to deposit funds into account",
96				);
97				XcmError::NotDepositable
98			})
99	}
100
101	fn can_reduce_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
102		Fungible::can_withdraw(&checking_account, amount)
103			.into_result(false)
104			.map_err(|error| {
105				tracing::debug!(
106					target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
107					"Failed to withdraw funds from account",
108				);
109				XcmError::NotWithdrawable
110			})
111			.map(|_| ())
112	}
113
114	fn accrue_checked(checking_account: AccountId, amount: Fungible::Balance) {
115		let ok = Fungible::mint_into(&checking_account, amount).is_ok();
116		debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
117	}
118
119	fn reduce_checked(checking_account: AccountId, amount: Fungible::Balance) {
120		let ok = Fungible::burn_from(&checking_account, amount, Expendable, Exact, Polite).is_ok();
121		debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
122	}
123}
124
125impl<
126		Fungible: fungible::Mutate<AccountId>,
127		Matcher: MatchesFungible<Fungible::Balance>,
128		AccountIdConverter: ConvertLocation<AccountId>,
129		AccountId: Eq + Clone + Debug,
130		CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
131	> TransactAsset
132	for FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
133{
134	fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
135		tracing::trace!(
136			target: "xcm::fungible_adapter",
137			?origin, ?what,
138			"can_check_in origin",
139		);
140		// Check we handle this asset
141		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
142		match CheckingAccount::get() {
143			Some((checking_account, MintLocation::Local)) =>
144				Self::can_reduce_checked(checking_account, amount),
145			Some((checking_account, MintLocation::NonLocal)) =>
146				Self::can_accrue_checked(checking_account, amount),
147			None => Ok(()),
148		}
149	}
150
151	fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
152		tracing::trace!(
153			target: "xcm::fungible_adapter",
154			?origin, ?what,
155			"check_in origin",
156		);
157		if let Some(amount) = Matcher::matches_fungible(what) {
158			match CheckingAccount::get() {
159				Some((checking_account, MintLocation::Local)) =>
160					Self::reduce_checked(checking_account, amount),
161				Some((checking_account, MintLocation::NonLocal)) =>
162					Self::accrue_checked(checking_account, amount),
163				None => (),
164			}
165		}
166	}
167
168	fn can_check_out(dest: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
169		tracing::trace!(
170			target: "xcm::fungible_adapter",
171			?dest,
172			?what,
173			"can_check_out",
174		);
175		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
176		match CheckingAccount::get() {
177			Some((checking_account, MintLocation::Local)) =>
178				Self::can_accrue_checked(checking_account, amount),
179			Some((checking_account, MintLocation::NonLocal)) =>
180				Self::can_reduce_checked(checking_account, amount),
181			None => Ok(()),
182		}
183	}
184
185	fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
186		tracing::trace!(
187			target: "xcm::fungible_adapter",
188			?dest,
189			?what,
190			"check_out",
191		);
192		if let Some(amount) = Matcher::matches_fungible(what) {
193			match CheckingAccount::get() {
194				Some((checking_account, MintLocation::Local)) =>
195					Self::accrue_checked(checking_account, amount),
196				Some((checking_account, MintLocation::NonLocal)) =>
197					Self::reduce_checked(checking_account, amount),
198				None => (),
199			}
200		}
201	}
202
203	fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
204		tracing::trace!(
205			target: "xcm::fungible_adapter",
206			?what, ?who,
207			"deposit_asset",
208		);
209		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
210		let who = AccountIdConverter::convert_location(who)
211			.ok_or(MatchError::AccountIdConversionFailed)?;
212		Fungible::mint_into(&who, amount).map_err(|error| {
213			tracing::debug!(
214				target: "xcm::fungible_adapter", ?error, ?who, ?amount,
215				"Failed to deposit assets",
216			);
217			XcmError::FailedToTransactAsset(error.into())
218		})?;
219		Ok(())
220	}
221
222	fn withdraw_asset(
223		what: &Asset,
224		who: &Location,
225		_context: Option<&XcmContext>,
226	) -> result::Result<AssetsInHolding, XcmError> {
227		tracing::trace!(
228			target: "xcm::fungible_adapter",
229			?what, ?who,
230			"withdraw_asset",
231		);
232		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
233		let who = AccountIdConverter::convert_location(who)
234			.ok_or(MatchError::AccountIdConversionFailed)?;
235		Fungible::burn_from(&who, amount, Expendable, Exact, Polite).map_err(|error| {
236			tracing::debug!(
237				target: "xcm::fungible_adapter", ?error, ?who, ?amount,
238				"Failed to withdraw assets",
239			);
240			XcmError::FailedToTransactAsset(error.into())
241		})?;
242		Ok(what.clone().into())
243	}
244}
245
246/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
247/// handling an asset in the XCM executor.
248/// Works for everything, transfers and teleport bookkeeping.
249pub struct FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
250	PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
251);
252impl<
253		Fungible: fungible::Mutate<AccountId>,
254		Matcher: MatchesFungible<Fungible::Balance>,
255		AccountIdConverter: ConvertLocation<AccountId>,
256		AccountId: Eq + Clone + Debug,
257		CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
258	> TransactAsset
259	for FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
260{
261	fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
262		FungibleMutateAdapter::<
263			Fungible,
264			Matcher,
265			AccountIdConverter,
266			AccountId,
267			CheckingAccount,
268		>::can_check_in(origin, what, context)
269	}
270
271	fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
272		FungibleMutateAdapter::<
273			Fungible,
274			Matcher,
275			AccountIdConverter,
276			AccountId,
277			CheckingAccount,
278		>::check_in(origin, what, context)
279	}
280
281	fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
282		FungibleMutateAdapter::<
283			Fungible,
284			Matcher,
285			AccountIdConverter,
286			AccountId,
287			CheckingAccount,
288		>::can_check_out(dest, what, context)
289	}
290
291	fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
292		FungibleMutateAdapter::<
293			Fungible,
294			Matcher,
295			AccountIdConverter,
296			AccountId,
297			CheckingAccount,
298		>::check_out(dest, what, context)
299	}
300
301	fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
302		FungibleMutateAdapter::<
303			Fungible,
304			Matcher,
305			AccountIdConverter,
306			AccountId,
307			CheckingAccount,
308		>::deposit_asset(what, who, context)
309	}
310
311	fn withdraw_asset(
312		what: &Asset,
313		who: &Location,
314		maybe_context: Option<&XcmContext>,
315	) -> result::Result<AssetsInHolding, XcmError> {
316		FungibleMutateAdapter::<
317			Fungible,
318			Matcher,
319			AccountIdConverter,
320			AccountId,
321			CheckingAccount,
322		>::withdraw_asset(what, who, maybe_context)
323	}
324
325	fn internal_transfer_asset(
326		what: &Asset,
327		from: &Location,
328		to: &Location,
329		context: &XcmContext,
330	) -> result::Result<AssetsInHolding, XcmError> {
331		FungibleTransferAdapter::<Fungible, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
332			what, from, to, context
333		)
334	}
335}