1use frame_support::{
37 traits::{tokens::fungibles, ContainsPair, Get},
38 weights::constants::WEIGHT_REF_TIME_PER_SECOND,
39};
40use sp_runtime::traits::{Bounded, MaybeEquivalence, Zero};
41use sp_std::marker::PhantomData;
42
43use xcm::latest::{prelude::*, Weight};
45use xcm_builder::TakeRevenue;
46use xcm_executor::traits::{MatchesFungibles, TransferType, WeightTrader, XcmAssetTransfers};
47
48use pallet_xc_asset_config::{ExecutionPaymentRate, XcAssetLocation};
49
50#[cfg(test)]
51mod tests;
52
53pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16);
54pub const MAX_ASSETS: u32 = 64;
55pub const ASSET_HUB_PARA_ID: u32 = 1000;
56
57pub struct AssetLocationIdConverter<AssetId, AssetMapper>(PhantomData<(AssetId, AssetMapper)>);
62impl<AssetId, AssetMapper> MaybeEquivalence<Location, AssetId>
63 for AssetLocationIdConverter<AssetId, AssetMapper>
64where
65 AssetId: Clone + Eq + Bounded,
66 AssetMapper: XcAssetLocation<AssetId>,
67{
68 fn convert(location: &Location) -> Option<AssetId> {
69 AssetMapper::get_asset_id(location.clone())
70 }
71
72 fn convert_back(id: &AssetId) -> Option<Location> {
73 AssetMapper::get_xc_asset_location(id.clone())
74 }
75}
76
77pub struct FixedRateOfForeignAsset<T: ExecutionPaymentRate, R: TakeRevenue> {
82 weight: Weight,
84 consumed: u128,
86 asset_location_and_units_per_second: Option<(Location, u128)>,
88 _pd: PhantomData<(T, R)>,
89}
90
91impl<T: ExecutionPaymentRate, R: TakeRevenue> WeightTrader for FixedRateOfForeignAsset<T, R> {
92 fn new() -> Self {
93 Self {
94 weight: Weight::zero(),
95 consumed: 0,
96 asset_location_and_units_per_second: None,
97 _pd: PhantomData,
98 }
99 }
100
101 fn buy_weight(
102 &mut self,
103 weight: Weight,
104 payment: xcm_executor::AssetsInHolding,
105 _: &XcmContext,
106 ) -> Result<xcm_executor::AssetsInHolding, XcmError> {
107 log::trace!(
108 target: "xcm::weight",
109 "FixedRateOfForeignAsset::buy_weight weight: {:?}, payment: {:?}",
110 weight, payment,
111 );
112
113 let payment_asset = payment
115 .fungible_assets_iter()
116 .next()
117 .ok_or(XcmError::TooExpensive)?;
118
119 match payment_asset {
120 Asset {
121 id: AssetId(asset_location),
122 fun: Fungibility::Fungible(_),
123 } => {
124 if let Some(units_per_second) = T::get_units_per_second(asset_location.clone()) {
125 let amount = units_per_second.saturating_mul(weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128);
127 if amount == 0 {
128 return Ok(payment);
129 }
130
131 if let Some((tracked_asset_location, _)) =
133 &self.asset_location_and_units_per_second
134 {
135 if *tracked_asset_location != asset_location {
136 return Err(XcmError::NotWithdrawable);
137 }
138 }
139
140 let unused = payment
141 .checked_sub((asset_location.clone(), amount).into())
142 .map_err(|_| XcmError::TooExpensive)?;
143
144 self.weight = self.weight.saturating_add(weight);
145 self.consumed = self.consumed.saturating_add(amount);
146 self.asset_location_and_units_per_second =
147 Some((asset_location, units_per_second));
148
149 Ok(unused)
150 } else {
151 Err(XcmError::TooExpensive)
152 }
153 }
154 _ => Err(XcmError::TooExpensive),
155 }
156 }
157
158 fn refund_weight(&mut self, weight: Weight, _: &XcmContext) -> Option<Asset> {
159 log::trace!(target: "xcm::weight", "FixedRateOfForeignAsset::refund_weight weight: {:?}", weight);
160
161 if let Some((asset_location, units_per_second)) =
162 self.asset_location_and_units_per_second.clone()
163 {
164 let weight = weight.min(self.weight);
165 let amount = units_per_second
167 .saturating_mul(weight.ref_time() as u128)
168 .saturating_div(WEIGHT_REF_TIME_PER_SECOND as u128)
169 .min(self.consumed);
170
171 self.weight = self.weight.saturating_sub(weight);
172 self.consumed = self.consumed.saturating_sub(amount);
173
174 if amount > 0 {
175 Some((asset_location, amount).into())
176 } else {
177 None
178 }
179 } else {
180 None
181 }
182 }
183}
184
185impl<T: ExecutionPaymentRate, R: TakeRevenue> Drop for FixedRateOfForeignAsset<T, R> {
186 fn drop(&mut self) {
187 if let Some((asset_location, _)) = self.asset_location_and_units_per_second.clone() {
188 if self.consumed > 0 {
189 R::take_revenue((asset_location, self.consumed).into());
190 }
191 }
192 }
193}
194
195pub struct ReserveAssetFilter;
201impl ContainsPair<Asset, Location> for ReserveAssetFilter {
202 fn contains(asset: &Asset, origin: &Location) -> bool {
203 let AssetId(location) = &asset.id;
204 match (location.parents, location.first_interior()) {
205 (1, Some(Parachain(id))) => origin == &Location::new(1, [Parachain(*id)]),
207 (1, None) => origin == &Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]),
209 _ => false,
210 }
211 }
212}
213
214pub struct XcmFungibleFeeHandler<AccountId, Matcher, Assets, FeeDestination>(
220 sp_std::marker::PhantomData<(AccountId, Matcher, Assets, FeeDestination)>,
221);
222impl<
223 AccountId: Eq,
224 Assets: fungibles::Mutate<AccountId>,
225 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
226 FeeDestination: Get<AccountId>,
227 > TakeRevenue for XcmFungibleFeeHandler<AccountId, Matcher, Assets, FeeDestination>
228{
229 fn take_revenue(revenue: Asset) {
230 match Matcher::matches_fungibles(&revenue) {
231 Ok((asset_id, amount)) => {
232 if amount > Zero::zero() {
233 if let Err(error) =
234 Assets::mint_into(asset_id.clone(), &FeeDestination::get(), amount)
235 {
236 log::error!(
237 target: "xcm::weight",
238 "XcmFeeHandler::take_revenue failed when minting asset: {:?}", error,
239 );
240 } else {
241 log::trace!(
242 target: "xcm::weight",
243 "XcmFeeHandler::take_revenue took {:?} of asset Id {:?}",
244 amount, asset_id,
245 );
246 }
247 }
248 }
249 Err(_) => {
250 log::error!(
251 target: "xcm::weight",
252 "XcmFeeHandler:take_revenue failed to match fungible asset, it has been burned."
253 );
254 }
255 }
256 }
257}
258
259pub fn split_location_into_chain_part_and_beneficiary(
267 mut location: Location,
268) -> Option<(Location, Location)> {
269 let mut beneficiary_junctions = Junctions::Here;
270
271 while let Some(junction) = location.last() {
272 if matches!(
273 junction,
274 Junction::Parachain(_) | Junction::GlobalConsensus(_)
275 ) {
276 return Some((location, beneficiary_junctions.into_location()));
277 }
278
279 let (prefix, maybe_last) = location.split_last_interior();
280 location = prefix;
281 if let Some(junction) = maybe_last {
282 beneficiary_junctions.push_front(junction).ok()?;
283 }
284 }
285
286 if location.parent_count() == 1 {
288 Some((Location::parent(), beneficiary_junctions.into_location()))
289 } else {
290 None
291 }
292}
293
294pub fn resolve_transfer_type<XcmExecutor: XcmAssetTransfers>(
305 asset: &Asset,
306 dest: &Location,
307) -> Option<TransferType> {
308 if let Ok(transfer_type) = XcmExecutor::determine_for(asset, dest) {
309 return Some(transfer_type);
310 }
311
312 let AssetId(location) = &asset.id;
313 if location == &Location::parent() {
314 return Some(TransferType::RemoteReserve(
315 Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]).into(),
316 ));
317 }
318
319 None
320}