1use alloc::vec;
20use core::marker::PhantomData;
21use frame_support::traits::{
22 tokens::{Pay, PaymentStatus},
23 Get,
24};
25use sp_runtime::traits::TryConvert;
26use xcm::{opaque::lts::Weight, prelude::*};
27use xcm_executor::traits::{QueryHandler, QueryResponseStatus};
28
29pub struct PayOverXcm<
48 Interior,
49 Router,
50 Querier,
51 Timeout,
52 Beneficiary,
53 AssetKind,
54 AssetKindToLocatableAsset,
55 BeneficiaryRefToLocation,
56>(
57 PhantomData<(
58 Interior,
59 Router,
60 Querier,
61 Timeout,
62 Beneficiary,
63 AssetKind,
64 AssetKindToLocatableAsset,
65 BeneficiaryRefToLocation,
66 )>,
67);
68impl<
69 Interior: Get<InteriorLocation>,
70 Router: SendXcm,
71 Querier: QueryHandler,
72 Timeout: Get<Querier::BlockNumber>,
73 Beneficiary: Clone + core::fmt::Debug,
74 AssetKind: core::fmt::Debug,
75 AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
76 BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
77 > Pay
78 for PayOverXcm<
79 Interior,
80 Router,
81 Querier,
82 Timeout,
83 Beneficiary,
84 AssetKind,
85 AssetKindToLocatableAsset,
86 BeneficiaryRefToLocation,
87 >
88{
89 type Beneficiary = Beneficiary;
90 type AssetKind = AssetKind;
91 type Balance = u128;
92 type Id = QueryId;
93 type Error = xcm::latest::Error;
94
95 fn pay(
96 who: &Self::Beneficiary,
97 asset_kind: Self::AssetKind,
98 amount: Self::Balance,
99 ) -> Result<Self::Id, Self::Error> {
100 let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).map_err(|error| {
101 tracing::debug!(target: "xcm::pay", ?error, "Failed to convert asset kind to locatable asset");
102 xcm::latest::Error::InvalidLocation
103 })?;
104 let LocatableAssetId { asset_id, location: asset_location } = locatable;
105 let destination =
106 Querier::UniversalLocation::get().invert_target(&asset_location).map_err(|()| {
107 tracing::debug!(target: "xcm::pay", "Failed to invert asset location");
108 Self::Error::LocationNotInvertible
109 })?;
110 let beneficiary = BeneficiaryRefToLocation::try_convert(&who).map_err(|error| {
111 tracing::debug!(target: "xcm::pay", ?error, "Failed to convert beneficiary to location");
112 xcm::latest::Error::InvalidLocation
113 })?;
114
115 let query_id = Querier::new_query(asset_location.clone(), Timeout::get(), Interior::get());
116
117 let message = Xcm(vec![
118 DescendOrigin(Interior::get()),
119 UnpaidExecution { weight_limit: Unlimited, check_origin: None },
120 SetAppendix(Xcm(vec![
121 SetFeesMode { jit_withdraw: true },
122 ReportError(QueryResponseInfo {
123 destination,
124 query_id,
125 max_weight: Weight::zero(),
126 }),
127 ])),
128 TransferAsset {
129 beneficiary,
130 assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
131 },
132 ]);
133
134 let (ticket, _) = Router::validate(&mut Some(asset_location), &mut Some(message))?;
135 Router::deliver(ticket)?;
136 Ok(query_id.into())
137 }
138
139 fn check_payment(id: Self::Id) -> PaymentStatus {
140 use QueryResponseStatus::*;
141 match Querier::take_response(id) {
142 Ready { response, .. } => match response {
143 Response::ExecutionResult(None) => PaymentStatus::Success,
144 Response::ExecutionResult(Some(_)) => PaymentStatus::Failure,
145 _ => PaymentStatus::Unknown,
146 },
147 Pending { .. } => PaymentStatus::InProgress,
148 NotFound | UnexpectedVersion => PaymentStatus::Unknown,
149 }
150 }
151
152 #[cfg(feature = "runtime-benchmarks")]
153 fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) {
154 let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap();
155 Router::ensure_successful_delivery(Some(locatable.location));
156 }
157
158 #[cfg(feature = "runtime-benchmarks")]
159 fn ensure_concluded(id: Self::Id) {
160 Querier::expect_response(id, Response::ExecutionResult(None));
161 }
162}
163
164pub type PayAccountId32OnChainOverXcm<
179 DestinationChain,
180 Interior,
181 Router,
182 Querier,
183 Timeout,
184 Beneficiary,
185 AssetKind,
186> = PayOverXcm<
187 Interior,
188 Router,
189 Querier,
190 Timeout,
191 Beneficiary,
192 AssetKind,
193 crate::AliasesIntoAccountId32<(), Beneficiary>,
194 FixedLocation<DestinationChain>,
195>;
196
197pub struct LocatableAssetId {
200 pub asset_id: AssetId,
202 pub location: Location,
204}
205
206pub struct FixedLocation<FixedLocationValue>(core::marker::PhantomData<FixedLocationValue>);
209impl<FixedLocationValue: Get<Location>, AssetKind: Into<AssetId>>
210 TryConvert<AssetKind, LocatableAssetId> for FixedLocation<FixedLocationValue>
211{
212 fn try_convert(value: AssetKind) -> Result<LocatableAssetId, AssetKind> {
213 Ok(LocatableAssetId { asset_id: value.into(), location: FixedLocationValue::get() })
214 }
215}