1#![allow(deprecated)]
20
21use super::MintLocation;
22use core::{fmt::Debug, marker::PhantomData, result};
23use frame_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons};
24use sp_runtime::traits::CheckedSub;
25use xcm::latest::{Asset, Error as XcmError, Location, Result, XcmContext};
26use xcm_executor::{
27 traits::{ConvertLocation, MatchesFungible, TransactAsset},
28 AssetsInHolding,
29};
30
31enum Error {
33 AssetNotHandled,
35 AccountIdConversionFailed,
37}
38
39impl From<Error> for XcmError {
40 fn from(e: Error) -> Self {
41 use XcmError::FailedToTransactAsset;
42 match e {
43 Error::AssetNotHandled => XcmError::AssetNotFound,
44 Error::AccountIdConversionFailed => FailedToTransactAsset("AccountIdConversionFailed"),
45 }
46 }
47}
48
49#[deprecated = "Use `FungibleAdapter` instead"]
91pub struct CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>(
92 PhantomData<(Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount)>,
93);
94
95impl<
96 Currency: frame_support::traits::Currency<AccountId>,
97 Matcher: MatchesFungible<Currency::Balance>,
98 AccountIdConverter: ConvertLocation<AccountId>,
99 AccountId: Clone, CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
101 > CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
102{
103 fn can_accrue_checked(_checked_account: AccountId, _amount: Currency::Balance) -> Result {
104 Ok(())
105 }
106 fn can_reduce_checked(checked_account: AccountId, amount: Currency::Balance) -> Result {
107 let new_balance = Currency::free_balance(&checked_account)
108 .checked_sub(&amount)
109 .ok_or(XcmError::NotWithdrawable)?;
110 Currency::ensure_can_withdraw(
111 &checked_account,
112 amount,
113 WithdrawReasons::TRANSFER,
114 new_balance,
115 )
116 .map_err(|error| {
117 tracing::debug!(target: "xcm::currency_adapter", ?error, "Failed to ensure can withdraw");
118 XcmError::NotWithdrawable
119 })
120 }
121 fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) {
122 let _ = Currency::deposit_creating(&checked_account, amount);
123 Currency::deactivate(amount);
124 }
125 fn reduce_checked(checked_account: AccountId, amount: Currency::Balance) {
126 let ok =
127 Currency::withdraw(&checked_account, amount, WithdrawReasons::TRANSFER, AllowDeath)
128 .is_ok();
129 if ok {
130 Currency::reactivate(amount);
131 } else {
132 frame_support::defensive!(
133 "`can_check_in` must have returned `true` immediately prior; qed"
134 );
135 }
136 }
137}
138
139impl<
140 Currency: frame_support::traits::Currency<AccountId>,
141 Matcher: MatchesFungible<Currency::Balance>,
142 AccountIdConverter: ConvertLocation<AccountId>,
143 AccountId: Clone + Debug, CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
145 > TransactAsset
146 for CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
147{
148 fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> Result {
149 tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "can_check_in origin");
150 let amount: Currency::Balance =
152 Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
153 match CheckedAccount::get() {
154 Some((checked_account, MintLocation::Local)) =>
155 Self::can_reduce_checked(checked_account, amount),
156 Some((checked_account, MintLocation::NonLocal)) =>
157 Self::can_accrue_checked(checked_account, amount),
158 None => Ok(()),
159 }
160 }
161
162 fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
163 tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "check_in origin");
164 if let Some(amount) = Matcher::matches_fungible(what) {
165 match CheckedAccount::get() {
166 Some((checked_account, MintLocation::Local)) =>
167 Self::reduce_checked(checked_account, amount),
168 Some((checked_account, MintLocation::NonLocal)) =>
169 Self::accrue_checked(checked_account, amount),
170 None => (),
171 }
172 }
173 }
174
175 fn can_check_out(dest: &Location, what: &Asset, _context: &XcmContext) -> Result {
176 tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "can_check_out");
177 let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
178 match CheckedAccount::get() {
179 Some((checked_account, MintLocation::Local)) =>
180 Self::can_accrue_checked(checked_account, amount),
181 Some((checked_account, MintLocation::NonLocal)) =>
182 Self::can_reduce_checked(checked_account, amount),
183 None => Ok(()),
184 }
185 }
186
187 fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
188 tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "check_out");
189 if let Some(amount) = Matcher::matches_fungible(what) {
190 match CheckedAccount::get() {
191 Some((checked_account, MintLocation::Local)) =>
192 Self::accrue_checked(checked_account, amount),
193 Some((checked_account, MintLocation::NonLocal)) =>
194 Self::reduce_checked(checked_account, amount),
195 None => (),
196 }
197 }
198 }
199
200 fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> Result {
201 tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "deposit_asset");
202 let amount = Matcher::matches_fungible(&what).ok_or(Error::AssetNotHandled)?;
204 let who =
205 AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?;
206 let _imbalance = Currency::deposit_creating(&who, amount);
207 Ok(())
208 }
209
210 fn withdraw_asset(
211 what: &Asset,
212 who: &Location,
213 _maybe_context: Option<&XcmContext>,
214 ) -> result::Result<AssetsInHolding, XcmError> {
215 tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "withdraw_asset");
216 let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
218 let who =
219 AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?;
220 let _ = Currency::withdraw(&who, amount, WithdrawReasons::TRANSFER, AllowDeath).map_err(
221 |error| {
222 tracing::debug!(target: "xcm::currency_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
223 XcmError::FailedToTransactAsset(error.into())
224 },
225 )?;
226 Ok(what.clone().into())
227 }
228
229 fn internal_transfer_asset(
230 asset: &Asset,
231 from: &Location,
232 to: &Location,
233 _context: &XcmContext,
234 ) -> result::Result<AssetsInHolding, XcmError> {
235 tracing::trace!(target: "xcm::currency_adapter", ?asset, ?from, ?to, "internal_transfer_asset");
236 let amount = Matcher::matches_fungible(asset).ok_or(Error::AssetNotHandled)?;
237 let from =
238 AccountIdConverter::convert_location(from).ok_or(Error::AccountIdConversionFailed)?;
239 let to =
240 AccountIdConverter::convert_location(to).ok_or(Error::AccountIdConversionFailed)?;
241 Currency::transfer(&from, &to, amount, AllowDeath).map_err(|error| {
242 tracing::debug!(target: "xcm::currency_adapter", ?error, ?from, ?to, ?amount, "Failed to transfer asset");
243 XcmError::FailedToTransactAsset(error.into())
244 })?;
245 Ok(asset.clone().into())
246 }
247}