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