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