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