1use codec::{Decode, DecodeLimit, FullCodec, MaxEncodedLen};
20use core::{fmt::Debug, marker::PhantomData};
21use frame_support::{
22 dispatch::GetDispatchInfo,
23 traits::{ProcessMessage, ProcessMessageError},
24};
25use scale_info::TypeInfo;
26use sp_weights::{Weight, WeightMeter};
27use xcm::{prelude::*, MAX_XCM_DECODE_DEPTH};
28
29const LOG_TARGET: &str = "xcm::process-message";
30
31pub struct ProcessXcmMessage<MessageOrigin, XcmExecutor, Call>(
33 PhantomData<(MessageOrigin, XcmExecutor, Call)>,
34);
35impl<
36 MessageOrigin: Into<Location> + FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug,
37 XcmExecutor: ExecuteXcm<Call>,
38 Call: Decode + GetDispatchInfo,
39 > ProcessMessage for ProcessXcmMessage<MessageOrigin, XcmExecutor, Call>
40{
41 type Origin = MessageOrigin;
42
43 fn process_message(
45 message: &[u8],
46 origin: Self::Origin,
47 meter: &mut WeightMeter,
48 id: &mut XcmHash,
49 ) -> Result<bool, ProcessMessageError> {
50 let versioned_message = VersionedXcm::<Call>::decode_all_with_depth_limit(
51 MAX_XCM_DECODE_DEPTH,
52 &mut &message[..],
53 )
54 .map_err(|e| {
55 tracing::trace!(
56 target: LOG_TARGET,
57 ?e,
58 "`VersionedXcm` failed to decode",
59 );
60
61 ProcessMessageError::Corrupt
62 })?;
63 let message = Xcm::<Call>::try_from(versioned_message).map_err(|_| {
64 tracing::trace!(
65 target: LOG_TARGET,
66 "Failed to convert `VersionedXcm` into `xcm::prelude::Xcm`!",
67 );
68
69 ProcessMessageError::Unsupported
70 })?;
71 let pre = XcmExecutor::prepare(message, Weight::MAX).map_err(|_| {
72 tracing::trace!(
73 target: LOG_TARGET,
74 "Failed to prepare message.",
75 );
76
77 ProcessMessageError::Unsupported
78 })?;
79 let required = pre.weight_of();
81 if !meter.can_consume(required) {
82 tracing::trace!(
83 target: LOG_TARGET,
84 "Xcm required {required} more than remaining {}",
85 meter.remaining(),
86 );
87
88 return Err(ProcessMessageError::Overweight(required))
89 }
90
91 let (consumed, result) = match XcmExecutor::execute(origin.into(), pre, id, Weight::zero())
92 {
93 Outcome::Complete { used } => {
94 tracing::trace!(
95 target: LOG_TARGET,
96 "XCM message execution complete, used weight: {used}",
97 );
98 (used, Ok(true))
99 },
100 Outcome::Incomplete { used, error: InstructionError { index, error } } => {
101 tracing::trace!(
102 target: LOG_TARGET,
103 ?error,
104 ?index,
105 ?used,
106 "XCM message execution incomplete",
107 );
108 (used, Ok(false))
109 },
110 Outcome::Error(InstructionError { error, index }) => {
112 tracing::trace!(
113 target: LOG_TARGET,
114 ?error,
115 ?index,
116 "XCM message execution error",
117 );
118 let error = match error {
119 xcm::latest::Error::ExceedsStackLimit => ProcessMessageError::StackLimitReached,
120 _ => ProcessMessageError::Unsupported,
121 };
122
123 (required, Err(error))
124 },
125 };
126 meter.consume(consumed);
127 result
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use alloc::vec;
135 use codec::Encode;
136 use frame_support::{
137 assert_err, assert_ok,
138 traits::{ProcessMessageError, ProcessMessageError::*},
139 };
140 use polkadot_test_runtime::*;
141 use xcm::{v3, v4, v5, VersionedXcm};
142
143 const ORIGIN: Junction = Junction::OnlyChild;
144 type Processor =
146 ProcessXcmMessage<Junction, xcm_executor::XcmExecutor<xcm_config::XcmConfig>, RuntimeCall>;
147
148 #[test]
149 fn process_message_trivial_works() {
150 assert!(process(v3_xcm(true)).unwrap());
152 assert!(process(v4_xcm(true)).unwrap());
153 assert!(process(v5_xcm(true)).unwrap());
154 }
155
156 #[test]
157 fn process_message_trivial_fails() {
158 sp_io::TestExternalities::default().execute_with(|| {
160 assert!(!process(v3_xcm(false)).unwrap());
161 assert!(!process(v4_xcm(false)).unwrap());
162 assert!(!process(v5_xcm(false)).unwrap());
163 });
164 }
165
166 #[test]
167 fn process_message_corrupted_fails() {
168 let msgs: &[&[u8]] = &[&[], &[55, 66], &[123, 222, 233]];
169 for msg in msgs {
170 assert_err!(process_raw(msg), Corrupt);
171 }
172 }
173
174 #[test]
175 fn process_message_exceeds_limits_fails() {
176 struct MockedExecutor;
177 impl ExecuteXcm<()> for MockedExecutor {
178 type Prepared = xcm_executor::WeighedMessage<()>;
179 fn prepare(
180 message: xcm::latest::Xcm<()>,
181 _: Weight,
182 ) -> core::result::Result<Self::Prepared, InstructionError> {
183 Ok(xcm_executor::WeighedMessage::new(Weight::zero(), message))
184 }
185 fn execute(
186 _: impl Into<Location>,
187 _: Self::Prepared,
188 _: &mut XcmHash,
189 _: Weight,
190 ) -> Outcome {
191 Outcome::Error(InstructionError {
192 index: 0,
193 error: xcm::latest::Error::ExceedsStackLimit,
194 })
195 }
196 fn charge_fees(_location: impl Into<Location>, _fees: Assets) -> xcm::latest::Result {
197 unreachable!()
198 }
199 }
200
201 type Processor = ProcessXcmMessage<Junction, MockedExecutor, ()>;
202
203 let xcm = VersionedXcm::from(xcm::latest::Xcm::<()>(vec![
204 xcm::latest::Instruction::<()>::ClearOrigin,
205 ]));
206 assert_err!(
207 Processor::process_message(
208 &xcm.encode(),
209 ORIGIN,
210 &mut WeightMeter::new(),
211 &mut [0; 32]
212 ),
213 ProcessMessageError::StackLimitReached,
214 );
215 }
216
217 #[test]
218 fn process_message_overweight_fails() {
219 sp_io::TestExternalities::default().execute_with(|| {
220 for msg in [v4_xcm(true), v4_xcm(false), v4_xcm(false), v3_xcm(false)] {
221 let msg = &msg.encode()[..];
222
223 for i in 0..10 {
225 let meter = &mut WeightMeter::with_limit((i * 10).into());
226 let mut id = [0; 32];
227 assert_err!(
228 Processor::process_message(msg, ORIGIN, meter, &mut id),
229 Overweight(1000.into())
230 );
231 assert_eq!(meter.consumed(), 0.into());
232 }
233
234 let meter = &mut WeightMeter::with_limit(1000.into());
236 let mut id = [0; 32];
237 assert_ok!(Processor::process_message(msg, ORIGIN, meter, &mut id));
238 assert_eq!(meter.consumed(), 1000.into());
239 }
240 });
241 }
242
243 fn v3_xcm(success: bool) -> VersionedXcm<RuntimeCall> {
244 let instr = if success {
245 v3::Instruction::<RuntimeCall>::ClearOrigin
246 } else {
247 v3::Instruction::<RuntimeCall>::Trap(1)
248 };
249 VersionedXcm::V3(v3::Xcm::<RuntimeCall>(vec![instr]))
250 }
251
252 fn v4_xcm(success: bool) -> VersionedXcm<RuntimeCall> {
253 let instr = if success {
254 v4::Instruction::<RuntimeCall>::ClearOrigin
255 } else {
256 v4::Instruction::<RuntimeCall>::Trap(1)
257 };
258 VersionedXcm::V4(v4::Xcm::<RuntimeCall>(vec![instr]))
259 }
260
261 fn v5_xcm(success: bool) -> VersionedXcm<RuntimeCall> {
262 let instr = if success {
263 v5::Instruction::<RuntimeCall>::ClearOrigin
264 } else {
265 v5::Instruction::<RuntimeCall>::Trap(1)
266 };
267 VersionedXcm::V5(v5::Xcm::<RuntimeCall>(vec![instr]))
268 }
269
270 fn process(msg: VersionedXcm<RuntimeCall>) -> Result<bool, ProcessMessageError> {
271 process_raw(msg.encode().as_slice())
272 }
273
274 fn process_raw(raw: &[u8]) -> Result<bool, ProcessMessageError> {
275 Processor::process_message(raw, ORIGIN, &mut WeightMeter::new(), &mut [0; 32])
276 }
277}