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 VersionedLocatableAccount::V5 { location, account_id } if location.is_here() =>
146 account_id,
147 _ => return Err(()),
148 };
149 C::convert_location(account_id).ok_or(())
150 }
151
152 fn match_asset(asset: &VersionedLocatableAsset) -> Result<xcm::v5::Location, ()> {
153 match asset {
154 VersionedLocatableAsset::V4 { location, asset_id } if location.is_here() =>
155 asset_id.clone().try_into().map(|a: xcm::v5::AssetId| a.0).map_err(|_| ()),
156 VersionedLocatableAsset::V5 { location, asset_id } if location.is_here() =>
157 Ok(asset_id.clone().0),
158 _ => Err(()),
159 }
160 }
161}
162
163impl<A, F, C> PayWithSource for LocalPay<F, A, C>
165where
166 A: Eq + Clone,
167 F: fungibles::Mutate<A, AssetId = xcm::v5::Location> + fungibles::Create<A>,
168 C: ConvertLocation<A>,
169{
170 type Balance = F::Balance;
171 type Source = VersionedLocatableAccount;
172 type Beneficiary = VersionedLocatableAccount;
173 type AssetKind = VersionedLocatableAsset;
174 type Id = QueryId;
175 type Error = DispatchError;
176 fn pay(
177 source: &Self::Source,
178 who: &Self::Beneficiary,
179 asset: Self::AssetKind,
180 amount: Self::Balance,
181 ) -> Result<Self::Id, Self::Error> {
182 let source = Self::match_location::<A>(source).map_err(|_| DispatchError::Unavailable)?;
183 let who = Self::match_location::<A>(who).map_err(|_| DispatchError::Unavailable)?;
184 let asset = Self::match_asset(&asset).map_err(|_| DispatchError::Unavailable)?;
185 <F as fungibles::Mutate<_>>::transfer(
186 asset,
187 &source,
188 &who,
189 amount,
190 Preservation::Expendable,
191 )?;
192 Ok(Self::Id::MAX)
196 }
197 fn check_payment(_: Self::Id) -> PaymentStatus {
198 PaymentStatus::Success
199 }
200 #[cfg(feature = "runtime-benchmarks")]
201 fn ensure_successful(
202 source: &Self::Source,
203 _: &Self::Beneficiary,
204 asset: Self::AssetKind,
205 amount: Self::Balance,
206 ) {
207 use sp_runtime::traits::Zero;
208
209 let source = Self::match_location::<A>(source).expect("invalid source");
210 let asset = Self::match_asset(&asset).expect("invalid asset");
211 if F::total_issuance(asset.clone()).is_zero() {
212 <F as fungibles::Create<_>>::create(asset.clone(), source.clone(), true, 1u32.into())
213 .unwrap();
214 }
215 <F as fungibles::Mutate<_>>::mint_into(asset, &source, amount).unwrap();
216 }
217 #[cfg(feature = "runtime-benchmarks")]
218 fn ensure_concluded(_: Self::Id) {}
219}
220
221#[cfg(feature = "runtime-benchmarks")]
222pub mod benchmarks {
223 use super::*;
224 use core::marker::PhantomData;
225 use frame_support::traits::Get;
226 use pallet_multi_asset_bounties::ArgumentsFactory as MultiAssetBountiesArgumentsFactory;
227 use pallet_treasury::ArgumentsFactory as TreasuryArgumentsFactory;
228 use sp_core::ConstU8;
229
230 pub struct LocalPayArguments<PalletId = ConstU8<0>>(PhantomData<PalletId>);
236 impl<PalletId: Get<u8>>
237 TreasuryArgumentsFactory<VersionedLocatableAsset, VersionedLocatableAccount>
238 for LocalPayArguments<PalletId>
239 {
240 fn create_asset_kind(seed: u32) -> VersionedLocatableAsset {
241 VersionedLocatableAsset::V5 {
242 location: Location::new(0, []),
243 asset_id: Location::new(
244 0,
245 [PalletInstance(PalletId::get()), GeneralIndex(seed.into())],
246 )
247 .into(),
248 }
249 }
250 fn create_beneficiary(seed: [u8; 32]) -> VersionedLocatableAccount {
251 VersionedLocatableAccount::V5 {
252 location: Location::new(0, []),
253 account_id: Location::new(0, [AccountId32 { network: None, id: seed }]),
254 }
255 }
256 }
257
258 pub struct LocalPayWithSourceArguments<PalletId = ConstU8<0>>(PhantomData<PalletId>);
264 impl<PalletId: Get<u8>, Balance>
265 MultiAssetBountiesArgumentsFactory<
266 VersionedLocatableAsset,
267 VersionedLocatableAccount,
268 Balance,
269 > for LocalPayWithSourceArguments<PalletId>
270 {
271 fn create_asset_kind(seed: u32) -> VersionedLocatableAsset {
272 VersionedLocatableAsset::V5 {
273 location: Location::new(0, []),
274 asset_id: Location::new(
275 0,
276 [PalletInstance(PalletId::get()), GeneralIndex(seed.into())],
277 )
278 .into(),
279 }
280 }
281 fn create_beneficiary(seed: [u8; 32]) -> VersionedLocatableAccount {
282 VersionedLocatableAccount::V5 {
283 location: Location::new(0, []),
284 account_id: Location::new(0, [AccountId32 { network: None, id: seed }]),
285 }
286 }
287 }
288}