1use crate::{AssetChecking, MintLocation};
20use core::{fmt::Debug, marker::PhantomData, result};
21use frame_support::{
22 ensure,
23 traits::{tokens::nonfungibles, Get},
24};
25use xcm::latest::prelude::*;
26use xcm_executor::traits::{
27 ConvertLocation, Error as MatchError, MatchesNonFungibles, TransactAsset,
28};
29
30const LOG_TARGET: &str = "xcm::nonfungibles_adapter";
31
32pub struct NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
36 PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
37)
38where
39 Assets: nonfungibles::Transfer<AccountId>,
40 Assets::CollectionId: Debug,
41 Assets::ItemId: Debug;
42impl<
43 Assets: nonfungibles::Transfer<AccountId>,
44 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
45 AccountIdConverter: ConvertLocation<AccountId>,
46 AccountId: Clone + Debug, > TransactAsset for NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
48where
49 Assets::CollectionId: Debug,
50 Assets::ItemId: Debug,
51{
52 fn transfer_asset(
53 what: &Asset,
54 from: &Location,
55 to: &Location,
56 context: &XcmContext,
57 ) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
58 tracing::trace!(
59 target: LOG_TARGET,
60 ?what,
61 ?from,
62 ?to,
63 ?context,
64 "transfer_asset",
65 );
66 let (class, instance) = Matcher::matches_nonfungibles(what)?;
68 let destination = AccountIdConverter::convert_location(to)
69 .ok_or(MatchError::AccountIdConversionFailed)?;
70 Assets::transfer(&class, &instance, &destination).map_err(|e| {
71 tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?destination, "Failed to transfer asset");
72 XcmError::FailedToTransactAsset(e.into())
73 })?;
74 Ok(what.clone().into())
75 }
76}
77
78pub struct NonFungiblesMutateAdapter<
82 Assets,
83 Matcher,
84 AccountIdConverter,
85 AccountId,
86 CheckAsset,
87 CheckingAccount,
88>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
89
90impl<
91 Assets: nonfungibles::Mutate<AccountId>,
92 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
93 AccountIdConverter: ConvertLocation<AccountId>,
94 AccountId: Clone + Eq, CheckAsset: AssetChecking<Assets::CollectionId>,
96 CheckingAccount: Get<Option<AccountId>>,
97 >
98 NonFungiblesMutateAdapter<
99 Assets,
100 Matcher,
101 AccountIdConverter,
102 AccountId,
103 CheckAsset,
104 CheckingAccount,
105 >
106{
107 fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
108 ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable);
109 Ok(())
110 }
111 fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
112 if let Some(checking_account) = CheckingAccount::get() {
113 let owner = Assets::owner(&class, &instance);
115 ensure!(owner == Some(checking_account), XcmError::NotWithdrawable);
116 ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable);
117 }
118 Ok(())
119 }
120 fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
121 if let Some(checking_account) = CheckingAccount::get() {
122 let ok = Assets::mint_into(&class, &instance, &checking_account).is_ok();
123 debug_assert!(ok, "`mint_into` cannot generally fail; qed");
124 }
125 }
126 fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
127 let ok = Assets::burn(&class, &instance, None).is_ok();
128 debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
129 }
130}
131
132impl<
133 Assets: nonfungibles::Mutate<AccountId>,
134 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
135 AccountIdConverter: ConvertLocation<AccountId>,
136 AccountId: Clone + Eq + Debug, CheckAsset: AssetChecking<Assets::CollectionId>,
139 CheckingAccount: Get<Option<AccountId>>,
140 > TransactAsset
141 for NonFungiblesMutateAdapter<
142 Assets,
143 Matcher,
144 AccountIdConverter,
145 AccountId,
146 CheckAsset,
147 CheckingAccount,
148 >
149where
150 Assets::CollectionId: Debug,
151 Assets::ItemId: Debug,
152{
153 fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
154 tracing::trace!(
155 target: LOG_TARGET,
156 ?origin,
157 ?what,
158 ?context,
159 "can_check_in",
160 );
161 let (class, instance) = Matcher::matches_nonfungibles(what)?;
163 match CheckAsset::asset_checking(&class) {
164 Some(MintLocation::Local) => Self::can_reduce_checked(class, instance),
166 Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance),
168 _ => Ok(()),
169 }
170 }
171
172 fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
173 tracing::trace!(
174 target: LOG_TARGET,
175 ?origin,
176 ?what,
177 ?context,
178 "check_in",
179 );
180 if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
181 match CheckAsset::asset_checking(&class) {
182 Some(MintLocation::Local) => Self::reduce_checked(class, instance),
184 Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance),
186 _ => (),
187 }
188 }
189 }
190
191 fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
192 tracing::trace!(
193 target: LOG_TARGET,
194 ?dest,
195 ?what,
196 ?context,
197 "can_check_out",
198 );
199 let (class, instance) = Matcher::matches_nonfungibles(what)?;
201 match CheckAsset::asset_checking(&class) {
202 Some(MintLocation::Local) => Self::can_accrue_checked(class, instance),
204 Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance),
206 _ => Ok(()),
207 }
208 }
209
210 fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
211 tracing::trace!(
212 target: LOG_TARGET,
213 ?dest,
214 ?what,
215 ?context,
216 "check_out",
217 );
218 if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
219 match CheckAsset::asset_checking(&class) {
220 Some(MintLocation::Local) => Self::accrue_checked(class, instance),
222 Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance),
224 _ => (),
225 }
226 }
227 }
228
229 fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
230 tracing::trace!(
231 target: LOG_TARGET,
232 ?what,
233 ?who,
234 ?context,
235 "deposit_asset",
236 );
237 let (class, instance) = Matcher::matches_nonfungibles(what)?;
239 let who = AccountIdConverter::convert_location(who)
240 .ok_or(MatchError::AccountIdConversionFailed)?;
241 Assets::mint_into(&class, &instance, &who).map_err(|e| {
242 tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?who, "Failed to mint asset");
243 XcmError::FailedToTransactAsset(e.into())
244 })
245 }
246
247 fn withdraw_asset(
248 what: &Asset,
249 who: &Location,
250 maybe_context: Option<&XcmContext>,
251 ) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
252 tracing::trace!(
253 target: LOG_TARGET,
254 ?what,
255 ?who,
256 ?maybe_context,
257 "withdraw_asset",
258 );
259 let who = AccountIdConverter::convert_location(who)
261 .ok_or(MatchError::AccountIdConversionFailed)?;
262 let (class, instance) = Matcher::matches_nonfungibles(what)?;
263 Assets::burn(&class, &instance, Some(&who)).map_err(|e| {
264 tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?who, "Failed to burn asset");
265 XcmError::FailedToTransactAsset(e.into())
266 })?;
267 Ok(what.clone().into())
268 }
269}
270
271pub struct NonFungiblesAdapter<
275 Assets,
276 Matcher,
277 AccountIdConverter,
278 AccountId,
279 CheckAsset,
280 CheckingAccount,
281>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>)
282where
283 Assets: nonfungibles::Transfer<AccountId>,
284 Assets::CollectionId: Debug,
285 Assets::ItemId: Debug;
286impl<
287 Assets: nonfungibles::Mutate<AccountId> + nonfungibles::Transfer<AccountId>,
288 Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
289 AccountIdConverter: ConvertLocation<AccountId>,
290 AccountId: Clone + Eq + Debug, CheckAsset: AssetChecking<Assets::CollectionId>,
293 CheckingAccount: Get<Option<AccountId>>,
294 > TransactAsset
295 for NonFungiblesAdapter<
296 Assets,
297 Matcher,
298 AccountIdConverter,
299 AccountId,
300 CheckAsset,
301 CheckingAccount,
302 >
303where
304 Assets::CollectionId: Debug,
305 Assets::ItemId: Debug,
306{
307 fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
308 NonFungiblesMutateAdapter::<
309 Assets,
310 Matcher,
311 AccountIdConverter,
312 AccountId,
313 CheckAsset,
314 CheckingAccount,
315 >::can_check_in(origin, what, context)
316 }
317
318 fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
319 NonFungiblesMutateAdapter::<
320 Assets,
321 Matcher,
322 AccountIdConverter,
323 AccountId,
324 CheckAsset,
325 CheckingAccount,
326 >::check_in(origin, what, context)
327 }
328
329 fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
330 NonFungiblesMutateAdapter::<
331 Assets,
332 Matcher,
333 AccountIdConverter,
334 AccountId,
335 CheckAsset,
336 CheckingAccount,
337 >::can_check_out(dest, what, context)
338 }
339
340 fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
341 NonFungiblesMutateAdapter::<
342 Assets,
343 Matcher,
344 AccountIdConverter,
345 AccountId,
346 CheckAsset,
347 CheckingAccount,
348 >::check_out(dest, what, context)
349 }
350
351 fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
352 NonFungiblesMutateAdapter::<
353 Assets,
354 Matcher,
355 AccountIdConverter,
356 AccountId,
357 CheckAsset,
358 CheckingAccount,
359 >::deposit_asset(what, who, context)
360 }
361
362 fn withdraw_asset(
363 what: &Asset,
364 who: &Location,
365 maybe_context: Option<&XcmContext>,
366 ) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
367 NonFungiblesMutateAdapter::<
368 Assets,
369 Matcher,
370 AccountIdConverter,
371 AccountId,
372 CheckAsset,
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::Result<xcm_executor::AssetsInHolding, XcmError> {
383 NonFungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::transfer_asset(
384 what, from, to, context,
385 )
386 }
387}