pezpallet_xcm_precompiles/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
2// This file is part of Pezkuwi.
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 pezframe_support::dispatch::RawOrigin;
26use pezpallet_revive::{
27	precompiles::{
28		alloy::{self, sol_types::SolValue},
29		AddressMatcher, Error, Ext, Precompile,
30	},
31	DispatchInfo, ExecOrigin as Origin, Weight,
32};
33use pezpallet_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 + pezpallet_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(pezpallet_revive::Error::<Self::T>::StateChangeDenied.into()))
87			},
88			IXcmCalls::send(IXcm::sendCall { destination, message }) => {
89				let _ = env.charge(<Runtime as Config>::WeightInfo::send())?;
90
91				let final_destination = VersionedLocation::decode_all(&mut &destination[..])
92					.map_err(|error| {
93						revert(&error, "XCM send failed: Invalid destination format")
94					})?;
95
96				ensure_xcm_version(&final_destination)?;
97
98				let final_message = VersionedXcm::<()>::decode_all_with_depth_limit(
99					MAX_XCM_DECODE_DEPTH,
100					&mut &message[..],
101				)
102				.map_err(|error| revert(&error, "XCM send failed: Invalid message format"))?;
103
104				ensure_xcm_version(&final_message)?;
105
106				pezpallet_xcm::Pezpallet::<Runtime>::send(
107					frame_origin,
108					final_destination.into(),
109					final_message.into(),
110				)
111				.map(|_| Vec::new())
112				.map_err(|error| {
113					revert(
114						&error,
115						"XCM send failed: destination or message format may be incompatible",
116					)
117				})
118			},
119			IXcmCalls::execute(IXcm::executeCall { message, weight }) => {
120				let max_weight = Weight::from_parts(weight.refTime, weight.proofSize);
121				let weight_to_charge =
122					max_weight.saturating_add(<Runtime as Config>::WeightInfo::execute());
123				let charged_amount = env.charge(weight_to_charge)?;
124
125				let final_message = VersionedXcm::decode_all_with_depth_limit(
126					MAX_XCM_DECODE_DEPTH,
127					&mut &message[..],
128				)
129				.map_err(|error| revert(&error, "XCM execute failed: Invalid message format"))?;
130
131				ensure_xcm_version(&final_message)?;
132
133				let result = pezpallet_xcm::Pezpallet::<Runtime>::execute(
134					frame_origin,
135					final_message.into(),
136					max_weight,
137				);
138
139				let pre = DispatchInfo {
140					call_weight: weight_to_charge,
141					extension_weight: Weight::zero(),
142					..Default::default()
143				};
144
145				// Adjust gas using actual weight or fallback to initially charged weight
146				let actual_weight =
147					pezframe_support::dispatch::extract_actual_weight(&result, &pre);
148				env.adjust_gas(charged_amount, actual_weight);
149
150				result.map(|_| Vec::new()).map_err(|error| {
151					revert(
152							&error,
153							"XCM execute failed: message may be invalid or execution constraints not satisfied"
154						)
155				})
156			},
157			IXcmCalls::weighMessage(IXcm::weighMessageCall { message }) => {
158				let _ = env.charge(<Runtime as Config>::WeightInfo::weigh_message())?;
159
160				let converted_message = VersionedXcm::decode_all_with_depth_limit(
161					MAX_XCM_DECODE_DEPTH,
162					&mut &message[..],
163				)
164				.map_err(|error| revert(&error, "XCM weightMessage: Invalid message format"))?;
165
166				ensure_xcm_version(&converted_message)?;
167
168				let mut final_message = converted_message.try_into().map_err(|error| {
169					revert(&error, "XCM weightMessage: Conversion to Xcm failed")
170				})?;
171
172				let weight = <<Runtime>::Weigher>::weight(&mut final_message, Weight::MAX)
173					.map_err(|error| {
174						revert(&error, "XCM weightMessage: Failed to calculate weight")
175					})?;
176
177				let final_weight =
178					IXcm::Weight { proofSize: weight.proof_size(), refTime: weight.ref_time() };
179
180				Ok(final_weight.abi_encode())
181			},
182		}
183	}
184}