1use alloc::vec::Vec;
20use codec::{DecodeLimit, Encode};
21use core::marker::PhantomData;
22use frame_support::traits::Get;
23use frame_system::pallet_prelude::BlockNumberFor;
24use polkadot_primitives::Id as ParaId;
25use polkadot_runtime_parachains::{
26 configuration::{self, HostConfiguration},
27 dmp, FeeTracker,
28};
29use sp_runtime::FixedPointNumber;
30use xcm::{prelude::*, MAX_XCM_DECODE_DEPTH};
31use xcm_builder::InspectMessageQueues;
32use SendError::*;
33
34pub trait PriceForMessageDelivery {
37 type Id;
39 fn price_for_delivery(id: Self::Id, message: &Xcm<()>) -> Assets;
41}
42impl PriceForMessageDelivery for () {
43 type Id = ();
44
45 fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> Assets {
46 Assets::new()
47 }
48}
49
50pub struct NoPriceForMessageDelivery<Id>(PhantomData<Id>);
51impl<Id> PriceForMessageDelivery for NoPriceForMessageDelivery<Id> {
52 type Id = Id;
53
54 fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> Assets {
55 Assets::new()
56 }
57}
58
59pub struct ConstantPrice<T>(core::marker::PhantomData<T>);
61impl<T: Get<Assets>> PriceForMessageDelivery for ConstantPrice<T> {
62 type Id = ();
63
64 fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> Assets {
65 T::get()
66 }
67}
68
69pub struct ExponentialPrice<A, B, M, F>(core::marker::PhantomData<(A, B, M, F)>);
84impl<A: Get<AssetId>, B: Get<u128>, M: Get<u128>, F: FeeTracker> PriceForMessageDelivery
85 for ExponentialPrice<A, B, M, F>
86{
87 type Id = F::Id;
88
89 fn price_for_delivery(id: Self::Id, msg: &Xcm<()>) -> Assets {
90 let msg_fee = (msg.encoded_size() as u128).saturating_mul(M::get());
91 let fee_sum = B::get().saturating_add(msg_fee);
92 let amount = F::get_fee_factor(id).saturating_mul_int(fee_sum);
93 (A::get(), amount).into()
94 }
95}
96
97pub struct ChildParachainRouter<T, W, P>(PhantomData<(T, W, P)>);
99
100impl<T: configuration::Config + dmp::Config, W: xcm::WrapVersion, P> SendXcm
101 for ChildParachainRouter<T, W, P>
102where
103 P: PriceForMessageDelivery<Id = ParaId>,
104{
105 type Ticket = (HostConfiguration<BlockNumberFor<T>>, ParaId, Vec<u8>);
106
107 fn validate(
108 dest: &mut Option<Location>,
109 msg: &mut Option<Xcm<()>>,
110 ) -> SendResult<(HostConfiguration<BlockNumberFor<T>>, ParaId, Vec<u8>)> {
111 let d = dest.take().ok_or(MissingArgument)?;
112 let id = if let (0, [Parachain(id)]) = d.unpack() {
113 *id
114 } else {
115 *dest = Some(d);
116 return Err(NotApplicable)
117 };
118
119 let xcm = msg.take().ok_or(MissingArgument)?;
121 let config = configuration::ActiveConfig::<T>::get();
122 let para = id.into();
123 let price = P::price_for_delivery(para, &xcm);
124 let versioned_xcm = W::wrap_version(&d, xcm).map_err(|()| DestinationUnsupported)?;
125 versioned_xcm.check_is_decodable().map_err(|()| ExceedsMaxMessageSize)?;
126 let blob = versioned_xcm.encode();
127 dmp::Pallet::<T>::can_queue_downward_message(&config, ¶, &blob)
128 .map_err(Into::<SendError>::into)?;
129
130 Ok(((config, para, blob), price))
131 }
132
133 fn deliver(
134 (config, para, blob): (HostConfiguration<BlockNumberFor<T>>, ParaId, Vec<u8>),
135 ) -> Result<XcmHash, SendError> {
136 let hash = sp_io::hashing::blake2_256(&blob[..]);
137 dmp::Pallet::<T>::queue_downward_message(&config, para, blob)
138 .map(|()| hash)
139 .map_err(|error| {
140 log::debug!(
141 target: "xcm::xcm_sender::deliver",
142 "Failed to place into DMP queue: error: {error:?}, id: {hash:?}",
143 );
144 SendError::Transport(&"Error placing into DMP queue")
145 })
146 }
147
148 #[cfg(feature = "runtime-benchmarks")]
149 fn ensure_successful_delivery(location: Option<Location>) {
150 if let Some((0, [Parachain(id)])) = location.as_ref().map(|l| l.unpack()) {
151 dmp::Pallet::<T>::make_parachain_reachable(*id);
152 }
153 }
154}
155
156impl<T: dmp::Config, W, P> InspectMessageQueues for ChildParachainRouter<T, W, P> {
157 fn clear_messages() {
158 let _ = dmp::DownwardMessageQueues::<T>::clear(u32::MAX, None);
160 }
161
162 fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
163 dmp::DownwardMessageQueues::<T>::iter()
164 .map(|(para_id, messages)| {
165 let decoded_messages: Vec<VersionedXcm<()>> = messages
166 .iter()
167 .map(|downward_message| {
168 let message = VersionedXcm::<()>::decode_all_with_depth_limit(
169 MAX_XCM_DECODE_DEPTH,
170 &mut &downward_message.msg[..],
171 )
172 .unwrap();
173 log::trace!(
174 target: "xcm::DownwardMessageQueues::get_messages",
175 "Message: {:?}, sent at: {:?}", message, downward_message.sent_at
176 );
177 message
178 })
179 .collect();
180 (
181 VersionedLocation::from(Location::from(Parachain(para_id.into()))),
182 decoded_messages,
183 )
184 })
185 .collect()
186 }
187}
188
189#[cfg(feature = "runtime-benchmarks")]
194pub struct ToParachainDeliveryHelper<
195 XcmConfig,
196 ExistentialDeposit,
197 PriceForDelivery,
198 ParaId,
199 ToParaIdHelper,
200>(
201 core::marker::PhantomData<(
202 XcmConfig,
203 ExistentialDeposit,
204 PriceForDelivery,
205 ParaId,
206 ToParaIdHelper,
207 )>,
208);
209
210#[cfg(feature = "runtime-benchmarks")]
211impl<
212 XcmConfig: xcm_executor::Config,
213 ExistentialDeposit: Get<Option<Asset>>,
214 PriceForDelivery: PriceForMessageDelivery<Id = ParaId>,
215 Parachain: Get<ParaId>,
216 ToParachainHelper: polkadot_runtime_parachains::EnsureForParachain,
217 > xcm_builder::EnsureDelivery
218 for ToParachainDeliveryHelper<
219 XcmConfig,
220 ExistentialDeposit,
221 PriceForDelivery,
222 Parachain,
223 ToParachainHelper,
224 >
225{
226 fn ensure_successful_delivery(
227 origin_ref: &Location,
228 dest: &Location,
229 fee_reason: xcm_executor::traits::FeeReason,
230 ) -> (Option<xcm_executor::FeesMode>, Option<Assets>) {
231 use alloc::vec;
232 use xcm::{latest::MAX_ITEMS_IN_ASSETS, MAX_INSTRUCTIONS_TO_DECODE};
233 use xcm_executor::{
234 traits::{FeeManager, TransactAsset},
235 FeesMode,
236 };
237
238 if let Some(Parachain(para_id)) = dest.first_interior() {
240 if ParaId::from(*para_id) != Parachain::get().into() {
241 return (None, None)
242 }
243 } else {
244 return (None, None)
245 }
246
247 ToParachainHelper::ensure(Parachain::get());
249
250 let mut fees_mode = None;
251 if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) {
252 if let Some(ed) = ExistentialDeposit::get() {
256 XcmConfig::AssetTransactor::deposit_asset(&ed, &origin_ref, None).unwrap();
257 }
258
259 let mut max_assets: Vec<Asset> = Vec::new();
261 for i in 0..MAX_ITEMS_IN_ASSETS {
262 max_assets.push((GeneralIndex(i as u128), 100u128).into());
263 }
264 let overestimated_xcm =
265 vec![WithdrawAsset(max_assets.into()); MAX_INSTRUCTIONS_TO_DECODE as usize].into();
266 let overestimated_fees =
267 PriceForDelivery::price_for_delivery(Parachain::get(), &overestimated_xcm);
268
269 for fee in overestimated_fees.inner() {
271 XcmConfig::AssetTransactor::deposit_asset(&fee, &origin_ref, None).unwrap();
272 }
273
274 fees_mode = Some(FeesMode { jit_withdraw: true });
276 }
277 (fees_mode, None)
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use crate::integration_tests::new_test_ext;
285 use alloc::vec;
286 use frame_support::{assert_ok, parameter_types};
287 use polkadot_runtime_parachains::FeeTracker;
288 use sp_runtime::FixedU128;
289 use xcm::MAX_XCM_DECODE_DEPTH;
290
291 parameter_types! {
292 pub const BaseDeliveryFee: u128 = 300_000_000;
293 pub const TransactionByteFee: u128 = 1_000_000;
294 pub FeeAssetId: AssetId = AssetId(Here.into());
295 }
296
297 struct TestFeeTracker;
298 impl FeeTracker for TestFeeTracker {
299 type Id = ParaId;
300
301 fn get_fee_factor(_: Self::Id) -> FixedU128 {
302 FixedU128::from_rational(101, 100)
303 }
304
305 fn set_fee_factor(_id: Self::Id, _val: FixedU128) {}
306
307 fn increase_fee_factor(_: Self::Id, _: u128) {}
308
309 fn decrease_fee_factor(_: Self::Id) -> bool {
310 true
311 }
312 }
313
314 type TestExponentialPrice =
315 ExponentialPrice<FeeAssetId, BaseDeliveryFee, TransactionByteFee, TestFeeTracker>;
316
317 #[test]
318 fn exponential_price_correct_price_calculation() {
319 let id: ParaId = 123.into();
320 let b: u128 = BaseDeliveryFee::get();
321 let m: u128 = TransactionByteFee::get();
322
323 let result: u128 = TestFeeTracker::get_fee_factor(id).saturating_mul_int(b + m);
326 assert_eq!(
327 TestExponentialPrice::price_for_delivery(id, &Xcm(vec![])),
328 (FeeAssetId::get(), result).into()
329 );
330
331 let result: u128 = TestFeeTracker::get_fee_factor(id).saturating_mul_int(b + (2 * m));
333 assert_eq!(
334 TestExponentialPrice::price_for_delivery(id, &Xcm(vec![ClearOrigin])),
335 (FeeAssetId::get(), result).into()
336 );
337
338 let result: u128 = TestFeeTracker::get_fee_factor(id).saturating_mul_int(b + (4 * m));
340 assert_eq!(
341 TestExponentialPrice::price_for_delivery(
342 id,
343 &Xcm(vec![SetAppendix(Xcm(vec![ClearOrigin]))])
344 ),
345 (FeeAssetId::get(), result).into()
346 );
347 }
348
349 #[test]
350 fn child_parachain_router_validate_nested_xcm_works() {
351 let dest = Parachain(5555);
352
353 type Router = ChildParachainRouter<
354 crate::integration_tests::Test,
355 (),
356 NoPriceForMessageDelivery<ParaId>,
357 >;
358
359 let mut good = Xcm(vec![ClearOrigin]);
361 for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
362 good = Xcm(vec![SetAppendix(good)]);
363 }
364
365 new_test_ext().execute_with(|| {
366 configuration::ActiveConfig::<crate::integration_tests::Test>::mutate(|c| {
367 c.max_downward_message_size = u32::MAX;
368 });
369
370 dmp::Pallet::<crate::integration_tests::Test>::make_parachain_reachable(5555);
371
372 assert_ok!(<Router as SendXcm>::validate(
374 &mut Some(dest.into()),
375 &mut Some(good.clone())
376 ));
377
378 let bad = Xcm(vec![SetAppendix(good)]);
380 assert_eq!(
381 Err(ExceedsMaxMessageSize),
382 <Router as SendXcm>::validate(&mut Some(dest.into()), &mut Some(bad))
383 );
384 });
385 }
386}