1use alloc::{collections::btree_set::BTreeSet, vec::Vec};
20use codec::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::*;
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::DownwardMessageQueueMeta::<T>::clear(u32::MAX, None);
160 let _ = dmp::DownwardMessageQueuePages::<T>::clear(u32::MAX, None);
161 let _ = dmp::DownwardMessageQueueLazyDelete::<T>::clear(u32::MAX, None);
162 }
163
164 fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
165 let para_ids: BTreeSet<_> = dmp::DownwardMessageQueueMeta::<T>::iter_keys()
166 .chain(dmp::migration::v0::DownwardMessageQueues::<T>::iter_keys())
167 .collect();
168
169 para_ids
170 .into_iter()
171 .map(|para_id| {
172 let decoded_messages: Vec<VersionedXcm<()>> =
173 dmp::Pallet::<T>::dmq_contents_do_not_call_in_consensus(para_id)
174 .iter()
175 .map(|downward_message| {
176 let message = VersionedXcm::<()>::decode_all_with_mem_and_depth_limit(
177 &mut &downward_message.msg[..],
178 )
179 .unwrap();
180 log::trace!(
181 target: "xcm::DownwardMessageQueues::get_messages",
182 "Message: {:?}, sent at: {:?}", message, downward_message.sent_at
183 );
184 message
185 })
186 .collect();
187 (
188 VersionedLocation::from(Location::from(Parachain(para_id.into()))),
189 decoded_messages,
190 )
191 })
192 .collect()
193 }
194}
195
196#[cfg(feature = "runtime-benchmarks")]
201pub struct ToParachainDeliveryHelper<
202 XcmConfig,
203 ExistentialDeposit,
204 PriceForDelivery,
205 ParaId,
206 ToParaIdHelper,
207>(
208 core::marker::PhantomData<(
209 XcmConfig,
210 ExistentialDeposit,
211 PriceForDelivery,
212 ParaId,
213 ToParaIdHelper,
214 )>,
215);
216
217#[cfg(feature = "runtime-benchmarks")]
218impl<
219 XcmConfig: xcm_executor::Config,
220 ExistentialDeposit: Get<Option<Asset>>,
221 PriceForDelivery: PriceForMessageDelivery<Id = ParaId>,
222 Parachain: Get<ParaId>,
223 ToParachainHelper: polkadot_runtime_parachains::EnsureForParachain,
224 > xcm_builder::EnsureDelivery
225 for ToParachainDeliveryHelper<
226 XcmConfig,
227 ExistentialDeposit,
228 PriceForDelivery,
229 Parachain,
230 ToParachainHelper,
231 >
232{
233 fn ensure_successful_delivery(
234 origin_ref: &Location,
235 dest: &Location,
236 fee_reason: xcm_executor::traits::FeeReason,
237 ) -> (Option<xcm_executor::FeesMode>, Option<Assets>) {
238 use alloc::vec;
239 use xcm::{latest::MAX_ITEMS_IN_ASSETS, MAX_INSTRUCTIONS_TO_DECODE};
240 use xcm_executor::{
241 traits::{FeeManager, TransactAsset},
242 FeesMode,
243 };
244
245 if let Some(Parachain(para_id)) = dest.first_interior() {
247 if ParaId::from(*para_id) != Parachain::get().into() {
248 return (None, None);
249 }
250 } else {
251 return (None, None);
252 }
253
254 ToParachainHelper::ensure(Parachain::get());
256
257 let mut fees_mode = None;
258 if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) {
259 let context = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
261
262 if let Some(ed) = ExistentialDeposit::get() {
264 let holdings = XcmConfig::AssetTransactor::mint_asset(&ed, &context).unwrap();
265 XcmConfig::AssetTransactor::deposit_asset(holdings, &origin_ref, Some(&context))
266 .unwrap();
267 }
268
269 let mut max_assets: Vec<Asset> = Vec::new();
271 for i in 0..MAX_ITEMS_IN_ASSETS {
272 max_assets.push((GeneralIndex(i as u128), 100u128).into());
273 }
274 let overestimated_xcm =
275 vec![WithdrawAsset(max_assets.into()); MAX_INSTRUCTIONS_TO_DECODE as usize].into();
276 let overestimated_fees =
277 PriceForDelivery::price_for_delivery(Parachain::get(), &overestimated_xcm);
278
279 for fee in overestimated_fees.inner() {
281 let holdings = XcmConfig::AssetTransactor::mint_asset(fee, &context).unwrap();
282 XcmConfig::AssetTransactor::deposit_asset(holdings, &origin_ref, Some(&context))
283 .unwrap();
284 }
285
286 fees_mode = Some(FeesMode { jit_withdraw: true });
288 }
289 (fees_mode, None)
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::integration_tests::new_test_ext;
297 use alloc::vec;
298 use frame_support::{assert_ok, parameter_types};
299 use polkadot_runtime_parachains::FeeTracker;
300 use sp_runtime::FixedU128;
301 use xcm::MAX_XCM_DECODE_DEPTH;
302
303 parameter_types! {
304 pub const BaseDeliveryFee: u128 = 300_000_000;
305 pub const TransactionByteFee: u128 = 1_000_000;
306 pub FeeAssetId: AssetId = AssetId(Here.into());
307 }
308
309 struct TestFeeTracker;
310 impl FeeTracker for TestFeeTracker {
311 type Id = ParaId;
312
313 fn get_fee_factor(_: Self::Id) -> FixedU128 {
314 FixedU128::from_rational(101, 100)
315 }
316
317 fn set_fee_factor(_id: Self::Id, _val: FixedU128) {}
318
319 fn increase_fee_factor(_: Self::Id, _: u128) {}
320
321 fn decrease_fee_factor(_: Self::Id) -> bool {
322 true
323 }
324 }
325
326 type TestExponentialPrice =
327 ExponentialPrice<FeeAssetId, BaseDeliveryFee, TransactionByteFee, TestFeeTracker>;
328
329 #[test]
330 fn exponential_price_correct_price_calculation() {
331 let id: ParaId = 123.into();
332 let b: u128 = BaseDeliveryFee::get();
333 let m: u128 = TransactionByteFee::get();
334
335 let result: u128 = TestFeeTracker::get_fee_factor(id).saturating_mul_int(b + m);
338 assert_eq!(
339 TestExponentialPrice::price_for_delivery(id, &Xcm(vec![])),
340 (FeeAssetId::get(), result).into()
341 );
342
343 let result: u128 = TestFeeTracker::get_fee_factor(id).saturating_mul_int(b + (2 * m));
345 assert_eq!(
346 TestExponentialPrice::price_for_delivery(id, &Xcm(vec![ClearOrigin])),
347 (FeeAssetId::get(), result).into()
348 );
349
350 let result: u128 = TestFeeTracker::get_fee_factor(id).saturating_mul_int(b + (4 * m));
352 assert_eq!(
353 TestExponentialPrice::price_for_delivery(
354 id,
355 &Xcm(vec![SetAppendix(Xcm(vec![ClearOrigin]))])
356 ),
357 (FeeAssetId::get(), result).into()
358 );
359 }
360
361 #[test]
362 fn child_parachain_router_validate_nested_xcm_works() {
363 let dest = Parachain(5555);
364
365 type Router = ChildParachainRouter<
366 crate::integration_tests::Test,
367 (),
368 NoPriceForMessageDelivery<ParaId>,
369 >;
370
371 let mut good = Xcm(vec![ClearOrigin]);
373 for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
374 good = Xcm(vec![SetAppendix(good)]);
375 }
376
377 new_test_ext().execute_with(|| {
378 configuration::ActiveConfig::<crate::integration_tests::Test>::mutate(|c| {
379 c.max_downward_message_size = u32::MAX;
380 });
381
382 dmp::Pallet::<crate::integration_tests::Test>::make_parachain_reachable(5555);
383
384 assert_ok!(<Router as SendXcm>::validate(
386 &mut Some(dest.into()),
387 &mut Some(good.clone())
388 ));
389
390 let bad = Xcm(vec![SetAppendix(good)]);
392 assert_eq!(
393 Err(ExceedsMaxMessageSize),
394 <Router as SendXcm>::validate(&mut Some(dest.into()), &mut Some(bad))
395 );
396 });
397 }
398
399 #[test]
400 fn get_messages_surfaces_v0_only_paras() {
401 use polkadot_primitives::InboundDownwardMessage;
402
403 type Test = crate::integration_tests::Test;
404 type Router = ChildParachainRouter<Test, (), NoPriceForMessageDelivery<ParaId>>;
405
406 let para_v1: ParaId = 5000.into();
407 let para_v0: ParaId = 6000.into();
408 let xcm_v1 = VersionedXcm::from(Xcm::<()>(vec![ClearOrigin]));
409 let xcm_v0 = VersionedXcm::from(Xcm::<()>(vec![ClearOrigin]));
410
411 new_test_ext().execute_with(|| {
412 configuration::ActiveConfig::<Test>::mutate(|c| {
413 c.max_downward_message_size = 1024;
414 });
415 dmp::Pallet::<Test>::make_parachain_reachable(para_v1);
416 let config = configuration::ActiveConfig::<Test>::get();
417 assert_ok!(dmp::Pallet::<Test>::queue_downward_message(
418 &config,
419 para_v1,
420 xcm_v1.encode()
421 ));
422
423 dmp::migration::v0::DownwardMessageQueues::<Test>::insert(
424 para_v0,
425 vec![InboundDownwardMessage { sent_at: 1, msg: xcm_v0.encode() }],
426 );
427
428 assert_eq!(
429 <Router as InspectMessageQueues>::get_messages(),
430 vec![
431 (
432 VersionedLocation::from(Location::from(Parachain(u32::from(para_v1)))),
433 vec![xcm_v1],
434 ),
435 (
436 VersionedLocation::from(Location::from(Parachain(u32::from(para_v0)))),
437 vec![xcm_v0],
438 ),
439 ]
440 );
441 });
442 }
443}