1use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
18use frame_support::traits::{
19 fungibles,
20 tokens::{PayWithSource, PaymentStatus, Preservation},
21};
22use polkadot_runtime_common::impls::VersionedLocatableAsset;
23use sp_runtime::{traits::TypedGet, DispatchError};
24use xcm::latest::prelude::*;
25use xcm_executor::traits::ConvertLocation;
26
27#[derive(
30 Encode,
31 Decode,
32 Eq,
33 PartialEq,
34 Clone,
35 Debug,
36 scale_info::TypeInfo,
37 MaxEncodedLen,
38 DecodeWithMemTracking,
39)]
40pub enum VersionedLocatableAccount {
41 #[codec(index = 4)]
42 V4 { location: xcm::v4::Location, account_id: xcm::v4::Location },
43 #[codec(index = 5)]
44 V5 { location: xcm::v5::Location, account_id: xcm::v5::Location },
45}
46
47pub struct AccountIdToLocalLocation;
64
65impl sp_runtime::traits::Convert<sp_runtime::AccountId32, VersionedLocatableAccount>
66 for AccountIdToLocalLocation
67{
68 fn convert(account_id: sp_runtime::AccountId32) -> VersionedLocatableAccount {
77 VersionedLocatableAccount::V5 {
78 location: Location::here(),
79 account_id: Location::new(
80 0,
81 [xcm::v5::Junction::AccountId32 { network: None, id: account_id.into() }],
82 ),
83 }
84 }
85}
86
87pub struct LocalPay<F, A, C>(core::marker::PhantomData<(F, A, C)>);
90impl<A, F, C> frame_support::traits::tokens::Pay for LocalPay<F, A, C>
91where
92 A: TypedGet,
93 F: fungibles::Mutate<A::Type, AssetId = xcm::v5::Location> + fungibles::Create<A::Type>,
94 C: ConvertLocation<A::Type>,
95 A::Type: Eq + Clone,
96{
97 type Balance = F::Balance;
98 type Beneficiary = VersionedLocatableAccount;
99 type AssetKind = VersionedLocatableAsset;
100 type Id = QueryId;
101 type Error = DispatchError;
102 fn pay(
103 who: &Self::Beneficiary,
104 asset: Self::AssetKind,
105 amount: Self::Balance,
106 ) -> Result<Self::Id, Self::Error> {
107 let who = Self::match_location::<A::Type>(who).map_err(|_| DispatchError::Unavailable)?;
108 let asset = Self::match_asset(&asset).map_err(|_| DispatchError::Unavailable)?;
109 <F as fungibles::Mutate<_>>::transfer(
110 asset,
111 &A::get(),
112 &who,
113 amount,
114 Preservation::Expendable,
115 )?;
116 Ok(Self::Id::MAX) }
122 fn check_payment(_: Self::Id) -> PaymentStatus {
123 PaymentStatus::Success
124 }
125 #[cfg(feature = "runtime-benchmarks")]
126 fn ensure_successful(_: &Self::Beneficiary, asset: Self::AssetKind, amount: Self::Balance) {
127 let asset = Self::match_asset(&asset).expect("invalid asset");
128 <F as fungibles::Create<_>>::create(asset.clone(), A::get(), true, amount).unwrap();
129 <F as fungibles::Mutate<_>>::mint_into(asset, &A::get(), amount).unwrap();
130 }
131 #[cfg(feature = "runtime-benchmarks")]
132 fn ensure_concluded(_: Self::Id) {}
133}
134
135impl<A, F, C> LocalPay<F, A, C> {
136 fn match_location<T>(who: &VersionedLocatableAccount) -> Result<T, ()>
137 where
138 T: Eq + Clone,
139 C: ConvertLocation<T>,
140 {
141 let account_id = match who {
143 VersionedLocatableAccount::V4 { location, account_id } if location.is_here() => {
144 &account_id.clone().try_into().map_err(|_| ())?
145 },
146 VersionedLocatableAccount::V5 { location, account_id } if location.is_here() => {
147 account_id
148 },
149 _ => return Err(()),
150 };
151 C::convert_location(account_id).ok_or(())
152 }
153
154 fn match_asset(asset: &VersionedLocatableAsset) -> Result<xcm::v5::Location, ()> {
155 match asset {
156 VersionedLocatableAsset::V3 { location, asset_id } if location.is_here() => {
157 let v4_asset_id: xcm::v4::AssetId = (*asset_id).try_into().map_err(|_| ())?;
159 let v5_asset_id: xcm::v5::AssetId = v4_asset_id.try_into().map_err(|_| ())?;
160 Ok(v5_asset_id.0)
161 },
162 VersionedLocatableAsset::V4 { location, asset_id } if location.is_here() => {
163 asset_id.clone().try_into().map(|a: xcm::v5::AssetId| a.0).map_err(|_| ())
164 },
165 VersionedLocatableAsset::V5 { location, asset_id } if location.is_here() => {
166 Ok(asset_id.clone().0)
167 },
168 _ => Err(()),
169 }
170 }
171}
172
173impl<A, F, C> PayWithSource for LocalPay<F, A, C>
175where
176 A: Eq + Clone,
177 F: fungibles::Mutate<A, AssetId = xcm::v5::Location> + fungibles::Create<A>,
178 C: ConvertLocation<A>,
179{
180 type Balance = F::Balance;
181 type Source = VersionedLocatableAccount;
182 type Beneficiary = VersionedLocatableAccount;
183 type AssetKind = VersionedLocatableAsset;
184 type Id = QueryId;
185 type Error = DispatchError;
186 fn pay(
187 source: &Self::Source,
188 who: &Self::Beneficiary,
189 asset: Self::AssetKind,
190 amount: Self::Balance,
191 ) -> Result<Self::Id, Self::Error> {
192 let source = Self::match_location::<A>(source).map_err(|_| DispatchError::Unavailable)?;
193 let who = Self::match_location::<A>(who).map_err(|_| DispatchError::Unavailable)?;
194 let asset = Self::match_asset(&asset).map_err(|_| DispatchError::Unavailable)?;
195 <F as fungibles::Mutate<_>>::transfer(
196 asset,
197 &source,
198 &who,
199 amount,
200 Preservation::Expendable,
201 )?;
202 Ok(Self::Id::MAX)
206 }
207 fn check_payment(_: Self::Id) -> PaymentStatus {
208 PaymentStatus::Success
209 }
210 #[cfg(feature = "runtime-benchmarks")]
211 fn ensure_successful(
212 source: &Self::Source,
213 _: &Self::Beneficiary,
214 asset: Self::AssetKind,
215 amount: Self::Balance,
216 ) {
217 use sp_runtime::traits::Zero;
218
219 let source = Self::match_location::<A>(source).expect("invalid source");
220 let asset = Self::match_asset(&asset).expect("invalid asset");
221 if F::total_issuance(asset.clone()).is_zero() {
222 <F as fungibles::Create<_>>::create(asset.clone(), source.clone(), true, 1u32.into())
223 .unwrap();
224 }
225 <F as fungibles::Mutate<_>>::mint_into(asset, &source, amount).unwrap();
226 }
227 #[cfg(feature = "runtime-benchmarks")]
228 fn ensure_concluded(_: Self::Id) {}
229}
230
231#[cfg(feature = "runtime-benchmarks")]
232pub mod benchmarks {
233 use super::*;
234 use core::marker::PhantomData;
235 use frame_support::traits::Get;
236 use pallet_multi_asset_bounties::ArgumentsFactory as MultiAssetBountiesArgumentsFactory;
237 use pallet_treasury::ArgumentsFactory as TreasuryArgumentsFactory;
238 use sp_core::ConstU8;
239
240 pub struct LocalPayArguments<PalletId = ConstU8<0>>(PhantomData<PalletId>);
246 impl<PalletId: Get<u8>>
247 TreasuryArgumentsFactory<VersionedLocatableAsset, VersionedLocatableAccount>
248 for LocalPayArguments<PalletId>
249 {
250 fn create_asset_kind(seed: u32) -> VersionedLocatableAsset {
251 VersionedLocatableAsset::V5 {
252 location: Location::new(0, []),
253 asset_id: Location::new(
254 0,
255 [PalletInstance(PalletId::get()), GeneralIndex(seed.into())],
256 )
257 .into(),
258 }
259 }
260 fn create_beneficiary(seed: [u8; 32]) -> VersionedLocatableAccount {
261 VersionedLocatableAccount::V5 {
262 location: Location::new(0, []),
263 account_id: Location::new(0, [AccountId32 { network: None, id: seed }]),
264 }
265 }
266 }
267
268 pub struct LocalPayWithSourceArguments<PalletId = ConstU8<0>>(PhantomData<PalletId>);
274 impl<PalletId: Get<u8>, Balance>
275 MultiAssetBountiesArgumentsFactory<
276 VersionedLocatableAsset,
277 VersionedLocatableAccount,
278 Balance,
279 > for LocalPayWithSourceArguments<PalletId>
280 {
281 fn create_asset_kind(seed: u32) -> VersionedLocatableAsset {
282 VersionedLocatableAsset::V5 {
283 location: Location::new(0, []),
284 asset_id: Location::new(
285 0,
286 [PalletInstance(PalletId::get()), GeneralIndex(seed.into())],
287 )
288 .into(),
289 }
290 }
291 fn create_beneficiary(seed: [u8; 32]) -> VersionedLocatableAccount {
292 VersionedLocatableAccount::V5 {
293 location: Location::new(0, []),
294 account_id: Location::new(0, [AccountId32 { network: None, id: seed }]),
295 }
296 }
297 }
298}