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 alloc::boxed::Box;
21use core::{fmt::Debug, marker::PhantomData, result};
22use frame_support::{
23	defensive_assert,
24	traits::{
25		tokens::{
26			fungible,
27			imbalance::{ImbalanceAccounting, UnsafeManualAccounting},
28			Fortitude::Polite,
29			Precision::Exact,
30			Preservation::Expendable,
31			Provenance::Minted,
32		},
33		Get, Imbalance as ImbalanceT,
34	},
35};
36use xcm::latest::prelude::*;
37use xcm_executor::{
38	traits::{ConvertLocation, Error as MatchError, MatchesFungible, TransactAsset},
39	AssetsInHolding,
40};
41
42/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
43/// handling an asset in the XCM executor.
44/// Only works for transfers.
45pub struct FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>(
46	PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId)>,
47);
48impl<
49		Fungible: fungible::Mutate<AccountId>,
50		Matcher: MatchesFungible<Fungible::Balance>,
51		AccountIdConverter: ConvertLocation<AccountId>,
52		AccountId: Eq + Clone + Debug,
53	> TransactAsset for FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>
54{
55	fn internal_transfer_asset(
56		what: &Asset,
57		from: &Location,
58		to: &Location,
59		_context: &XcmContext,
60	) -> result::Result<Asset, XcmError> {
61		tracing::trace!(
62			target: "xcm::fungible_adapter",
63			?what, ?from, ?to,
64			"internal_transfer_asset",
65		);
66		// Check we handle the asset
67		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
68		let source = AccountIdConverter::convert_location(from)
69			.ok_or(MatchError::AccountIdConversionFailed)?;
70		let dest = AccountIdConverter::convert_location(to)
71			.ok_or(MatchError::AccountIdConversionFailed)?;
72		Fungible::transfer(&source, &dest, amount, Expendable).map_err(|error| {
73			tracing::debug!(
74				target: "xcm::fungible_adapter", ?error, ?source, ?dest, ?amount,
75				"Failed to transfer asset",
76			);
77			XcmError::FailedToTransactAsset(error.into())
78		})?;
79		Ok(what.clone())
80	}
81}
82
83/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
84/// handling an asset in the XCM executor.
85/// Works for everything but transfers.
86pub struct FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
87	PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
88);
89
90impl<
91		Fungible: fungible::Mutate<AccountId>,
92		Matcher: MatchesFungible<Fungible::Balance>,
93		AccountIdConverter: ConvertLocation<AccountId>,
94		AccountId: Eq + Clone + Debug,
95		CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
96	> FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
97{
98	fn can_accrue_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
99		Fungible::can_deposit(&checking_account, amount, Minted)
100			.into_result()
101			.map_err(|error| {
102				tracing::debug!(
103					target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
104					"Failed to deposit funds into account",
105				);
106				XcmError::NotDepositable
107			})
108	}
109
110	fn can_reduce_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
111		Fungible::can_withdraw(&checking_account, amount)
112			.into_result(false)
113			.map_err(|error| {
114				tracing::debug!(
115					target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
116					"Failed to withdraw funds from account",
117				);
118				XcmError::NotWithdrawable
119			})
120			.map(|_| ())
121	}
122
123	fn accrue_checked(checking_account: AccountId, amount: Fungible::Balance) {
124		let ok = Fungible::mint_into(&checking_account, amount).is_ok();
125		debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
126	}
127
128	fn reduce_checked(checking_account: AccountId, amount: Fungible::Balance) {
129		let ok = Fungible::burn_from(&checking_account, amount, Expendable, Exact, Polite).is_ok();
130		debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
131	}
132}
133
134impl<
135		Fungible: fungible::Inspect<AccountId, Balance: 'static>
136			+ fungible::Mutate<AccountId>
137			+ fungible::Balanced<AccountId, OnDropCredit: 'static, OnDropDebt: 'static>,
138		Matcher: MatchesFungible<Fungible::Balance>,
139		AccountIdConverter: ConvertLocation<AccountId>,
140		AccountId: Eq + Clone + Debug,
141		CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
142	> TransactAsset
143	for FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
144where
145	fungible::Imbalance<
146		<Fungible as fungible::Inspect<AccountId>>::Balance,
147		<Fungible as fungible::Balanced<AccountId>>::OnDropCredit,
148		<Fungible as fungible::Balanced<AccountId>>::OnDropDebt,
149	>: ImbalanceAccounting<u128>,
150{
151	fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
152		tracing::trace!(
153			target: "xcm::fungible_adapter",
154			?origin, ?what,
155			"can_check_in origin",
156		);
157		// Check we handle this asset
158		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
159		match CheckingAccount::get() {
160			Some((checking_account, MintLocation::Local)) => {
161				Self::can_reduce_checked(checking_account, amount)
162			},
163			Some((checking_account, MintLocation::NonLocal)) => {
164				Self::can_accrue_checked(checking_account, amount)
165			},
166			None => Ok(()),
167		}
168	}
169
170	fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
171		tracing::trace!(
172			target: "xcm::fungible_adapter",
173			?origin, ?what,
174			"check_in origin",
175		);
176		if let Some(amount) = Matcher::matches_fungible(what) {
177			match CheckingAccount::get() {
178				Some((checking_account, MintLocation::Local)) => {
179					Self::reduce_checked(checking_account, amount)
180				},
181				Some((checking_account, MintLocation::NonLocal)) => {
182					Self::accrue_checked(checking_account, amount)
183				},
184				None => (),
185			}
186		}
187	}
188
189	fn can_check_out(dest: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
190		tracing::trace!(
191			target: "xcm::fungible_adapter",
192			?dest,
193			?what,
194			"can_check_out",
195		);
196		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
197		match CheckingAccount::get() {
198			Some((checking_account, MintLocation::Local)) => {
199				Self::can_accrue_checked(checking_account, amount)
200			},
201			Some((checking_account, MintLocation::NonLocal)) => {
202				Self::can_reduce_checked(checking_account, amount)
203			},
204			None => Ok(()),
205		}
206	}
207
208	fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
209		tracing::trace!(
210			target: "xcm::fungible_adapter",
211			?dest,
212			?what,
213			"check_out",
214		);
215		if let Some(amount) = Matcher::matches_fungible(what) {
216			match CheckingAccount::get() {
217				Some((checking_account, MintLocation::Local)) => {
218					Self::accrue_checked(checking_account, amount)
219				},
220				Some((checking_account, MintLocation::NonLocal)) => {
221					Self::reduce_checked(checking_account, amount)
222				},
223				None => (),
224			}
225		}
226	}
227
228	fn deposit_asset(
229		mut what: AssetsInHolding,
230		who: &Location,
231		_context: Option<&XcmContext>,
232	) -> Result<(), (AssetsInHolding, XcmError)> {
233		tracing::trace!(
234			target: "xcm::fungible_adapter",
235			?what, ?who,
236			"deposit_asset",
237		);
238		defensive_assert!(what.len() == 1, "Trying to deposit more than one asset!");
239		// Check we handle this asset.
240		let maybe = what
241			.fungible_assets_iter()
242			.next()
243			.and_then(|asset| Matcher::matches_fungible(&asset).map(|amount| (asset.id, amount)));
244		let Some((asset_id, amount)) = maybe else {
245			return Err((what, MatchError::AssetNotHandled.into()));
246		};
247		let Some(who) = AccountIdConverter::convert_location(who) else {
248			return Err((what, MatchError::AccountIdConversionFailed.into()));
249		};
250		let Some(imbalance) = what.fungible.remove(&asset_id) else {
251			return Err((what, MatchError::AssetNotHandled.into()));
252		};
253		// "manually" build the concrete credit and move the imbalance there.
254		let mut credit = fungible::Credit::<AccountId, Fungible>::zero();
255		credit.saturating_subsume(imbalance);
256		Fungible::resolve(&who, credit).map_err(|unspent| {
257			tracing::debug!(target: "xcm::fungible_adapter", ?asset_id, ?who, ?amount, "Failed to deposit asset");
258			(
259				AssetsInHolding::new_from_fungible_credit(asset_id, Box::new(unspent)),
260				XcmError::FailedToTransactAsset("")
261			)
262		})?;
263		Ok(())
264	}
265
266	fn withdraw_asset(
267		what: &Asset,
268		who: &Location,
269		_context: Option<&XcmContext>,
270	) -> result::Result<AssetsInHolding, XcmError> {
271		tracing::trace!(
272			target: "xcm::fungible_adapter",
273			?what, ?who,
274			"withdraw_asset",
275		);
276		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
277		let who = AccountIdConverter::convert_location(who)
278			.ok_or(MatchError::AccountIdConversionFailed)?;
279		let credit = Fungible::withdraw(&who, amount, Exact, Expendable, Polite).map_err(|error| {
280			tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
281			XcmError::FailedToTransactAsset(error.into())
282		})?;
283		Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
284	}
285
286	fn mint_asset(what: &Asset, context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
287		tracing::trace!(
288			target: "xcm::fungible_adapter",
289			?what, ?context,
290			"mint_asset",
291		);
292		let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
293		let credit = Fungible::issue(amount);
294		Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
295	}
296}
297
298/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
299/// handling an asset in the XCM executor.
300/// Works for everything, transfers and teleport bookkeeping.
301pub struct FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
302	PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
303);
304impl<
305		Fungible: fungible::Inspect<AccountId, Balance: 'static>
306			+ fungible::Mutate<AccountId>
307			+ fungible::Balanced<AccountId, OnDropCredit: 'static, OnDropDebt: 'static>,
308		Matcher: MatchesFungible<Fungible::Balance>,
309		AccountIdConverter: ConvertLocation<AccountId>,
310		AccountId: Eq + Clone + Debug,
311		CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
312	> TransactAsset
313	for FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
314where
315	fungible::Imbalance<
316		<Fungible as fungible::Inspect<AccountId>>::Balance,
317		<Fungible as fungible::Balanced<AccountId>>::OnDropCredit,
318		<Fungible as fungible::Balanced<AccountId>>::OnDropDebt,
319	>: ImbalanceAccounting<u128>,
320{
321	fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
322		FungibleMutateAdapter::<
323			Fungible,
324			Matcher,
325			AccountIdConverter,
326			AccountId,
327			CheckingAccount,
328		>::can_check_in(origin, what, context)
329	}
330
331	fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
332		FungibleMutateAdapter::<
333			Fungible,
334			Matcher,
335			AccountIdConverter,
336			AccountId,
337			CheckingAccount,
338		>::check_in(origin, what, context)
339	}
340
341	fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
342		FungibleMutateAdapter::<
343			Fungible,
344			Matcher,
345			AccountIdConverter,
346			AccountId,
347			CheckingAccount,
348		>::can_check_out(dest, what, context)
349	}
350
351	fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
352		FungibleMutateAdapter::<
353			Fungible,
354			Matcher,
355			AccountIdConverter,
356			AccountId,
357			CheckingAccount,
358		>::check_out(dest, what, context)
359	}
360
361	fn deposit_asset(
362		what: AssetsInHolding,
363		who: &Location,
364		context: Option<&XcmContext>,
365	) -> Result<(), (AssetsInHolding, XcmError)> {
366		FungibleMutateAdapter::<
367			Fungible,
368			Matcher,
369			AccountIdConverter,
370			AccountId,
371			CheckingAccount,
372		>::deposit_asset(what, who, context)
373	}
374
375	fn withdraw_asset(
376		what: &Asset,
377		who: &Location,
378		maybe_context: Option<&XcmContext>,
379	) -> result::Result<AssetsInHolding, XcmError> {
380		FungibleMutateAdapter::<
381			Fungible,
382			Matcher,
383			AccountIdConverter,
384			AccountId,
385			CheckingAccount,
386		>::withdraw_asset(what, who, maybe_context)
387	}
388
389	fn internal_transfer_asset(
390		what: &Asset,
391		from: &Location,
392		to: &Location,
393		context: &XcmContext,
394	) -> result::Result<Asset, XcmError> {
395		FungibleTransferAdapter::<Fungible, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
396			what, from, to, context
397		)
398	}
399
400	fn mint_asset(what: &Asset, context: &XcmContext) -> result::Result<AssetsInHolding, XcmError> {
401		FungibleMutateAdapter::<
402			Fungible,
403			Matcher,
404			AccountIdConverter,
405			AccountId,
406			CheckingAccount,
407		>::mint_asset(
408			what, context,
409		)
410	}
411}