referrerpolicy=no-referrer-when-downgrade

pallet_xcm_precompiles/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17// Ensure we're `no_std` when compiling for Wasm.
18#![cfg_attr(not(feature = "std"), no_std)]
19
20extern crate alloc;
21
22use alloc::vec::Vec;
23use codec::DecodeAll;
24use core::{fmt, marker::PhantomData, num::NonZero};
25use frame_support::dispatch::RawOrigin;
26use pallet_revive::{
27	precompiles::{
28		alloy::{self, sol_types::SolValue},
29		AddressMatcher, Error, Ext, Precompile,
30	},
31	DispatchInfo, ExecOrigin as Origin, Weight,
32};
33use pallet_xcm::{Config, WeightInfo};
34use tracing::error;
35use xcm::{v5, IdentifyVersion, VersionedLocation, VersionedXcm};
36use xcm_executor::traits::WeightBounds;
37
38alloy::sol!("src/interface/IXcm.sol");
39use IXcm::IXcmCalls;
40
41#[cfg(test)]
42mod mock;
43#[cfg(test)]
44mod tests;
45
46const LOG_TARGET: &str = "xcm::precompiles";
47
48fn revert(error: &impl fmt::Debug, message: &str) -> Error {
49	error!(target: LOG_TARGET, ?error, "{}", message);
50	Error::Revert(message.into())
51}
52
53// We don't allow XCM versions older than 5.
54fn ensure_xcm_version<V: IdentifyVersion>(input: &V) -> Result<(), Error> {
55	let version = input.identify_version();
56	if version < v5::VERSION {
57		return Err(Error::Revert("Only XCM version 5 and onwards are supported.".into()));
58	}
59	Ok(())
60}
61
62pub struct XcmPrecompile<T>(PhantomData<T>);
63
64impl<Runtime> Precompile for XcmPrecompile<Runtime>
65where
66	Runtime: crate::Config + pallet_revive::Config,
67{
68	type T = Runtime;
69	const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(10).unwrap());
70	const HAS_CONTRACT_INFO: bool = false;
71	type Interface = IXcm::IXcmCalls;
72
73	fn call(
74		_address: &[u8; 20],
75		input: &Self::Interface,
76		env: &mut impl Ext<T = Self::T>,
77	) -> Result<Vec<u8>, Error> {
78		frame_support::ensure!(
79			!env.is_delegate_call(),
80			pallet_revive::Error::<Self::T>::PrecompileDelegateDenied,
81		);
82
83		let origin = env.caller();
84		let frame_origin = match origin {
85			Origin::Root => RawOrigin::Root.into(),
86			Origin::Signed(account_id) => RawOrigin::Signed(account_id.clone()).into(),
87		};
88
89		match input {
90			IXcmCalls::send(_) | IXcmCalls::execute(_) if env.is_read_only() => {
91				Err(Error::Error(pallet_revive::Error::<Self::T>::StateChangeDenied.into()))
92			},
93			IXcmCalls::send(IXcm::sendCall { destination, message }) => {
94				// Charged before decoding; see `WeightInfo::decode_xcm`.
95				env.charge(
96					<Runtime as Config>::WeightInfo::decode_xcm(
97						message.len().saturating_add(destination.len()) as u32,
98					)
99					.saturating_add(<Runtime as Config>::WeightInfo::send()),
100				)?;
101
102				let final_destination = VersionedLocation::decode_all(&mut &destination[..])
103					.map_err(|error| {
104						revert(&error, "XCM send failed: Invalid destination format")
105					})?;
106
107				ensure_xcm_version(&final_destination)?;
108
109				let final_message =
110					VersionedXcm::<()>::decode_all_with_mem_and_depth_limit(&mut &message[..])
111						.map_err(|error| {
112							revert(&error, "XCM send failed: Invalid message format")
113						})?;
114
115				ensure_xcm_version(&final_message)?;
116
117				pallet_xcm::Pallet::<Runtime>::send(
118					frame_origin,
119					final_destination.into(),
120					final_message.into(),
121				)
122				.map(|_| Vec::new())
123				.map_err(|error| {
124					revert(
125						&error,
126						"XCM send failed: destination or message format may be incompatible",
127					)
128				})
129			},
130			IXcmCalls::execute(IXcm::executeCall { message, weight }) => {
131				// Executing weighs the blob too, via `prepare`. Kept separate from the execution
132				// charge below, which gets refunded and would otherwise give this back.
133				env.charge(<Runtime as Config>::WeightInfo::weigh_message(message.len() as u32))?;
134
135				let max_weight = Weight::from_parts(weight.refTime, weight.proofSize);
136				let weight_to_charge =
137					max_weight.saturating_add(<Runtime as Config>::WeightInfo::execute());
138				let charged_amount = env.charge(weight_to_charge)?;
139
140				let final_message = VersionedXcm::decode_all_with_mem_and_depth_limit(
141					&mut &message[..],
142				)
143				.map_err(|error| revert(&error, "XCM execute failed: Invalid message format"))?;
144
145				ensure_xcm_version(&final_message)?;
146
147				let result = pallet_xcm::Pallet::<Runtime>::execute(
148					frame_origin,
149					final_message.into(),
150					max_weight,
151				);
152
153				let pre = DispatchInfo {
154					call_weight: weight_to_charge,
155					extension_weight: Weight::zero(),
156					..Default::default()
157				};
158
159				// Adjust gas using actual weight or fallback to initially charged weight
160				let actual_weight = frame_support::dispatch::extract_actual_weight(&result, &pre);
161				env.adjust_gas(charged_amount, actual_weight);
162
163				result.map(|_| Vec::new()).map_err(|error| {
164					revert(
165							&error,
166							"XCM execute failed: message may be invalid or execution constraints not satisfied"
167						)
168				})
169			},
170			IXcmCalls::weighMessage(IXcm::weighMessageCall { message }) => {
171				// Charged before decoding; see `WeightInfo::weigh_message`.
172				env.charge(<Runtime as Config>::WeightInfo::weigh_message(message.len() as u32))?;
173
174				let converted_message = VersionedXcm::decode_all_with_mem_and_depth_limit(
175					&mut &message[..],
176				)
177				.map_err(|error| revert(&error, "XCM weightMessage: Invalid message format"))?;
178
179				ensure_xcm_version(&converted_message)?;
180
181				let mut final_message = converted_message.try_into().map_err(|error| {
182					revert(&error, "XCM weightMessage: Conversion to Xcm failed")
183				})?;
184
185				let weight = <<Runtime>::Weigher>::weight(&mut final_message, Weight::MAX)
186					.map_err(|error| {
187						revert(&error, "XCM weightMessage: Failed to calculate weight")
188					})?;
189
190				let final_weight =
191					IXcm::Weight { proofSize: weight.proof_size(), refTime: weight.ref_time() };
192
193				Ok(final_weight.abi_encode())
194			},
195		}
196	}
197}