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, DecodeLimit};
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, MAX_XCM_DECODE_DEPTH};
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				let _ = env.charge(<Runtime as Config>::WeightInfo::send())?;
95
96				let final_destination = VersionedLocation::decode_all(&mut &destination[..])
97					.map_err(|error| {
98						revert(&error, "XCM send failed: Invalid destination format")
99					})?;
100
101				ensure_xcm_version(&final_destination)?;
102
103				let final_message = VersionedXcm::<()>::decode_all_with_depth_limit(
104					MAX_XCM_DECODE_DEPTH,
105					&mut &message[..],
106				)
107				.map_err(|error| revert(&error, "XCM send failed: Invalid message format"))?;
108
109				ensure_xcm_version(&final_message)?;
110
111				pallet_xcm::Pallet::<Runtime>::send(
112					frame_origin,
113					final_destination.into(),
114					final_message.into(),
115				)
116				.map(|_| Vec::new())
117				.map_err(|error| {
118					revert(
119						&error,
120						"XCM send failed: destination or message format may be incompatible",
121					)
122				})
123			},
124			IXcmCalls::execute(IXcm::executeCall { message, weight }) => {
125				let max_weight = Weight::from_parts(weight.refTime, weight.proofSize);
126				let weight_to_charge =
127					max_weight.saturating_add(<Runtime as Config>::WeightInfo::execute());
128				let charged_amount = env.charge(weight_to_charge)?;
129
130				let final_message = VersionedXcm::decode_all_with_depth_limit(
131					MAX_XCM_DECODE_DEPTH,
132					&mut &message[..],
133				)
134				.map_err(|error| revert(&error, "XCM execute failed: Invalid message format"))?;
135
136				ensure_xcm_version(&final_message)?;
137
138				let result = pallet_xcm::Pallet::<Runtime>::execute(
139					frame_origin,
140					final_message.into(),
141					max_weight,
142				);
143
144				let pre = DispatchInfo {
145					call_weight: weight_to_charge,
146					extension_weight: Weight::zero(),
147					..Default::default()
148				};
149
150				// Adjust gas using actual weight or fallback to initially charged weight
151				let actual_weight = frame_support::dispatch::extract_actual_weight(&result, &pre);
152				env.adjust_gas(charged_amount, actual_weight);
153
154				result.map(|_| Vec::new()).map_err(|error| {
155					revert(
156							&error,
157							"XCM execute failed: message may be invalid or execution constraints not satisfied"
158						)
159				})
160			},
161			IXcmCalls::weighMessage(IXcm::weighMessageCall { message }) => {
162				let _ = env.charge(<Runtime as Config>::WeightInfo::weigh_message())?;
163
164				let converted_message = VersionedXcm::decode_all_with_depth_limit(
165					MAX_XCM_DECODE_DEPTH,
166					&mut &message[..],
167				)
168				.map_err(|error| revert(&error, "XCM weightMessage: Invalid message format"))?;
169
170				ensure_xcm_version(&converted_message)?;
171
172				let mut final_message = converted_message.try_into().map_err(|error| {
173					revert(&error, "XCM weightMessage: Conversion to Xcm failed")
174				})?;
175
176				let weight = <<Runtime>::Weigher>::weight(&mut final_message, Weight::MAX)
177					.map_err(|error| {
178						revert(&error, "XCM weightMessage: Failed to calculate weight")
179					})?;
180
181				let final_weight =
182					IXcm::Weight { proofSize: weight.proof_size(), refTime: weight.ref_time() };
183
184				Ok(final_weight.abi_encode())
185			},
186		}
187	}
188}