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