1use super::MintLocation;
20use alloc::boxed::Box;
21use core::{fmt::Debug, marker::PhantomData, result};
22use frame_support::{
23 defensive_assert,
24 traits::{
25 tokens::{
26 fungible,
27 imbalance::{ImbalanceAccounting, UnsafeManualAccounting},
28 Fortitude::Polite,
29 Precision::Exact,
30 Preservation::Expendable,
31 Provenance::Minted,
32 },
33 Get, Imbalance as ImbalanceT,
34 },
35};
36use xcm::latest::prelude::*;
37use xcm_executor::{
38 traits::{ConvertLocation, Error as MatchError, MatchesFungible, TransactAsset},
39 AssetsInHolding,
40};
41
42pub struct FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>(
46 PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId)>,
47);
48impl<
49 Fungible: fungible::Mutate<AccountId>,
50 Matcher: MatchesFungible<Fungible::Balance>,
51 AccountIdConverter: ConvertLocation<AccountId>,
52 AccountId: Eq + Clone + Debug,
53 > TransactAsset for FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>
54{
55 fn internal_transfer_asset(
56 what: &Asset,
57 from: &Location,
58 to: &Location,
59 _context: &XcmContext,
60 ) -> result::Result<Asset, XcmError> {
61 tracing::trace!(
62 target: "xcm::fungible_adapter",
63 ?what, ?from, ?to,
64 "internal_transfer_asset",
65 );
66 let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
68 let source = AccountIdConverter::convert_location(from)
69 .ok_or(MatchError::AccountIdConversionFailed)?;
70 let dest = AccountIdConverter::convert_location(to)
71 .ok_or(MatchError::AccountIdConversionFailed)?;
72 Fungible::transfer(&source, &dest, amount, Expendable).map_err(|error| {
73 tracing::debug!(
74 target: "xcm::fungible_adapter", ?error, ?source, ?dest, ?amount,
75 "Failed to transfer asset",
76 );
77 XcmError::FailedToTransactAsset(error.into())
78 })?;
79 Ok(what.clone())
80 }
81}
82
83pub struct FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
87 PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
88);
89
90impl<
91 Fungible: fungible::Mutate<AccountId>,
92 Matcher: MatchesFungible<Fungible::Balance>,
93 AccountIdConverter: ConvertLocation<AccountId>,
94 AccountId: Eq + Clone + Debug,
95 CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
96 > FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
97{
98 fn can_accrue_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
99 Fungible::can_deposit(&checking_account, amount, Minted)
100 .into_result()
101 .map_err(|error| {
102 tracing::debug!(
103 target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
104 "Failed to deposit funds into account",
105 );
106 XcmError::NotDepositable
107 })
108 }
109
110 fn can_reduce_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
111 Fungible::can_withdraw(&checking_account, amount)
112 .into_result(false)
113 .map_err(|error| {
114 tracing::debug!(
115 target: "xcm::fungible_adapter", ?error, ?checking_account, ?amount,
116 "Failed to withdraw funds from account",
117 );
118 XcmError::NotWithdrawable
119 })
120 .map(|_| ())
121 }
122
123 fn accrue_checked(checking_account: AccountId, amount: Fungible::Balance) {
124 let ok = Fungible::mint_into(&checking_account, amount).is_ok();
125 debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
126 }
127
128 fn reduce_checked(checking_account: AccountId, amount: Fungible::Balance) {
129 let ok = Fungible::burn_from(&checking_account, amount, Expendable, Exact, Polite).is_ok();
130 debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
131 }
132}
133
134impl<
135 Fungible: fungible::Inspect<AccountId, Balance: 'static>
136 + fungible::Mutate<AccountId>
137 + fungible::Balanced<AccountId, OnDropCredit: 'static, OnDropDebt: 'static>,
138 Matcher: MatchesFungible<Fungible::Balance>,
139 AccountIdConverter: ConvertLocation<AccountId>,
140 AccountId: Eq + Clone + Debug,
141 CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
142 > TransactAsset
143 for FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
144where
145 fungible::Imbalance<
146 <Fungible as fungible::Inspect<AccountId>>::Balance,
147 <Fungible as fungible::Balanced<AccountId>>::OnDropCredit,
148 <Fungible as fungible::Balanced<AccountId>>::OnDropDebt,
149 >: ImbalanceAccounting<u128>,
150{
151 fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
152 tracing::trace!(
153 target: "xcm::fungible_adapter",
154 ?origin, ?what,
155 "can_check_in origin",
156 );
157 let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
159 match CheckingAccount::get() {
160 Some((checking_account, MintLocation::Local)) => {
161 Self::can_reduce_checked(checking_account, amount)
162 },
163 Some((checking_account, MintLocation::NonLocal)) => {
164 Self::can_accrue_checked(checking_account, amount)
165 },
166 None => Ok(()),
167 }
168 }
169
170 fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
171 tracing::trace!(
172 target: "xcm::fungible_adapter",
173 ?origin, ?what,
174 "check_in origin",
175 );
176 if let Some(amount) = Matcher::matches_fungible(what) {
177 match CheckingAccount::get() {
178 Some((checking_account, MintLocation::Local)) => {
179 Self::reduce_checked(checking_account, amount)
180 },
181 Some((checking_account, MintLocation::NonLocal)) => {
182 Self::accrue_checked(checking_account, amount)
183 },
184 None => (),
185 }
186 }
187 }
188
189 fn can_check_out(dest: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
190 tracing::trace!(
191 target: "xcm::fungible_adapter",
192 ?dest,
193 ?what,
194 "can_check_out",
195 );
196 let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
197 match CheckingAccount::get() {
198 Some((checking_account, MintLocation::Local)) => {
199 Self::can_accrue_checked(checking_account, amount)
200 },
201 Some((checking_account, MintLocation::NonLocal)) => {
202 Self::can_reduce_checked(checking_account, amount)
203 },
204 None => Ok(()),
205 }
206 }
207
208 fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
209 tracing::trace!(
210 target: "xcm::fungible_adapter",
211 ?dest,
212 ?what,
213 "check_out",
214 );
215 if let Some(amount) = Matcher::matches_fungible(what) {
216 match CheckingAccount::get() {
217 Some((checking_account, MintLocation::Local)) => {
218 Self::accrue_checked(checking_account, amount)
219 },
220 Some((checking_account, MintLocation::NonLocal)) => {
221 Self::reduce_checked(checking_account, amount)
222 },
223 None => (),
224 }
225 }
226 }
227
228 fn deposit_asset(
229 mut what: AssetsInHolding,
230 who: &Location,
231 _context: Option<&XcmContext>,
232 ) -> Result<(), (AssetsInHolding, XcmError)> {
233 tracing::trace!(
234 target: "xcm::fungible_adapter",
235 ?what, ?who,
236 "deposit_asset",
237 );
238 defensive_assert!(what.len() == 1, "Trying to deposit more than one asset!");
239 let maybe = what
241 .fungible_assets_iter()
242 .next()
243 .and_then(|asset| Matcher::matches_fungible(&asset).map(|amount| (asset.id, amount)));
244 let Some((asset_id, amount)) = maybe else {
245 return Err((what, MatchError::AssetNotHandled.into()));
246 };
247 let Some(who) = AccountIdConverter::convert_location(who) else {
248 return Err((what, MatchError::AccountIdConversionFailed.into()));
249 };
250 let Some(imbalance) = what.fungible.remove(&asset_id) else {
251 return Err((what, MatchError::AssetNotHandled.into()));
252 };
253 let mut credit = fungible::Credit::<AccountId, Fungible>::zero();
255 credit.saturating_subsume(imbalance);
256 Fungible::resolve(&who, credit).map_err(|unspent| {
257 tracing::debug!(target: "xcm::fungible_adapter", ?asset_id, ?who, ?amount, "Failed to deposit asset");
258 (
259 AssetsInHolding::new_from_fungible_credit(asset_id, Box::new(unspent)),
260 XcmError::FailedToTransactAsset("")
261 )
262 })?;
263 Ok(())
264 }
265
266 fn withdraw_asset(
267 what: &Asset,
268 who: &Location,
269 _context: Option<&XcmContext>,
270 ) -> result::Result<AssetsInHolding, XcmError> {
271 tracing::trace!(
272 target: "xcm::fungible_adapter",
273 ?what, ?who,
274 "withdraw_asset",
275 );
276 let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
277 let who = AccountIdConverter::convert_location(who)
278 .ok_or(MatchError::AccountIdConversionFailed)?;
279 let credit = Fungible::withdraw(&who, amount, Exact, Expendable, Polite).map_err(|error| {
280 tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
281 XcmError::FailedToTransactAsset(error.into())
282 })?;
283 Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
284 }
285
286 fn mint_asset(what: &Asset, context: &XcmContext) -> Result<AssetsInHolding, XcmError> {
287 tracing::trace!(
288 target: "xcm::fungible_adapter",
289 ?what, ?context,
290 "mint_asset",
291 );
292 let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
293 let credit = Fungible::issue(amount);
294 Ok(AssetsInHolding::new_from_fungible_credit(what.id.clone(), Box::new(credit)))
295 }
296}
297
298pub struct FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
302 PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
303);
304impl<
305 Fungible: fungible::Inspect<AccountId, Balance: 'static>
306 + fungible::Mutate<AccountId>
307 + fungible::Balanced<AccountId, OnDropCredit: 'static, OnDropDebt: 'static>,
308 Matcher: MatchesFungible<Fungible::Balance>,
309 AccountIdConverter: ConvertLocation<AccountId>,
310 AccountId: Eq + Clone + Debug,
311 CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
312 > TransactAsset
313 for FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
314where
315 fungible::Imbalance<
316 <Fungible as fungible::Inspect<AccountId>>::Balance,
317 <Fungible as fungible::Balanced<AccountId>>::OnDropCredit,
318 <Fungible as fungible::Balanced<AccountId>>::OnDropDebt,
319 >: ImbalanceAccounting<u128>,
320{
321 fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
322 FungibleMutateAdapter::<
323 Fungible,
324 Matcher,
325 AccountIdConverter,
326 AccountId,
327 CheckingAccount,
328 >::can_check_in(origin, what, context)
329 }
330
331 fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
332 FungibleMutateAdapter::<
333 Fungible,
334 Matcher,
335 AccountIdConverter,
336 AccountId,
337 CheckingAccount,
338 >::check_in(origin, what, context)
339 }
340
341 fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
342 FungibleMutateAdapter::<
343 Fungible,
344 Matcher,
345 AccountIdConverter,
346 AccountId,
347 CheckingAccount,
348 >::can_check_out(dest, what, context)
349 }
350
351 fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
352 FungibleMutateAdapter::<
353 Fungible,
354 Matcher,
355 AccountIdConverter,
356 AccountId,
357 CheckingAccount,
358 >::check_out(dest, what, context)
359 }
360
361 fn deposit_asset(
362 what: AssetsInHolding,
363 who: &Location,
364 context: Option<&XcmContext>,
365 ) -> Result<(), (AssetsInHolding, XcmError)> {
366 FungibleMutateAdapter::<
367 Fungible,
368 Matcher,
369 AccountIdConverter,
370 AccountId,
371 CheckingAccount,
372 >::deposit_asset(what, who, context)
373 }
374
375 fn withdraw_asset(
376 what: &Asset,
377 who: &Location,
378 maybe_context: Option<&XcmContext>,
379 ) -> result::Result<AssetsInHolding, XcmError> {
380 FungibleMutateAdapter::<
381 Fungible,
382 Matcher,
383 AccountIdConverter,
384 AccountId,
385 CheckingAccount,
386 >::withdraw_asset(what, who, maybe_context)
387 }
388
389 fn internal_transfer_asset(
390 what: &Asset,
391 from: &Location,
392 to: &Location,
393 context: &XcmContext,
394 ) -> result::Result<Asset, XcmError> {
395 FungibleTransferAdapter::<Fungible, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
396 what, from, to, context
397 )
398 }
399
400 fn mint_asset(what: &Asset, context: &XcmContext) -> result::Result<AssetsInHolding, XcmError> {
401 FungibleMutateAdapter::<
402 Fungible,
403 Matcher,
404 AccountIdConverter,
405 AccountId,
406 CheckingAccount,
407 >::mint_asset(
408 what, context,
409 )
410 }
411}