1use core::{fmt::Debug, marker::PhantomData, result};
20use frame_support::traits::{
21 tokens::{
22 fungibles, Fortitude::Polite, Precision::Exact, Preservation::Expendable,
23 Provenance::Minted,
24 },
25 Contains, Get,
26};
27use xcm::latest::prelude::*;
28use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset};
29
30pub struct FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
32 PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
33);
34impl<
35 Assets: fungibles::Mutate<AccountId>,
36 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
37 AccountIdConverter: ConvertLocation<AccountId>,
38 AccountId: Eq + Clone + Debug, > TransactAsset for FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
41{
42 fn internal_transfer_asset(
43 what: &Asset,
44 from: &Location,
45 to: &Location,
46 _context: &XcmContext,
47 ) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
48 tracing::trace!(
49 target: "xcm::fungibles_adapter",
50 ?what, ?from, ?to,
51 "internal_transfer_asset"
52 );
53 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
55 let source = AccountIdConverter::convert_location(from)
56 .ok_or(MatchError::AccountIdConversionFailed)?;
57 let dest = AccountIdConverter::convert_location(to)
58 .ok_or(MatchError::AccountIdConversionFailed)?;
59 Assets::transfer(asset_id.clone(), &source, &dest, amount, Expendable).map_err(|e| {
60 tracing::debug!(target: "xcm::fungibles_adapter", error = ?e, ?asset_id, ?source, ?dest, ?amount, "Failed internal transfer asset");
61 XcmError::FailedToTransactAsset(e.into())
62 })?;
63 Ok(what.clone().into())
64 }
65}
66
67#[derive(Copy, Clone, Eq, PartialEq)]
69pub enum MintLocation {
70 Local,
73 NonLocal,
76}
77
78pub trait AssetChecking<AssetId> {
87 fn asset_checking(asset: &AssetId) -> Option<MintLocation>;
91}
92
93pub struct NoChecking;
96impl<AssetId> AssetChecking<AssetId> for NoChecking {
97 fn asset_checking(_: &AssetId) -> Option<MintLocation> {
98 None
99 }
100}
101
102pub struct LocalMint<T>(core::marker::PhantomData<T>);
105impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for LocalMint<T> {
106 fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
107 match T::contains(asset) {
108 true => Some(MintLocation::Local),
109 false => None,
110 }
111 }
112}
113
114pub struct NonLocalMint<T>(core::marker::PhantomData<T>);
117impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for NonLocalMint<T> {
118 fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
119 match T::contains(asset) {
120 true => Some(MintLocation::NonLocal),
121 false => None,
122 }
123 }
124}
125
126pub struct DualMint<L, R>(core::marker::PhantomData<(L, R)>);
130impl<AssetId, L: Contains<AssetId>, R: Contains<AssetId>> AssetChecking<AssetId>
131 for DualMint<L, R>
132{
133 fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
134 if L::contains(asset) {
135 Some(MintLocation::Local)
136 } else if R::contains(asset) {
137 Some(MintLocation::NonLocal)
138 } else {
139 None
140 }
141 }
142}
143
144pub struct FungiblesMutateAdapter<
145 Assets,
146 Matcher,
147 AccountIdConverter,
148 AccountId,
149 CheckAsset,
150 CheckingAccount,
151>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
152
153impl<
154 Assets: fungibles::Mutate<AccountId>,
155 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
156 AccountIdConverter: ConvertLocation<AccountId>,
157 AccountId: Eq + Clone + Debug, CheckAsset: AssetChecking<Assets::AssetId>,
160 CheckingAccount: Get<AccountId>,
161 >
162 FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
163{
164 fn can_accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
165 let checking_account = CheckingAccount::get();
166 Assets::can_deposit(asset_id, &checking_account, amount, Minted)
167 .into_result()
168 .map_err(|error| {
169 tracing::debug!(
170 target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
171 "Failed to check if asset can be accrued"
172 );
173 XcmError::NotDepositable
174 })
175 }
176 fn can_reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
177 let checking_account = CheckingAccount::get();
178 Assets::can_withdraw(asset_id, &checking_account, amount)
179 .into_result(false)
180 .map_err(|error| {
181 tracing::debug!(
182 target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
183 "Failed to check if asset can be reduced"
184 );
185 XcmError::NotWithdrawable
186 })
187 .map(|_| ())
188 }
189 fn accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
190 let checking_account = CheckingAccount::get();
191 let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok();
192 debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
193 }
194 fn reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
195 let checking_account = CheckingAccount::get();
196 let ok = Assets::burn_from(asset_id, &checking_account, amount, Expendable, Exact, Polite)
197 .is_ok();
198 debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
199 }
200}
201
202impl<
203 Assets: fungibles::Mutate<AccountId>,
204 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
205 AccountIdConverter: ConvertLocation<AccountId>,
206 AccountId: Eq + Clone + Debug, CheckAsset: AssetChecking<Assets::AssetId>,
209 CheckingAccount: Get<AccountId>,
210 > TransactAsset
211 for FungiblesMutateAdapter<
212 Assets,
213 Matcher,
214 AccountIdConverter,
215 AccountId,
216 CheckAsset,
217 CheckingAccount,
218 >
219{
220 fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
221 tracing::trace!(
222 target: "xcm::fungibles_adapter",
223 ?origin, ?what,
224 "can_check_in"
225 );
226 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
228 match CheckAsset::asset_checking(&asset_id) {
229 Some(MintLocation::Local) => Self::can_reduce_checked(asset_id, amount),
231 Some(MintLocation::NonLocal) => Self::can_accrue_checked(asset_id, amount),
233 _ => Ok(()),
234 }
235 }
236
237 fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
238 tracing::trace!(
239 target: "xcm::fungibles_adapter",
240 ?origin, ?what,
241 "check_in"
242 );
243 if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
244 match CheckAsset::asset_checking(&asset_id) {
245 Some(MintLocation::Local) => Self::reduce_checked(asset_id, amount),
247 Some(MintLocation::NonLocal) => Self::accrue_checked(asset_id, amount),
249 _ => (),
250 }
251 }
252 }
253
254 fn can_check_out(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
255 tracing::trace!(
256 target: "xcm::fungibles_adapter",
257 ?origin, ?what,
258 "can_check_out"
259 );
260 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
262 match CheckAsset::asset_checking(&asset_id) {
263 Some(MintLocation::Local) => Self::can_accrue_checked(asset_id, amount),
265 Some(MintLocation::NonLocal) => Self::can_reduce_checked(asset_id, amount),
267 _ => Ok(()),
268 }
269 }
270
271 fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
272 tracing::trace!(
273 target: "xcm::fungibles_adapter",
274 ?dest, ?what,
275 "check_out"
276 );
277 if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
278 match CheckAsset::asset_checking(&asset_id) {
279 Some(MintLocation::Local) => Self::accrue_checked(asset_id, amount),
281 Some(MintLocation::NonLocal) => Self::reduce_checked(asset_id, amount),
283 _ => (),
284 }
285 }
286 }
287
288 fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
289 tracing::trace!(
290 target: "xcm::fungibles_adapter",
291 ?what, ?who,
292 "deposit_asset"
293 );
294 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
296 let who = AccountIdConverter::convert_location(who)
297 .ok_or(MatchError::AccountIdConversionFailed)?;
298 Assets::mint_into(asset_id, &who, amount).map_err(|error| {
299 tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to deposit asset");
300 XcmError::FailedToTransactAsset(error.into())
301 })?;
302 Ok(())
303 }
304
305 fn withdraw_asset(
306 what: &Asset,
307 who: &Location,
308 _maybe_context: Option<&XcmContext>,
309 ) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
310 tracing::trace!(
311 target: "xcm::fungibles_adapter",
312 ?what, ?who,
313 "withdraw_asset"
314 );
315 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
317 let who = AccountIdConverter::convert_location(who)
318 .ok_or(MatchError::AccountIdConversionFailed)?;
319 Assets::burn_from(asset_id, &who, amount, Expendable, Exact, Polite).map_err(|error| {
320 tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
321 XcmError::FailedToTransactAsset(error.into())
322 })?;
323 Ok(what.clone().into())
324 }
325}
326
327pub struct FungiblesAdapter<
328 Assets,
329 Matcher,
330 AccountIdConverter,
331 AccountId,
332 CheckAsset,
333 CheckingAccount,
334>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
335impl<
336 Assets: fungibles::Mutate<AccountId>,
337 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
338 AccountIdConverter: ConvertLocation<AccountId>,
339 AccountId: Eq + Clone + Debug, CheckAsset: AssetChecking<Assets::AssetId>,
342 CheckingAccount: Get<AccountId>,
343 > TransactAsset
344 for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
345{
346 fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
347 FungiblesMutateAdapter::<
348 Assets,
349 Matcher,
350 AccountIdConverter,
351 AccountId,
352 CheckAsset,
353 CheckingAccount,
354 >::can_check_in(origin, what, context)
355 }
356
357 fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
358 FungiblesMutateAdapter::<
359 Assets,
360 Matcher,
361 AccountIdConverter,
362 AccountId,
363 CheckAsset,
364 CheckingAccount,
365 >::check_in(origin, what, context)
366 }
367
368 fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
369 FungiblesMutateAdapter::<
370 Assets,
371 Matcher,
372 AccountIdConverter,
373 AccountId,
374 CheckAsset,
375 CheckingAccount,
376 >::can_check_out(dest, what, context)
377 }
378
379 fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
380 FungiblesMutateAdapter::<
381 Assets,
382 Matcher,
383 AccountIdConverter,
384 AccountId,
385 CheckAsset,
386 CheckingAccount,
387 >::check_out(dest, what, context)
388 }
389
390 fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
391 FungiblesMutateAdapter::<
392 Assets,
393 Matcher,
394 AccountIdConverter,
395 AccountId,
396 CheckAsset,
397 CheckingAccount,
398 >::deposit_asset(what, who, context)
399 }
400
401 fn withdraw_asset(
402 what: &Asset,
403 who: &Location,
404 maybe_context: Option<&XcmContext>,
405 ) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
406 FungiblesMutateAdapter::<
407 Assets,
408 Matcher,
409 AccountIdConverter,
410 AccountId,
411 CheckAsset,
412 CheckingAccount,
413 >::withdraw_asset(what, who, maybe_context)
414 }
415
416 fn internal_transfer_asset(
417 what: &Asset,
418 from: &Location,
419 to: &Location,
420 context: &XcmContext,
421 ) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
422 FungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
423 what, from, to, context
424 )
425 }
426}