1use crate::{AssetChecking, MintLocation};
20use core::{fmt::Debug, marker::PhantomData, result};
21use frame_support::{
22 defensive_assert, ensure,
23 traits::{tokens::nonfungibles, Get},
24};
25use xcm::latest::prelude::*;
26use xcm_executor::{
27 traits::{ConvertLocation, Error as MatchError, MatchesNonFungibles, TransactAsset},
28 AssetsInHolding,
29};
30
31const LOG_TARGET: &str = "xcm::nonfungibles_adapter";
32
33pub struct NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
37 PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
38)
39where
40 Assets: nonfungibles::Transfer<AccountId>,
41 Assets::CollectionId: Debug,
42 Assets::ItemId: Debug;
43impl<
44 Assets: nonfungibles::Transfer<AccountId>,
45 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
46 AccountIdConverter: ConvertLocation<AccountId>,
47 AccountId: Clone + Debug, > TransactAsset for NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
49where
50 Assets::CollectionId: Debug,
51 Assets::ItemId: Debug,
52{
53 fn transfer_asset(
54 what: &Asset,
55 from: &Location,
56 to: &Location,
57 context: &XcmContext,
58 ) -> Result<Asset, XcmError> {
59 tracing::trace!(
60 target: LOG_TARGET,
61 ?what,
62 ?from,
63 ?to,
64 ?context,
65 "transfer_asset",
66 );
67 let (class, instance) = Matcher::matches_nonfungibles(what)?;
69 let destination = AccountIdConverter::convert_location(to)
70 .ok_or(MatchError::AccountIdConversionFailed)?;
71 Assets::transfer(&class, &instance, &destination).map_err(|e| {
72 tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?destination, "Failed to transfer asset");
73 XcmError::FailedToTransactAsset(e.into())
74 })?;
75 Ok(what.clone())
76 }
77}
78
79pub struct NonFungiblesMutateAdapter<
83 Assets,
84 Matcher,
85 AccountIdConverter,
86 AccountId,
87 CheckAsset,
88 CheckingAccount,
89>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
90
91impl<
92 Assets: nonfungibles::Mutate<AccountId>,
93 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
94 AccountIdConverter: ConvertLocation<AccountId>,
95 AccountId: Clone + Eq, CheckAsset: AssetChecking<Assets::CollectionId>,
97 CheckingAccount: Get<Option<AccountId>>,
98 >
99 NonFungiblesMutateAdapter<
100 Assets,
101 Matcher,
102 AccountIdConverter,
103 AccountId,
104 CheckAsset,
105 CheckingAccount,
106 >
107{
108 fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
109 ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable);
110 Ok(())
111 }
112 fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
113 if let Some(checking_account) = CheckingAccount::get() {
114 let owner = Assets::owner(&class, &instance);
116 ensure!(owner == Some(checking_account), XcmError::NotWithdrawable);
117 ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable);
118 }
119 Ok(())
120 }
121 fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
122 if let Some(checking_account) = CheckingAccount::get() {
123 let ok = Assets::mint_into(&class, &instance, &checking_account).is_ok();
124 debug_assert!(ok, "`mint_into` cannot generally fail; qed");
125 }
126 }
127 fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
128 let ok = Assets::burn(&class, &instance, None).is_ok();
129 debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
130 }
131}
132
133impl<
134 Assets: nonfungibles::Mutate<AccountId>,
135 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
136 AccountIdConverter: ConvertLocation<AccountId>,
137 AccountId: Clone + Eq + Debug, CheckAsset: AssetChecking<Assets::CollectionId>,
140 CheckingAccount: Get<Option<AccountId>>,
141 > TransactAsset
142 for NonFungiblesMutateAdapter<
143 Assets,
144 Matcher,
145 AccountIdConverter,
146 AccountId,
147 CheckAsset,
148 CheckingAccount,
149 >
150where
151 Assets::CollectionId: Debug,
152 Assets::ItemId: Debug,
153{
154 fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
155 tracing::trace!(
156 target: LOG_TARGET,
157 ?origin,
158 ?what,
159 ?context,
160 "can_check_in",
161 );
162 let (class, instance) = Matcher::matches_nonfungibles(what)?;
164 match CheckAsset::asset_checking(&class) {
165 Some(MintLocation::Local) => Self::can_reduce_checked(class, instance),
167 Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance),
169 _ => Ok(()),
170 }
171 }
172
173 fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
174 tracing::trace!(
175 target: LOG_TARGET,
176 ?origin,
177 ?what,
178 ?context,
179 "check_in",
180 );
181 if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
182 match CheckAsset::asset_checking(&class) {
183 Some(MintLocation::Local) => Self::reduce_checked(class, instance),
185 Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance),
187 _ => (),
188 }
189 }
190 }
191
192 fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
193 tracing::trace!(
194 target: LOG_TARGET,
195 ?dest,
196 ?what,
197 ?context,
198 "can_check_out",
199 );
200 let (class, instance) = Matcher::matches_nonfungibles(what)?;
202 match CheckAsset::asset_checking(&class) {
203 Some(MintLocation::Local) => Self::can_accrue_checked(class, instance),
205 Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance),
207 _ => Ok(()),
208 }
209 }
210
211 fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
212 tracing::trace!(
213 target: LOG_TARGET,
214 ?dest,
215 ?what,
216 ?context,
217 "check_out",
218 );
219 if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
220 match CheckAsset::asset_checking(&class) {
221 Some(MintLocation::Local) => Self::accrue_checked(class, instance),
223 Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance),
225 _ => (),
226 }
227 }
228 }
229
230 fn deposit_asset(
231 what: AssetsInHolding,
232 who: &Location,
233 context: Option<&XcmContext>,
234 ) -> Result<(), (AssetsInHolding, XcmError)> {
235 tracing::trace!(
236 target: LOG_TARGET,
237 ?what,
238 ?who,
239 ?context,
240 "deposit_asset",
241 );
242 defensive_assert!(what.len() == 1, "Trying to deposit more than one asset!");
243 let maybe = what
245 .non_fungible_assets_iter()
246 .next()
247 .and_then(|asset| Matcher::matches_nonfungibles(&asset).ok());
248 let Some((class, instance)) = maybe else {
249 return Err((what, MatchError::AssetNotHandled.into()));
250 };
251 let Some(who) = AccountIdConverter::convert_location(who) else {
252 return Err((what, MatchError::AccountIdConversionFailed.into()));
253 };
254 Assets::mint_into(&class, &instance, &who).map_err(|e| {
255 tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?who, "Failed to mint asset");
256 (what, XcmError::FailedToTransactAsset(e.into()))
257 })
258 }
259
260 fn withdraw_asset(
261 what: &Asset,
262 who: &Location,
263 maybe_context: Option<&XcmContext>,
264 ) -> Result<AssetsInHolding, XcmError> {
265 tracing::trace!(
266 target: LOG_TARGET,
267 ?what,
268 ?who,
269 ?maybe_context,
270 "withdraw_asset",
271 );
272 let who = AccountIdConverter::convert_location(who)
274 .ok_or(MatchError::AccountIdConversionFailed)?;
275 let asset_instance = match what.fun {
276 NonFungible(instance) => instance,
277 _ => return Err(MatchError::AssetNotHandled.into()),
278 };
279 let (class, instance) = Matcher::matches_nonfungibles(what)?;
280 Assets::burn(&class, &instance, Some(&who)).map_err(|e| {
281 tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?who, "Failed to burn asset");
282 XcmError::FailedToTransactAsset(e.into())
283 })?;
284 Ok(AssetsInHolding::new_from_non_fungible(what.id.clone(), asset_instance))
285 }
286
287 fn mint_asset(what: &Asset, context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
288 tracing::trace!(
289 target: LOG_TARGET,
290 ?what, ?context,
291 "mint_asset",
292 );
293 let asset_instance = match what.fun {
294 NonFungible(instance) => instance,
295 _ => return Err(MatchError::AssetNotHandled.into()),
296 };
297 Matcher::matches_nonfungibles(what)?;
298 Ok(AssetsInHolding::new_from_non_fungible(what.id.clone(), asset_instance))
299 }
300}
301
302pub struct NonFungiblesAdapter<
306 Assets,
307 Matcher,
308 AccountIdConverter,
309 AccountId,
310 CheckAsset,
311 CheckingAccount,
312>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>)
313where
314 Assets: nonfungibles::Transfer<AccountId>,
315 Assets::CollectionId: Debug,
316 Assets::ItemId: Debug;
317impl<
318 Assets: nonfungibles::Mutate<AccountId> + nonfungibles::Transfer<AccountId>,
319 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
320 AccountIdConverter: ConvertLocation<AccountId>,
321 AccountId: Clone + Eq + Debug, CheckAsset: AssetChecking<Assets::CollectionId>,
324 CheckingAccount: Get<Option<AccountId>>,
325 > TransactAsset
326 for NonFungiblesAdapter<
327 Assets,
328 Matcher,
329 AccountIdConverter,
330 AccountId,
331 CheckAsset,
332 CheckingAccount,
333 >
334where
335 Assets::CollectionId: Debug,
336 Assets::ItemId: Debug,
337{
338 fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
339 NonFungiblesMutateAdapter::<
340 Assets,
341 Matcher,
342 AccountIdConverter,
343 AccountId,
344 CheckAsset,
345 CheckingAccount,
346 >::can_check_in(origin, what, context)
347 }
348
349 fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
350 NonFungiblesMutateAdapter::<
351 Assets,
352 Matcher,
353 AccountIdConverter,
354 AccountId,
355 CheckAsset,
356 CheckingAccount,
357 >::check_in(origin, what, context)
358 }
359
360 fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
361 NonFungiblesMutateAdapter::<
362 Assets,
363 Matcher,
364 AccountIdConverter,
365 AccountId,
366 CheckAsset,
367 CheckingAccount,
368 >::can_check_out(dest, what, context)
369 }
370
371 fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
372 NonFungiblesMutateAdapter::<
373 Assets,
374 Matcher,
375 AccountIdConverter,
376 AccountId,
377 CheckAsset,
378 CheckingAccount,
379 >::check_out(dest, what, context)
380 }
381
382 fn deposit_asset(
383 what: AssetsInHolding,
384 who: &Location,
385 context: Option<&XcmContext>,
386 ) -> Result<(), (AssetsInHolding, XcmError)> {
387 NonFungiblesMutateAdapter::<
388 Assets,
389 Matcher,
390 AccountIdConverter,
391 AccountId,
392 CheckAsset,
393 CheckingAccount,
394 >::deposit_asset(what, who, context)
395 }
396
397 fn withdraw_asset(
398 what: &Asset,
399 who: &Location,
400 maybe_context: Option<&XcmContext>,
401 ) -> result::Result<AssetsInHolding, XcmError> {
402 NonFungiblesMutateAdapter::<
403 Assets,
404 Matcher,
405 AccountIdConverter,
406 AccountId,
407 CheckAsset,
408 CheckingAccount,
409 >::withdraw_asset(what, who, maybe_context)
410 }
411
412 fn transfer_asset(
413 what: &Asset,
414 from: &Location,
415 to: &Location,
416 context: &XcmContext,
417 ) -> Result<Asset, XcmError> {
418 NonFungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::transfer_asset(
419 what, from, to, context,
420 )
421 }
422
423 fn mint_asset(what: &Asset, context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
424 NonFungiblesMutateAdapter::<
425 Assets,
426 Matcher,
427 AccountIdConverter,
428 AccountId,
429 CheckAsset,
430 CheckingAccount,
431 >::mint_asset(what, context)
432 }
433}