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::{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 XcmConfig,
111 Querier,
112 Timeout,
113 Beneficiary,
114 AssetKind,
115 AssetKindToLocatableAsset,
116 BeneficiaryRefToLocation,
117>(
118 PhantomData<(
119 XcmConfig,
120 Querier,
121 Timeout,
122 Beneficiary,
123 AssetKind,
124 AssetKindToLocatableAsset,
125 BeneficiaryRefToLocation,
126 )>,
127);
128
129pub trait TransferOverXcmHelperT {
131 type Beneficiary: Debug + Clone;
133 type AssetKind: Debug + Clone;
135 type Balance: Debug + Clone;
137 type QueryId: Debug + Clone;
139 fn send_remote_transfer_xcm(
141 from_location: Location,
142 to: &Self::Beneficiary,
143 asset_kind: Self::AssetKind,
144 amount: Self::Balance,
145 remote_fee: Option<Asset>,
146 ) -> Result<QueryId, Error>;
147
148 fn check_transfer(id: Self::QueryId) -> TransferStatus;
149 #[cfg(feature = "runtime-benchmarks")]
152 fn ensure_successful(
153 beneficiary: &Self::Beneficiary,
154 asset_kind: Self::AssetKind,
155 balance: Self::Balance,
156 );
157
158 #[cfg(feature = "runtime-benchmarks")]
159 fn ensure_concluded(id: Self::QueryId);
160}
161
162impl<
163 XcmConfig: xcm_executor::Config,
164 Querier: QueryHandler,
165 Timeout: Get<Querier::BlockNumber>,
166 Beneficiary: Clone + Debug,
167 AssetKind: Clone + Debug,
168 AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
169 BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
170 > TransferOverXcmHelperT
171 for TransferOverXcmHelper<
172 XcmConfig,
173 Querier,
174 Timeout,
175 Beneficiary,
176 AssetKind,
177 AssetKindToLocatableAsset,
178 BeneficiaryRefToLocation,
179 >
180{
181 type Beneficiary = Beneficiary;
182 type AssetKind = AssetKind;
183 type Balance = u128;
184 type QueryId = QueryId;
185
186 fn send_remote_transfer_xcm(
188 from_location: Location,
189 to: &Beneficiary,
190 asset_kind: AssetKind,
191 amount: Self::Balance,
192 remote_fee: Option<Asset>,
193 ) -> Result<QueryId, Error> {
194 let locatable = Self::locatable_asset_id(asset_kind)?;
195 let LocatableAssetId { asset_id, location: asset_location } = locatable;
196 tracing::trace!(
197 target: LOG_TARGET,
198 ?from_location, ?asset_id, ?asset_location, ?amount, ?remote_fee, "send_remote_transfer_xcm"
199 );
200
201 let origin_location_on_remote = Self::origin_location_on_remote(&asset_location)?;
202 let beneficiary = BeneficiaryRefToLocation::try_convert(to).map_err(|error| {
203 tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert beneficiary to location");
204 Error::InvalidLocation
205 })?;
206 let query_id = Querier::new_query(
207 asset_location.clone(),
208 Timeout::get(),
209 from_location.interior.clone(),
210 );
211 let message = match remote_fee {
212 None => remote_transfer_xcm_free_execution(
213 from_location.clone(),
214 origin_location_on_remote,
215 beneficiary,
216 asset_id,
217 amount,
218 query_id,
219 )?,
220 Some(fee_asset) => remote_transfer_xcm_paying_fees(
221 from_location.clone(),
222 origin_location_on_remote,
223 beneficiary,
224 asset_id,
225 amount,
226 fee_asset,
227 query_id,
228 )?,
229 };
230
231 let (ticket, delivery_fees) =
232 XcmConfig::XcmSender::validate(&mut Some(asset_location), &mut Some(message))?;
233 xcm_executor::XcmExecutor::<XcmConfig>::charge_fees(from_location, delivery_fees)?;
234 XcmConfig::XcmSender::deliver(ticket)?;
235
236 Ok(query_id)
237 }
238
239 fn check_transfer(id: Self::QueryId) -> TransferStatus {
240 use QueryResponseStatus::*;
241 match Querier::take_response(id) {
242 Ready { response, .. } => match response {
243 Response::ExecutionResult(None) => TransferStatus::Success,
244 Response::ExecutionResult(Some(_)) => TransferStatus::Failure,
245 _ => TransferStatus::Unknown,
246 },
247 Pending { .. } => TransferStatus::InProgress,
248 NotFound | UnexpectedVersion => TransferStatus::Unknown,
249 }
250 }
251
252 #[cfg(feature = "runtime-benchmarks")]
253 fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) {
254 let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap();
255 XcmConfig::XcmSender::ensure_successful_delivery(Some(locatable.location));
256 }
257 #[cfg(feature = "runtime-benchmarks")]
260 fn ensure_concluded(id: Self::QueryId) {
261 Querier::expect_response(id, Response::ExecutionResult(None));
262 }
263}
264
265impl<
266 XcmConfig,
267 Querier: QueryHandler,
268 Timeout,
269 Beneficiary: Clone + Debug,
270 AssetKind: Clone + Debug,
271 AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
272 BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
273 >
274 TransferOverXcmHelper<
275 XcmConfig,
276 Querier,
277 Timeout,
278 Beneficiary,
279 AssetKind,
280 AssetKindToLocatableAsset,
281 BeneficiaryRefToLocation,
282 >
283{
284 pub fn from_relative_to_asset_location(
288 from: &Beneficiary,
289 asset_kind: AssetKind,
290 ) -> Result<Location, Error> {
291 let from_location = BeneficiaryRefToLocation::try_convert(from).map_err(|error| {
292 tracing::error!(target: LOG_TARGET, ?error, "Failed to convert from to location");
293 Error::InvalidLocation
294 })?;
295
296 let locatable = Self::locatable_asset_id(asset_kind)?;
297
298 let origin_location_on_remote = Self::origin_location_on_remote(&locatable.location)?;
299 append_from_to_target(from_location, origin_location_on_remote)
300 }
301
302 fn origin_location_on_remote(asset_location: &Location) -> Result<Location, Error> {
303 let origin_on_remote =
304 Querier::UniversalLocation::get().invert_target(asset_location).map_err(|()| {
305 tracing::debug!(target: LOG_TARGET, "Failed to invert asset location");
306 Error::LocationNotInvertible
307 })?;
308 tracing::trace!(target: LOG_TARGET, ?origin_on_remote, "Origin on destination");
309 Ok(origin_on_remote)
310 }
311
312 fn locatable_asset_id(asset_kind: AssetKind) -> Result<LocatableAssetId, Error> {
313 AssetKindToLocatableAsset::try_convert(asset_kind).map_err(|error| {
314 tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert asset kind to locatable asset");
315 Error::InvalidLocation
316 })
317 }
318}
319
320fn remote_transfer_xcm_paying_fees(
321 from_location: Location,
322 origin_relative_to_remote: Location,
323 beneficiary: Location,
324 asset_id: AssetId,
325 amount: u128,
326 remote_fee: Asset,
327 _: QueryId,
328) -> Result<Xcm<()>, Error> {
329 let from_at_target =
332 append_from_to_target(from_location.clone(), origin_relative_to_remote.clone())?;
333 tracing::trace!(target: LOG_TARGET, ?from_at_target, "From at target");
334
335 let xcm = Xcm(vec![
336 DescendOrigin(from_location.interior.clone()),
338 WithdrawAsset(vec![remote_fee.clone()].into()),
339 PayFees { asset: remote_fee },
340 SetAppendix(Xcm(vec![
341 RefundSurplus,
343 DepositAsset { assets: AssetFilter::Wild(WildAsset::All), beneficiary: from_at_target },
344 ])),
345 TransferAsset { beneficiary, assets: (asset_id, amount).into() },
346 ]);
347
348 Ok(xcm)
349}
350
351fn remote_transfer_xcm_free_execution(
352 from_location: Location,
353 origin_relative_to_remote: Location,
354 beneficiary: Location,
355 asset_id: AssetId,
356 amount: u128,
357 query_id: QueryId,
358) -> Result<Xcm<()>, Error> {
359 let xcm = Xcm(vec![
360 DescendOrigin(from_location.interior),
361 UnpaidExecution { weight_limit: Unlimited, check_origin: None },
362 SetAppendix(Xcm(vec![
363 SetFeesMode { jit_withdraw: true },
364 ReportError(QueryResponseInfo {
365 destination: origin_relative_to_remote,
366 query_id,
367 max_weight: Weight::zero(),
368 }),
369 ])),
370 TransferAsset {
371 beneficiary,
372 assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
373 },
374 ]);
375
376 Ok(xcm)
377}
378
379fn append_from_to_target(from: Location, target: Location) -> Result<Location, Error> {
380 let from_at_target = target.appended_with(from).map_err(|_| Error::LocationFull)?;
381 Ok(from_at_target)
382}