1use crate::LocatableAssetId;
20use alloc::vec;
21use core::{fmt::Debug, marker::PhantomData};
22use frame_support::traits::Get;
23use sp_runtime::traits::TryConvert;
24use xcm::{latest::Error, opaque::lts::Weight, prelude::*};
25use xcm_executor::traits::{FeeManager, FeeReason, QueryHandler, QueryResponseStatus};
26
27pub use frame_support::traits::tokens::transfer::{Transfer, TransferStatus};
28
29const LOG_TARGET: &str = "xcm::transfer_over_xcm";
30
31pub struct TransferOverXcm<TransactorRefToLocation, TransferOverXcmHelper>(
50 PhantomData<(TransactorRefToLocation, TransferOverXcmHelper)>,
51);
52
53impl<TransactorRefToLocation, TransferOverXcmHelper> Transfer
54 for TransferOverXcm<TransactorRefToLocation, TransferOverXcmHelper>
55where
56 TransferOverXcmHelper: TransferOverXcmHelperT<Balance = u128, QueryId = QueryId>,
57 TransactorRefToLocation: for<'a> TryConvert<&'a TransferOverXcmHelper::Beneficiary, Location>,
58{
59 type Balance = u128;
60 type Sender = TransferOverXcmHelper::Beneficiary;
61 type Beneficiary = TransferOverXcmHelper::Beneficiary;
62 type AssetKind = TransferOverXcmHelper::AssetKind;
63 type RemoteFeeAsset = Asset;
64
65 type Id = TransferOverXcmHelper::QueryId;
66 type Error = Error;
67
68 fn transfer(
69 from: &Self::Sender,
70 to: &Self::Beneficiary,
71 asset_kind: Self::AssetKind,
72 amount: Self::Balance,
73 remote_fee: Option<Self::RemoteFeeAsset>,
74 ) -> Result<Self::Id, Self::Error> {
75 let from_location = TransactorRefToLocation::try_convert(from).map_err(|error| {
76 tracing::error!(target: LOG_TARGET, ?error, "Failed to convert `Sender` to location");
77 Error::InvalidLocation
78 })?;
79
80 TransferOverXcmHelper::send_remote_transfer_xcm(
81 from_location.clone(),
82 to,
83 asset_kind,
84 amount,
85 remote_fee,
86 )
87 }
88
89 fn check_transfer(id: Self::Id) -> TransferStatus {
90 TransferOverXcmHelper::check_transfer(id)
91 }
92
93 #[cfg(feature = "runtime-benchmarks")]
94 fn ensure_successful(
95 beneficiary: &Self::Beneficiary,
96 asset_kind: Self::AssetKind,
97 balance: Self::Balance,
98 ) {
99 TransferOverXcmHelper::ensure_successful(beneficiary, asset_kind, balance);
100 }
101
102 #[cfg(feature = "runtime-benchmarks")]
103 fn ensure_concluded(id: Self::Id) {
104 TransferOverXcmHelper::ensure_concluded(id);
105 }
106}
107
108pub struct TransferOverXcmHelper<
110 Router,
111 Querier,
112 XcmFeeHandler,
113 Timeout,
114 Transactor,
115 AssetKind,
116 AssetKindToLocatableAsset,
117 BeneficiaryRefToLocation,
118>(
119 PhantomData<(
120 Router,
121 Querier,
122 XcmFeeHandler,
123 Timeout,
124 Transactor,
125 AssetKind,
126 AssetKindToLocatableAsset,
127 BeneficiaryRefToLocation,
128 )>,
129);
130
131pub trait TransferOverXcmHelperT {
133 type Beneficiary: Debug + Clone;
135 type AssetKind: Debug + Clone;
137 type Balance: Debug + Clone;
139 type QueryId: Debug + Clone;
141 fn send_remote_transfer_xcm(
143 from_location: Location,
144 to: &Self::Beneficiary,
145 asset_kind: Self::AssetKind,
146 amount: Self::Balance,
147 remote_fee: Option<Asset>,
148 ) -> Result<QueryId, Error>;
149
150 fn check_transfer(id: Self::QueryId) -> TransferStatus;
151 #[cfg(feature = "runtime-benchmarks")]
154 fn ensure_successful(
155 beneficiary: &Self::Beneficiary,
156 asset_kind: Self::AssetKind,
157 balance: Self::Balance,
158 );
159
160 #[cfg(feature = "runtime-benchmarks")]
161 fn ensure_concluded(id: Self::QueryId);
162}
163
164impl<
165 Router: SendXcm,
166 Querier: QueryHandler,
167 XcmFeeHandler: FeeManager,
168 Timeout: Get<Querier::BlockNumber>,
169 Beneficiary: Clone + Debug,
170 AssetKind: Clone + Debug,
171 AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
172 BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
173 > TransferOverXcmHelperT
174 for TransferOverXcmHelper<
175 Router,
176 Querier,
177 XcmFeeHandler,
178 Timeout,
179 Beneficiary,
180 AssetKind,
181 AssetKindToLocatableAsset,
182 BeneficiaryRefToLocation,
183 >
184{
185 type Beneficiary = Beneficiary;
186 type AssetKind = AssetKind;
187 type Balance = u128;
188 type QueryId = QueryId;
189
190 fn send_remote_transfer_xcm(
192 from_location: Location,
193 to: &Beneficiary,
194 asset_kind: AssetKind,
195 amount: Self::Balance,
196 remote_fee: Option<Asset>,
197 ) -> Result<QueryId, Error> {
198 let locatable = Self::locatable_asset_id(asset_kind)?;
199 let LocatableAssetId { asset_id, location: asset_location } = locatable;
200
201 let origin_location_on_remote = Self::origin_location_on_remote(&asset_location)?;
202
203 let beneficiary = BeneficiaryRefToLocation::try_convert(to).map_err(|error| {
204 tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert beneficiary to location");
205 Error::InvalidLocation
206 })?;
207
208 let query_id = Querier::new_query(
209 asset_location.clone(),
210 Timeout::get(),
211 from_location.interior.clone(),
212 );
213
214 let message = match remote_fee {
215 None => remote_transfer_xcm_free_execution(
216 from_location.clone(),
217 origin_location_on_remote,
218 beneficiary,
219 asset_id,
220 amount,
221 query_id,
222 )?,
223 Some(fee_asset) => remote_transfer_xcm_paying_fees(
224 from_location.clone(),
225 origin_location_on_remote,
226 beneficiary,
227 asset_id,
228 amount,
229 fee_asset,
230 query_id,
231 )?,
232 };
233
234 let (ticket, delivery_fees) =
235 Router::validate(&mut Some(asset_location), &mut Some(message))?;
236 Router::deliver(ticket)?;
237
238 if !XcmFeeHandler::is_waived(Some(&from_location), FeeReason::ChargeFees) {
239 XcmFeeHandler::handle_fee(delivery_fees, None, FeeReason::ChargeFees)
240 }
241
242 Ok(query_id)
243 }
244
245 fn check_transfer(id: Self::QueryId) -> TransferStatus {
246 use QueryResponseStatus::*;
247 match Querier::take_response(id) {
248 Ready { response, .. } => match response {
249 Response::ExecutionResult(None) => TransferStatus::Success,
250 Response::ExecutionResult(Some(_)) => TransferStatus::Failure,
251 _ => TransferStatus::Unknown,
252 },
253 Pending { .. } => TransferStatus::InProgress,
254 NotFound | UnexpectedVersion => TransferStatus::Unknown,
255 }
256 }
257
258 #[cfg(feature = "runtime-benchmarks")]
259 fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) {
260 let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap();
261 Router::ensure_successful_delivery(Some(locatable.location));
262 }
263 #[cfg(feature = "runtime-benchmarks")]
266 fn ensure_concluded(id: Self::QueryId) {
267 Querier::expect_response(id, Response::ExecutionResult(None));
268 }
269}
270
271impl<
272 Router,
273 Querier: QueryHandler,
274 XcmFeeHandler,
275 Timeout,
276 Beneficiary: Clone + Debug,
277 AssetKind: Clone + Debug,
278 AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
279 BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
280 >
281 TransferOverXcmHelper<
282 Router,
283 Querier,
284 XcmFeeHandler,
285 Timeout,
286 Beneficiary,
287 AssetKind,
288 AssetKindToLocatableAsset,
289 BeneficiaryRefToLocation,
290 >
291{
292 pub fn from_relative_to_asset_location(
296 from: &Beneficiary,
297 asset_kind: AssetKind,
298 ) -> Result<Location, Error> {
299 let from_location = BeneficiaryRefToLocation::try_convert(from).map_err(|error| {
300 tracing::error!(target: LOG_TARGET, ?error, "Failed to convert from to location");
301 Error::InvalidLocation
302 })?;
303
304 let locatable = Self::locatable_asset_id(asset_kind)?;
305
306 let origin_location_on_remote = Self::origin_location_on_remote(&locatable.location)?;
307 append_from_to_target(from_location, origin_location_on_remote)
308 }
309
310 fn origin_location_on_remote(asset_location: &Location) -> Result<Location, Error> {
311 let origin_on_remote =
312 Querier::UniversalLocation::get().invert_target(asset_location).map_err(|()| {
313 tracing::debug!(target: LOG_TARGET, "Failed to invert asset location");
314 Error::LocationNotInvertible
315 })?;
316 tracing::trace!(target: LOG_TARGET, ?origin_on_remote, "Origin on destination");
317 Ok(origin_on_remote)
318 }
319
320 fn locatable_asset_id(asset_kind: AssetKind) -> Result<LocatableAssetId, Error> {
321 AssetKindToLocatableAsset::try_convert(asset_kind).map_err(|error| {
322 tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert asset kind to locatable asset");
323 Error::InvalidLocation
324 })
325 }
326}
327
328fn remote_transfer_xcm_paying_fees(
329 from_location: Location,
330 origin_relative_to_remote: Location,
331 beneficiary: Location,
332 asset_id: AssetId,
333 amount: u128,
334 remote_fee: Asset,
335 query_id: QueryId,
336) -> Result<Xcm<()>, Error> {
337 let from_at_target =
340 append_from_to_target(from_location.clone(), origin_relative_to_remote.clone())?;
341 tracing::trace!(target: LOG_TARGET, ?from_at_target, "From at target");
342
343 let xcm = Xcm(vec![
344 DescendOrigin(from_location.interior.clone()),
346 WithdrawAsset(vec![remote_fee.clone()].into()),
347 PayFees { asset: remote_fee },
348 SetAppendix(Xcm(vec![
349 ReportError(QueryResponseInfo {
350 destination: origin_relative_to_remote.clone(),
351 query_id,
352 max_weight: Weight::MAX,
353 }),
354 RefundSurplus,
355 DepositAsset { assets: AssetFilter::Wild(WildAsset::All), beneficiary: from_at_target },
356 ])),
357 TransferAsset { beneficiary, assets: (asset_id, amount).into() },
358 ]);
359
360 Ok(xcm)
361}
362
363fn remote_transfer_xcm_free_execution(
364 from_location: Location,
365 origin_relative_to_remote: Location,
366 beneficiary: Location,
367 asset_id: AssetId,
368 amount: u128,
369 query_id: QueryId,
370) -> Result<Xcm<()>, Error> {
371 let xcm = Xcm(vec![
372 DescendOrigin(from_location.interior),
373 UnpaidExecution { weight_limit: Unlimited, check_origin: None },
374 SetAppendix(Xcm(vec![
375 SetFeesMode { jit_withdraw: true },
376 ReportError(QueryResponseInfo {
377 destination: origin_relative_to_remote,
378 query_id,
379 max_weight: Weight::zero(),
380 }),
381 ])),
382 TransferAsset {
383 beneficiary,
384 assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
385 },
386 ]);
387
388 Ok(xcm)
389}
390
391fn append_from_to_target(from: Location, target: Location) -> Result<Location, Error> {
392 let from_at_target = target.appended_with(from).map_err(|_| Error::LocationFull)?;
393 Ok(from_at_target)
394}