1use crate::AccountId;
34
35use frame_support::{
36 ensure,
37 traits::{tokens::fungibles, Contains, ContainsPair, Get, ProcessMessageError},
38 weights::constants::WEIGHT_REF_TIME_PER_SECOND,
39};
40use sp_runtime::traits::{Bounded, Convert, MaybeEquivalence, Zero};
41use sp_std::marker::PhantomData;
42
43use xcm::latest::{prelude::*, Weight};
45use xcm_builder::{CreateMatcher, MatchXcm, TakeRevenue};
46use xcm_executor::traits::{MatchesFungibles, Properties, ShouldExecute, WeightTrader};
47
48use orml_traits::location::{RelativeReserveProvider, Reserve};
50
51use pallet_xc_asset_config::{ExecutionPaymentRate, XcAssetLocation};
52
53#[cfg(test)]
54mod tests;
55
56pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16);
57pub const MAX_ASSETS: u32 = 64;
58pub const ASSET_HUB_PARA_ID: u32 = 1000;
59
60pub struct AssetLocationIdConverter<AssetId, AssetMapper>(PhantomData<(AssetId, AssetMapper)>);
65impl<AssetId, AssetMapper> MaybeEquivalence<Location, AssetId>
66 for AssetLocationIdConverter<AssetId, AssetMapper>
67where
68 AssetId: Clone + Eq + Bounded,
69 AssetMapper: XcAssetLocation<AssetId>,
70{
71 fn convert(location: &Location) -> Option<AssetId> {
72 AssetMapper::get_asset_id(location.clone())
73 }
74
75 fn convert_back(id: &AssetId) -> Option<Location> {
76 AssetMapper::get_xc_asset_location(id.clone())
77 }
78}
79
80pub struct FixedRateOfForeignAsset<T: ExecutionPaymentRate, R: TakeRevenue> {
85 weight: Weight,
87 consumed: u128,
89 asset_location_and_units_per_second: Option<(Location, u128)>,
91 _pd: PhantomData<(T, R)>,
92}
93
94impl<T: ExecutionPaymentRate, R: TakeRevenue> WeightTrader for FixedRateOfForeignAsset<T, R> {
95 fn new() -> Self {
96 Self {
97 weight: Weight::zero(),
98 consumed: 0,
99 asset_location_and_units_per_second: None,
100 _pd: PhantomData,
101 }
102 }
103
104 fn buy_weight(
105 &mut self,
106 weight: Weight,
107 payment: xcm_executor::AssetsInHolding,
108 _: &XcmContext,
109 ) -> Result<xcm_executor::AssetsInHolding, XcmError> {
110 log::trace!(
111 target: "xcm::weight",
112 "FixedRateOfForeignAsset::buy_weight weight: {:?}, payment: {:?}",
113 weight, payment,
114 );
115
116 let payment_asset = payment
118 .fungible_assets_iter()
119 .next()
120 .ok_or(XcmError::TooExpensive)?;
121
122 match payment_asset {
123 Asset {
124 id: AssetId(asset_location),
125 fun: Fungibility::Fungible(_),
126 } => {
127 if let Some(units_per_second) = T::get_units_per_second(asset_location.clone()) {
128 let amount = units_per_second.saturating_mul(weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128);
130 if amount == 0 {
131 return Ok(payment);
132 }
133
134 let unused = payment
135 .checked_sub((asset_location.clone(), amount).into())
136 .map_err(|_| XcmError::TooExpensive)?;
137
138 self.weight = self.weight.saturating_add(weight);
139
140 if let Some((old_asset_location, _)) =
144 self.asset_location_and_units_per_second.clone()
145 {
146 if old_asset_location == asset_location {
147 self.consumed = self.consumed.saturating_add(amount);
148 }
149 } else {
150 self.consumed = self.consumed.saturating_add(amount);
151 self.asset_location_and_units_per_second =
152 Some((asset_location, units_per_second));
153 }
154
155 Ok(unused)
156 } else {
157 Err(XcmError::TooExpensive)
158 }
159 }
160 _ => Err(XcmError::TooExpensive),
161 }
162 }
163
164 fn refund_weight(&mut self, weight: Weight, _: &XcmContext) -> Option<Asset> {
165 log::trace!(target: "xcm::weight", "FixedRateOfForeignAsset::refund_weight weight: {:?}", weight);
166
167 if let Some((asset_location, units_per_second)) =
168 self.asset_location_and_units_per_second.clone()
169 {
170 let weight = weight.min(self.weight);
171 let amount = units_per_second.saturating_mul(weight.ref_time() as u128)
172 / (WEIGHT_REF_TIME_PER_SECOND as u128);
173
174 self.weight = self.weight.saturating_sub(weight);
175 self.consumed = self.consumed.saturating_sub(amount);
176
177 if amount > 0 {
178 Some((asset_location, amount).into())
179 } else {
180 None
181 }
182 } else {
183 None
184 }
185 }
186}
187
188impl<T: ExecutionPaymentRate, R: TakeRevenue> Drop for FixedRateOfForeignAsset<T, R> {
189 fn drop(&mut self) {
190 if let Some((asset_location, _)) = self.asset_location_and_units_per_second.clone() {
191 if self.consumed > 0 {
192 R::take_revenue((asset_location, self.consumed).into());
193 }
194 }
195 }
196}
197
198pub struct ReserveAssetFilter;
204impl ContainsPair<Asset, Location> for ReserveAssetFilter {
205 fn contains(asset: &Asset, origin: &Location) -> bool {
206 let AssetId(location) = &asset.id;
207 match (location.parents, location.first_interior()) {
208 (1, Some(Parachain(id))) => origin == &Location::new(1, [Parachain(*id)]),
210 (1, None) => origin == &Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]),
212 _ => false,
213 }
214 }
215}
216
217pub struct XcmFungibleFeeHandler<AccountId, Matcher, Assets, FeeDestination>(
223 sp_std::marker::PhantomData<(AccountId, Matcher, Assets, FeeDestination)>,
224);
225impl<
226 AccountId: Eq,
227 Assets: fungibles::Mutate<AccountId>,
228 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
229 FeeDestination: Get<AccountId>,
230 > TakeRevenue for XcmFungibleFeeHandler<AccountId, Matcher, Assets, FeeDestination>
231{
232 fn take_revenue(revenue: Asset) {
233 match Matcher::matches_fungibles(&revenue) {
234 Ok((asset_id, amount)) => {
235 if amount > Zero::zero() {
236 if let Err(error) =
237 Assets::mint_into(asset_id.clone(), &FeeDestination::get(), amount)
238 {
239 log::error!(
240 target: "xcm::weight",
241 "XcmFeeHandler::take_revenue failed when minting asset: {:?}", error,
242 );
243 } else {
244 log::trace!(
245 target: "xcm::weight",
246 "XcmFeeHandler::take_revenue took {:?} of asset Id {:?}",
247 amount, asset_id,
248 );
249 }
250 }
251 }
252 Err(_) => {
253 log::error!(
254 target: "xcm::weight",
255 "XcmFeeHandler:take_revenue failed to match fungible asset, it has been burned."
256 );
257 }
258 }
259 }
260}
261
262pub struct AccountIdToMultiLocation;
264impl Convert<AccountId, Location> for AccountIdToMultiLocation {
265 fn convert(account: AccountId) -> Location {
266 AccountId32 {
267 network: None,
268 id: account.into(),
269 }
270 .into()
271 }
272}
273
274pub struct AbsoluteAndRelativeReserveProvider<AbsoluteLocation>(PhantomData<AbsoluteLocation>);
277impl<AbsoluteLocation: Get<Location>> Reserve
278 for AbsoluteAndRelativeReserveProvider<AbsoluteLocation>
279{
280 fn reserve(asset: &Asset) -> Option<Location> {
281 let reserve_location = RelativeReserveProvider::reserve(asset)?;
282
283 if reserve_location == AbsoluteLocation::get() {
284 return Some(Location::here());
285 }
286
287 let is_relay_token = reserve_location.contains_parents_only(1);
288 if is_relay_token {
289 return Some(Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]));
290 }
291
292 Some(reserve_location)
293 }
294}
295
296const MAX_ASSETS_FOR_BUY_EXECUTION: usize = 2;
301
302pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
308impl<T: Contains<Location>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
309 fn should_execute<RuntimeCall>(
310 origin: &Location,
311 instructions: &mut [Instruction<RuntimeCall>],
312 max_weight: Weight,
313 _properties: &mut Properties,
314 ) -> Result<(), ProcessMessageError> {
315 log::trace!(
316 target: "xcm::barriers",
317 "AllowTopLevelPaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}",
318 origin, instructions, max_weight, _properties,
319 );
320
321 ensure!(T::contains(origin), ProcessMessageError::Unsupported);
322 let end = instructions.len().min(5);
326 instructions[..end]
327 .matcher()
328 .match_next_inst(|inst| match inst {
329 ReceiveTeleportedAsset(..) | ReserveAssetDeposited(..) => Ok(()),
330 WithdrawAsset(ref assets) if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION => Ok(()),
331 ClaimAsset { ref assets, .. } if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION => {
332 Ok(())
333 }
334 _ => Err(ProcessMessageError::BadFormat),
335 })?
336 .skip_inst_while(|inst| matches!(inst, ClearOrigin))?
337 .match_next_inst(|inst| match inst {
338 BuyExecution {
339 weight_limit: Limited(ref mut weight),
340 ..
341 } if weight.all_gte(max_weight) => {
342 *weight = max_weight;
343 Ok(())
344 }
345 BuyExecution {
346 ref mut weight_limit,
347 ..
348 } if weight_limit == &Unlimited => {
349 *weight_limit = Limited(max_weight);
350 Ok(())
351 }
352 _ => Err(ProcessMessageError::Overweight(max_weight)),
353 })?;
354 Ok(())
355 }
356}