1#![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
39enum Error {
41 AssetNotHandled,
43 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#[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, 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, 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 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 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 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 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 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}