referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
currency_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::Currency` through XCM.
18
19#![allow(deprecated)]
20
21use super::MintLocation;
22use alloc::boxed::Box;
23use core::{fmt::Debug, marker::PhantomData, result};
24use frame_support::{
25	defensive_assert,
26	traits::{
27		tokens::imbalance::{ImbalanceAccounting, UnsafeManualAccounting},
28		ExistenceRequirement::AllowDeath,
29		Get, Imbalance as ImbalanceT, WithdrawReasons,
30	},
31};
32use sp_runtime::traits::CheckedSub;
33use xcm::latest::{Asset, Error as XcmError, Location, Result, XcmContext};
34use xcm_executor::{
35	traits::{ConvertLocation, MatchesFungible, TransactAsset},
36	AssetsInHolding,
37};
38
39/// Asset transaction errors.
40enum Error {
41	/// The given asset is not handled. (According to [`XcmError::AssetNotFound`])
42	AssetNotHandled,
43	/// `Location` to `AccountId` conversion failed.
44	AccountIdConversionFailed,
45}
46
47impl From<Error> for XcmError {
48	fn from(e: Error) -> Self {
49		use XcmError::FailedToTransactAsset;
50		match e {
51			Error::AssetNotHandled => XcmError::AssetNotFound,
52			Error::AccountIdConversionFailed => FailedToTransactAsset("AccountIdConversionFailed"),
53		}
54	}
55}
56
57/// Simple adapter to use a currency as asset transactor. This type can be used as `type
58/// AssetTransactor` in `xcm::Config`.
59///
60/// # Example
61/// ```
62/// use codec::Decode;
63/// use frame_support::{parameter_types, PalletId};
64/// use sp_runtime::traits::{AccountIdConversion, TrailingZeroInput};
65/// use xcm::latest::prelude::*;
66/// use staging_xcm_builder::{ParentIsPreset, CurrencyAdapter, IsConcrete};
67///
68/// /// Our chain's account id.
69/// type AccountId = sp_runtime::AccountId32;
70///
71/// /// Our relay chain's location.
72/// parameter_types! {
73///     pub RelayChain: Location = Parent.into();
74///     pub CheckingAccount: AccountId = PalletId(*b"checking").into_account_truncating();
75/// }
76///
77/// /// Some items that implement `ConvertLocation<AccountId>`. Can be more, but for now we just assume we accept
78/// /// messages from the parent (relay chain).
79/// pub type LocationConverter = (ParentIsPreset<AccountId>);
80///
81/// /// Just a dummy implementation of `Currency`. Normally this would be `Balances`.
82/// pub type CurrencyImpl = ();
83///
84/// /// Final currency adapter. This can be used in `xcm::Config` to specify how asset related transactions happen.
85/// pub type AssetTransactor = CurrencyAdapter<
86///     // Use this `Currency` impl instance:
87///     CurrencyImpl,
88///     // The matcher: use the currency when the asset is a concrete asset in our relay chain.
89///     IsConcrete<RelayChain>,
90///     // The local converter: default account of the parent relay chain.
91///     LocationConverter,
92///     // Our chain's account ID type.
93///     AccountId,
94///     // The checking account. Can be any deterministic inaccessible account.
95///     CheckingAccount,
96/// >;
97/// ```
98#[deprecated = "Use `FungibleAdapter` instead"]
99pub struct CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>(
100	PhantomData<(Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount)>,
101);
102
103impl<
104		Currency: frame_support::traits::Currency<AccountId>,
105		Matcher: MatchesFungible<Currency::Balance>,
106		AccountIdConverter: ConvertLocation<AccountId>,
107		AccountId: Clone, // can't get away without it since Currency is generic over it.
108		CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
109	> CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
110{
111	fn can_accrue_checked(_checked_account: AccountId, _amount: Currency::Balance) -> Result {
112		Ok(())
113	}
114	fn can_reduce_checked(checked_account: AccountId, amount: Currency::Balance) -> Result {
115		let new_balance = Currency::free_balance(&checked_account)
116			.checked_sub(&amount)
117			.ok_or(XcmError::NotWithdrawable)?;
118		Currency::ensure_can_withdraw(
119			&checked_account,
120			amount,
121			WithdrawReasons::TRANSFER,
122			new_balance,
123		)
124		.map_err(|error| {
125			tracing::debug!(target: "xcm::currency_adapter", ?error, "Failed to ensure can withdraw");
126			XcmError::NotWithdrawable
127		})
128	}
129	fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) {
130		let _ = Currency::deposit_creating(&checked_account, amount);
131		Currency::deactivate(amount);
132	}
133	fn reduce_checked(checked_account: AccountId, amount: Currency::Balance) {
134		let ok =
135			Currency::withdraw(&checked_account, amount, WithdrawReasons::TRANSFER, AllowDeath)
136				.is_ok();
137		if ok {
138			Currency::reactivate(amount);
139		} else {
140			frame_support::defensive!(
141				"`can_check_in` must have returned `true` immediately prior; qed"
142			);
143		}
144	}
145}
146
147impl<
148		Currency: frame_support::traits::Currency<
149			AccountId,
150			NegativeImbalance: ImbalanceAccounting<u128> + 'static,
151		>,
152		Matcher: MatchesFungible<Currency::Balance>,
153		AccountIdConverter: ConvertLocation<AccountId>,
154		AccountId: Clone + Debug, // can't get away without it since Currency is generic over it.
155		CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
156	> TransactAsset
157	for CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
158{
159	fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> Result {
160		tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "can_check_in origin");
161		// Check we handle this asset.
162		let amount: Currency::Balance =
163			Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
164		match CheckedAccount::get() {
165			Some((checked_account, MintLocation::Local)) => {
166				Self::can_reduce_checked(checked_account, amount)
167			},
168			Some((checked_account, MintLocation::NonLocal)) => {
169				Self::can_accrue_checked(checked_account, amount)
170			},
171			None => Ok(()),
172		}
173	}
174
175	fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
176		tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "check_in origin");
177		if let Some(amount) = Matcher::matches_fungible(what) {
178			match CheckedAccount::get() {
179				Some((checked_account, MintLocation::Local)) => {
180					Self::reduce_checked(checked_account, amount)
181				},
182				Some((checked_account, MintLocation::NonLocal)) => {
183					Self::accrue_checked(checked_account, amount)
184				},
185				None => (),
186			}
187		}
188	}
189
190	fn can_check_out(dest: &Location, what: &Asset, _context: &XcmContext) -> Result {
191		tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "can_check_out");
192		let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
193		match CheckedAccount::get() {
194			Some((checked_account, MintLocation::Local)) => {
195				Self::can_accrue_checked(checked_account, amount)
196			},
197			Some((checked_account, MintLocation::NonLocal)) => {
198				Self::can_reduce_checked(checked_account, amount)
199			},
200			None => Ok(()),
201		}
202	}
203
204	fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
205		tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "check_out");
206		if let Some(amount) = Matcher::matches_fungible(what) {
207			match CheckedAccount::get() {
208				Some((checked_account, MintLocation::Local)) => {
209					Self::accrue_checked(checked_account, amount)
210				},
211				Some((checked_account, MintLocation::NonLocal)) => {
212					Self::reduce_checked(checked_account, amount)
213				},
214				None => (),
215			}
216		}
217	}
218
219	fn deposit_asset(
220		mut what: AssetsInHolding,
221		who: &Location,
222		_context: Option<&XcmContext>,
223	) -> result::Result<(), (AssetsInHolding, XcmError)> {
224		tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "deposit_asset");
225		defensive_assert!(what.len() == 1, "Trying to deposit more than one asset!");
226		// Check we handle this asset.
227		let maybe = what
228			.fungible_assets_iter()
229			.next()
230			.and_then(|asset| Matcher::matches_fungible(&asset).map(|_| asset.id));
231		let Some(asset_id) = maybe else { return Err((what, Error::AssetNotHandled.into())) };
232		let Some(who) = AccountIdConverter::convert_location(who) else {
233			return Err((what, Error::AccountIdConversionFailed.into()));
234		};
235		let Some(imbalance) = what.fungible.remove(&asset_id) else {
236			return Err((what, Error::AssetNotHandled.into()));
237		};
238		// "manually" build the concrete credit and move the imbalance there.
239		let mut credit = Currency::NegativeImbalance::zero();
240		credit.saturating_subsume(imbalance);
241		Currency::resolve_creating(&who, credit);
242		Ok(())
243	}
244
245	fn withdraw_asset(
246		what: &Asset,
247		who: &Location,
248		_maybe_context: Option<&XcmContext>,
249	) -> result::Result<AssetsInHolding, XcmError> {
250		tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "withdraw_asset");
251		// Check we handle this asset.
252		let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
253		let who =
254			AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?;
255		let credit = Currency::withdraw(&who, amount, WithdrawReasons::TRANSFER, AllowDeath).map_err(
256			|error| {
257				tracing::debug!(target: "xcm::currency_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
258				XcmError::FailedToTransactAsset(error.into())
259			},
260		)?;
261		Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
262	}
263
264	fn internal_transfer_asset(
265		asset: &Asset,
266		from: &Location,
267		to: &Location,
268		_context: &XcmContext,
269	) -> result::Result<Asset, XcmError> {
270		tracing::trace!(target: "xcm::currency_adapter", ?asset, ?from, ?to, "internal_transfer_asset");
271		let amount = Matcher::matches_fungible(asset).ok_or(Error::AssetNotHandled)?;
272		let from =
273			AccountIdConverter::convert_location(from).ok_or(Error::AccountIdConversionFailed)?;
274		let to =
275			AccountIdConverter::convert_location(to).ok_or(Error::AccountIdConversionFailed)?;
276		Currency::transfer(&from, &to, amount, AllowDeath).map_err(|error| {
277			tracing::debug!(target: "xcm::currency_adapter", ?error, ?from, ?to, ?amount, "Failed to transfer asset");
278			XcmError::FailedToTransactAsset(error.into())
279		})?;
280		Ok(asset.clone())
281	}
282
283	fn mint_asset(what: &Asset, context: &XcmContext) -> result::Result<AssetsInHolding, XcmError> {
284		tracing::trace!(target: "xcm::currency_adapter", ?what, ?context, "mint_asset");
285		// Check we handle this asset.
286		let amount = Matcher::matches_fungible(&what).ok_or(Error::AssetNotHandled)?;
287		let credit = Currency::issue(amount);
288		Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
289	}
290}