referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
process_xcm_message.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Implementation of `ProcessMessage` for an `ExecuteXcm` implementation.
18
19use 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
31/// A message processor that delegates execution to an `XcmExecutor`.
32pub 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	/// Process the given message, using no more than the remaining `weight` to do so.
44	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		// The worst-case weight:
80		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			// In the error-case we assume the worst case and consume all possible weight.
111			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	/// The processor to use for tests.
145	type Processor =
146		ProcessXcmMessage<Junction, xcm_executor::XcmExecutor<xcm_config::XcmConfig>, RuntimeCall>;
147
148	#[test]
149	fn process_message_trivial_works() {
150		// ClearOrigin works.
151		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		// Trap makes it fail.
159		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				// Errors if we stay below a weight limit of 1000.
224				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				// Works with a limit of 1000.
235				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}