1use crate::AccountId;
34
35use frame_support::{
36 traits::{tokens::fungibles, ContainsPair, Get},
37 weights::constants::WEIGHT_REF_TIME_PER_SECOND,
38};
39use sp_runtime::traits::{Bounded, Convert, MaybeEquivalence, Zero};
40use sp_std::marker::PhantomData;
41
42use xcm::latest::{prelude::*, Weight};
44use xcm_builder::TakeRevenue;
45use xcm_executor::traits::{MatchesFungibles, WeightTrader};
46
47use orml_traits::location::Reserve;
49
50use pallet_xc_asset_config::{ExecutionPaymentRate, XcAssetLocation};
51
52#[cfg(test)]
53mod tests;
54
55pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16);
56pub const MAX_ASSETS: u32 = 64;
57pub const ASSET_HUB_PARA_ID: u32 = 1000;
58
59pub struct AssetLocationIdConverter<AssetId, AssetMapper>(PhantomData<(AssetId, AssetMapper)>);
64impl<AssetId, AssetMapper> MaybeEquivalence<Location, AssetId>
65 for AssetLocationIdConverter<AssetId, AssetMapper>
66where
67 AssetId: Clone + Eq + Bounded,
68 AssetMapper: XcAssetLocation<AssetId>,
69{
70 fn convert(location: &Location) -> Option<AssetId> {
71 AssetMapper::get_asset_id(location.clone())
72 }
73
74 fn convert_back(id: &AssetId) -> Option<Location> {
75 AssetMapper::get_xc_asset_location(id.clone())
76 }
77}
78
79pub struct FixedRateOfForeignAsset<T: ExecutionPaymentRate, R: TakeRevenue> {
84 weight: Weight,
86 consumed: u128,
88 asset_location_and_units_per_second: Option<(Location, u128)>,
90 _pd: PhantomData<(T, R)>,
91}
92
93impl<T: ExecutionPaymentRate, R: TakeRevenue> WeightTrader for FixedRateOfForeignAsset<T, R> {
94 fn new() -> Self {
95 Self {
96 weight: Weight::zero(),
97 consumed: 0,
98 asset_location_and_units_per_second: None,
99 _pd: PhantomData,
100 }
101 }
102
103 fn buy_weight(
104 &mut self,
105 weight: Weight,
106 payment: xcm_executor::AssetsInHolding,
107 _: &XcmContext,
108 ) -> Result<xcm_executor::AssetsInHolding, XcmError> {
109 log::trace!(
110 target: "xcm::weight",
111 "FixedRateOfForeignAsset::buy_weight weight: {:?}, payment: {:?}",
112 weight, payment,
113 );
114
115 let payment_asset = payment
117 .fungible_assets_iter()
118 .next()
119 .ok_or(XcmError::TooExpensive)?;
120
121 match payment_asset {
122 Asset {
123 id: AssetId(asset_location),
124 fun: Fungibility::Fungible(_),
125 } => {
126 if let Some(units_per_second) = T::get_units_per_second(asset_location.clone()) {
127 let amount = units_per_second.saturating_mul(weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128);
129 if amount == 0 {
130 return Ok(payment);
131 }
132
133 if let Some((tracked_asset_location, _)) =
135 &self.asset_location_and_units_per_second
136 {
137 if *tracked_asset_location != asset_location {
138 return Err(XcmError::NotWithdrawable);
139 }
140 }
141
142 let unused = payment
143 .checked_sub((asset_location.clone(), amount).into())
144 .map_err(|_| XcmError::TooExpensive)?;
145
146 self.weight = self.weight.saturating_add(weight);
147 self.consumed = self.consumed.saturating_add(amount);
148 self.asset_location_and_units_per_second =
149 Some((asset_location, units_per_second));
150
151 Ok(unused)
152 } else {
153 Err(XcmError::TooExpensive)
154 }
155 }
156 _ => Err(XcmError::TooExpensive),
157 }
158 }
159
160 fn refund_weight(&mut self, weight: Weight, _: &XcmContext) -> Option<Asset> {
161 log::trace!(target: "xcm::weight", "FixedRateOfForeignAsset::refund_weight weight: {:?}", weight);
162
163 if let Some((asset_location, units_per_second)) =
164 self.asset_location_and_units_per_second.clone()
165 {
166 let weight = weight.min(self.weight);
167 let amount = units_per_second
169 .saturating_mul(weight.ref_time() as u128)
170 .saturating_div(WEIGHT_REF_TIME_PER_SECOND as u128)
171 .min(self.consumed);
172
173 self.weight = self.weight.saturating_sub(weight);
174 self.consumed = self.consumed.saturating_sub(amount);
175
176 if amount > 0 {
177 Some((asset_location, amount).into())
178 } else {
179 None
180 }
181 } else {
182 None
183 }
184 }
185}
186
187impl<T: ExecutionPaymentRate, R: TakeRevenue> Drop for FixedRateOfForeignAsset<T, R> {
188 fn drop(&mut self) {
189 if let Some((asset_location, _)) = self.asset_location_and_units_per_second.clone() {
190 if self.consumed > 0 {
191 R::take_revenue((asset_location, self.consumed).into());
192 }
193 }
194 }
195}
196
197pub struct ReserveAssetFilter;
203impl ContainsPair<Asset, Location> for ReserveAssetFilter {
204 fn contains(asset: &Asset, origin: &Location) -> bool {
205 let AssetId(location) = &asset.id;
206 match (location.parents, location.first_interior()) {
207 (1, Some(Parachain(id))) => origin == &Location::new(1, [Parachain(*id)]),
209 (1, None) => origin == &Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]),
211 _ => false,
212 }
213 }
214}
215
216pub struct XcmFungibleFeeHandler<AccountId, Matcher, Assets, FeeDestination>(
222 sp_std::marker::PhantomData<(AccountId, Matcher, Assets, FeeDestination)>,
223);
224impl<
225 AccountId: Eq,
226 Assets: fungibles::Mutate<AccountId>,
227 Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
228 FeeDestination: Get<AccountId>,
229 > TakeRevenue for XcmFungibleFeeHandler<AccountId, Matcher, Assets, FeeDestination>
230{
231 fn take_revenue(revenue: Asset) {
232 match Matcher::matches_fungibles(&revenue) {
233 Ok((asset_id, amount)) => {
234 if amount > Zero::zero() {
235 if let Err(error) =
236 Assets::mint_into(asset_id.clone(), &FeeDestination::get(), amount)
237 {
238 log::error!(
239 target: "xcm::weight",
240 "XcmFeeHandler::take_revenue failed when minting asset: {:?}", error,
241 );
242 } else {
243 log::trace!(
244 target: "xcm::weight",
245 "XcmFeeHandler::take_revenue took {:?} of asset Id {:?}",
246 amount, asset_id,
247 );
248 }
249 }
250 }
251 Err(_) => {
252 log::error!(
253 target: "xcm::weight",
254 "XcmFeeHandler:take_revenue failed to match fungible asset, it has been burned."
255 );
256 }
257 }
258 }
259}
260
261pub struct AccountIdToMultiLocation;
263impl Convert<AccountId, Location> for AccountIdToMultiLocation {
264 fn convert(account: AccountId) -> Location {
265 AccountId32 {
266 network: None,
267 id: account.into(),
268 }
269 .into()
270 }
271}
272
273pub struct AbsoluteAndRelativeReserveProvider<AbsoluteLocation>(PhantomData<AbsoluteLocation>);
276impl<AbsoluteLocation: Get<Location>> Reserve
277 for AbsoluteAndRelativeReserveProvider<AbsoluteLocation>
278{
279 fn reserve(asset: &Asset) -> Option<Location> {
280 let reserve_location = {
283 let AssetId(location) = &asset.id;
284 match (location.parents, location.first_interior()) {
285 (1, Some(Parachain(id))) => Some(Location::new(1, [Parachain(*id)])),
286 (1, _) => Some(Location::parent()),
287 (0, Some(Parachain(id))) => Some(Location::new(0, [Parachain(*id)])),
288 (0, _) => Some(Location::here()), _ => None,
290 }
291 }?;
292
293 if reserve_location == AbsoluteLocation::get() {
294 return Some(Location::here());
295 }
296
297 let is_relay_token = reserve_location.contains_parents_only(1);
298 if is_relay_token {
299 return Some(Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]));
300 }
301
302 Some(reserve_location)
303 }
304}