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::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 = {
284 let AssetId(location) = &asset.id;
285 match (location.parents, location.first_interior()) {
286 (1, Some(Parachain(id))) => Some(Location::new(1, [Parachain(*id)])),
287 (1, _) => Some(Location::parent()),
288 (0, Some(Parachain(id))) => Some(Location::new(0, [Parachain(*id)])),
289 (0, _) => Some(Location::here()), _ => None,
291 }
292 }?;
293
294 if reserve_location == AbsoluteLocation::get() {
295 return Some(Location::here());
296 }
297
298 let is_relay_token = reserve_location.contains_parents_only(1);
299 if is_relay_token {
300 return Some(Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]));
301 }
302
303 Some(reserve_location)
304 }
305}
306
307const MAX_ASSETS_FOR_BUY_EXECUTION: usize = 2;
312
313pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
319impl<T: Contains<Location>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
320 fn should_execute<RuntimeCall>(
321 origin: &Location,
322 instructions: &mut [Instruction<RuntimeCall>],
323 max_weight: Weight,
324 _properties: &mut Properties,
325 ) -> Result<(), ProcessMessageError> {
326 log::trace!(
327 target: "xcm::barriers",
328 "AllowTopLevelPaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}",
329 origin, instructions, max_weight, _properties,
330 );
331
332 ensure!(T::contains(origin), ProcessMessageError::Unsupported);
333 let end = instructions.len().min(5);
337 instructions[..end]
338 .matcher()
339 .match_next_inst(|inst| match inst {
340 ReceiveTeleportedAsset(..) | ReserveAssetDeposited(..) => Ok(()),
341 WithdrawAsset(ref assets) if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION => Ok(()),
342 ClaimAsset { ref assets, .. } if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION => {
343 Ok(())
344 }
345 _ => Err(ProcessMessageError::BadFormat),
346 })?
347 .skip_inst_while(|inst| matches!(inst, ClearOrigin))?
348 .match_next_inst(|inst| match inst {
349 BuyExecution {
350 weight_limit: Limited(ref mut weight),
351 ..
352 } if weight.all_gte(max_weight) => {
353 *weight = max_weight;
354 Ok(())
355 }
356 BuyExecution {
357 ref mut weight_limit,
358 ..
359 } if weight_limit == &Unlimited => {
360 *weight_limit = Limited(max_weight);
361 Ok(())
362 }
363 _ => Err(ProcessMessageError::Overweight(max_weight)),
364 })?;
365 Ok(())
366 }
367}