diff --git a/Cargo.lock b/Cargo.lock index f8c62061fb23d..32e6b0d150cb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12740,6 +12740,7 @@ dependencies = [ "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", + "staging-xcm-executor", "substrate-bn", "subxt-signer 0.38.0", ] @@ -12797,6 +12798,7 @@ dependencies = [ name = "pallet-revive-mock-network" version = "0.1.0" dependencies = [ + "alloy-core", "frame-support", "frame-system", "pallet-assets", diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 7c88a0a5c41db..5474888c11fb2 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -36,7 +36,10 @@ rand = { workspace = true, optional = true } rand_pcg = { workspace = true, optional = true } rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } -serde = { features = ["alloc", "derive"], workspace = true, default-features = false } +serde = { features = [ + "alloc", + "derive", +], workspace = true, default-features = false } # Polkadot SDK Dependencies bn = { workspace = true } @@ -56,9 +59,12 @@ sp-consensus-slots = { workspace = true, optional = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -subxt-signer = { workspace = true, optional = true, features = ["unstable-eth"] } +subxt-signer = { workspace = true, optional = true, features = [ + "unstable-eth", +] } xcm = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } @@ -77,6 +83,7 @@ pallet-utility = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true } [features] default = ["std"] @@ -118,6 +125,7 @@ std = [ "sp-runtime/std", "subxt-signer", "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] runtime-benchmarks = [ diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml index becce626e7298..833fa2f77385b 100644 --- a/substrate/frame/revive/mock-network/Cargo.toml +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -35,6 +35,7 @@ xcm = { workspace = true } xcm-builder = { workspace = true, default-features = true } xcm-executor = { workspace = true } xcm-simulator = { workspace = true, default-features = true } +alloy-core = { workspace = true, features = ["sol-types"] } [dev-dependencies] pallet-revive-fixtures = { workspace = true } @@ -42,6 +43,7 @@ pallet-revive-fixtures = { workspace = true } [features] default = ["std"] std = [ + "alloy-core/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs index c2ddb8fb750fe..d0ec1b98bcdf1 100644 --- a/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs +++ b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs @@ -15,8 +15,15 @@ // along with Substrate. If not, see . use super::{Balances, Runtime, RuntimeCall, RuntimeEvent}; -use crate::parachain::RuntimeHoldReason; +use crate::parachain::{parameter_types, MaxInstructions, RuntimeHoldReason}; use frame_support::derive_impl; +use pallet_revive::precompiles::XcmPrecompile; +use xcm::latest::Weight; +use xcm_builder::FixedWeightBounds; + +parameter_types! { + pub const BaseXcmWeight: xcm::latest::Weight = Weight::from_parts(100_000_000, 2_000); +} #[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] impl pallet_revive::Config for Runtime { @@ -24,4 +31,6 @@ impl pallet_revive::Config for Runtime { type Currency = Balances; type Time = super::Timestamp; type Xcm = pallet_xcm::Pallet; + type Precompiles = (XcmPrecompile,); + type XcmWeigher = FixedWeightBounds; } diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs index 34f797c2b530f..a78c87983e3bd 100644 --- a/substrate/frame/revive/mock-network/src/tests.rs +++ b/substrate/frame/revive/mock-network/src/tests.rs @@ -19,21 +19,22 @@ use crate::{ parachain, parachain_account_sovereign_account_id, primitives::CENTS, relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, }; +use alloy_core::sol_types::{SolInterface, SolValue}; use codec::{Decode, Encode}; use frame_support::traits::{fungibles::Mutate, Currency}; use frame_system::RawOrigin; use pallet_revive::{ test_utils::{self, builder::*}, - Code, DepositLimit, + Code, DepositLimit, ExecReturnValue, IXcm, }; use pallet_revive_fixtures::compile_module; -use pallet_revive_uapi::ReturnErrorCode; +use pallet_revive_uapi::{ReturnErrorCode, ReturnFlags}; use sp_core::H160; use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; use xcm_simulator::TestExt; macro_rules! assert_return_code { - ( $x:expr , $y:expr $(,)? ) => {{ + ($x:expr, $y:expr $(,)?) => {{ assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); }}; } @@ -61,6 +62,7 @@ fn instantiate_test_contract(name: &str) -> Contract { parachain::Balances::make_free_balance_be(&contract.account_id, INITIAL_BALANCE); parachain::Assets::mint_into(0u32.into(), &contract.account_id, INITIAL_BALANCE).unwrap(); }); + Relay::execute_with(|| { let sovereign_account = parachain_account_sovereign_account_id(1u32, contract.account_id.clone()); @@ -70,6 +72,14 @@ fn instantiate_test_contract(name: &str) -> Contract { contract } +fn to_fixed_non_zero(precompile_id: u16) -> H160 { + let mut address = [0u8; 20]; + address[16] = (precompile_id >> 8) as u8; + address[17] = (precompile_id & 0xFF) as u8; + + H160::from(address) +} + #[test] fn test_xcm_execute() { MockNet::reset(); @@ -148,7 +158,6 @@ fn test_xcm_execute_reentrant_call() { value: 0u128, }); - // The XCM used to transfer funds to Bob. let message: Xcm = Xcm::builder_unsafe() .transact(OriginKind::Native, 1_000_000_000, transact_call.encode()) .expect_transact_status(MaybeErrorCode::Success) @@ -202,3 +211,271 @@ fn test_xcm_send() { assert_eq!(INITIAL_BALANCE + amount - fee, relay_chain::Balances::free_balance(ALICE)); }); } + +#[test] +fn test_xcm_execute_reentrant_call_via_precompile() { + MockNet::reset(); + + ParaA::execute_with(|| { + let initial_bob_balance = ParachainBalances::free_balance(BOB); + + let transact_call = parachain::RuntimeCall::Contracts(pallet_revive::Call::call { + dest: to_fixed_non_zero(10), + gas_limit: 1_000_000.into(), + storage_deposit_limit: test_utils::deposit_limit::(), + data: vec![], + value: 0u128, + }); + + let message: Xcm = Xcm::builder_unsafe() + .transact(OriginKind::Native, 1_000_000_000, transact_call.encode()) + .expect_transact_status(MaybeErrorCode::Success) + .build(); + + let weight_params = + IXcm::weighMessageCall { message: VersionedXcm::V4(message.clone()).encode().into() }; + let weight_call = IXcm::IXcmCalls::weighMessage(weight_params); + let xcm_weight_results = + bare_call(to_fixed_non_zero(10)).data(weight_call.abi_encode()).build(); + + let weight_result = match xcm_weight_results.result { + Ok(value) => value, + Err(_) => ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }, + }; + + let weight: IXcm::Weight = + IXcm::Weight::abi_decode(&weight_result.data[..], true).expect("Failed to weight"); + + let xcm_execute_params = + IXcm::xcmExecuteCall { message: VersionedXcm::V4(message).encode().into(), weight }; + + let call = IXcm::IXcmCalls::xcmExecute(xcm_execute_params); + let encoded_call = call.abi_encode(); + let results = bare_call(to_fixed_non_zero(10)).data(encoded_call).build(); + let result = match results.result { + Ok(value) => value, + Err(_) => ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }, + }; + + let final_bob_balance = ParachainBalances::free_balance(BOB); + + assert_eq!( + result.flags, + ReturnFlags::REVERT, + "Expected transaction to revert due to reentrant call" + ); + assert_eq!(final_bob_balance, initial_bob_balance, "Bob's balance should remain unchanged"); + }); +} + +#[test] +fn test_xcm_execute_incomplete_call_via_precompile() { + MockNet::reset(); + let amount = 10 * CENTS; + + ParaA::execute_with(|| { + let initial_bob_balance = ParachainBalances::free_balance(BOB); + let initial_alice_balance = ParachainBalances::free_balance(ALICE); + + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; + + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) + // This will fail as the contract does not have enough balance to complete both + // withdrawals. + .withdraw_asset((Here, INITIAL_BALANCE)) + .buy_execution(assets.clone(), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + // First, calculate the weight of the XCM message + let weight_params = + IXcm::weighMessageCall { message: VersionedXcm::V4(message.clone()).encode().into() }; + let weight_call = IXcm::IXcmCalls::weighMessage(weight_params); + let xcm_weight_results = + bare_call(to_fixed_non_zero(10)).data(weight_call.abi_encode()).build(); + + let weight_result = match xcm_weight_results.result { + Ok(value) => value, + Err(_) => ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }, + }; + + let weight: IXcm::Weight = + IXcm::Weight::abi_decode(&weight_result.data[..], true).expect("Failed to weight"); + + let xcm_execute_params = + IXcm::xcmExecuteCall { message: VersionedXcm::V4(message).encode().into(), weight }; + + let call = IXcm::IXcmCalls::xcmExecute(xcm_execute_params); + let encoded_call = call.abi_encode(); + bare_call(to_fixed_non_zero(10)).data(encoded_call).build(); + + let final_bob_balance = ParachainBalances::free_balance(BOB); + let final_alice_balance = ParachainBalances::free_balance(ALICE); + + assert_eq!(final_bob_balance, initial_bob_balance, "Bob's balance should remain unchanged"); + assert_eq!( + final_alice_balance, initial_alice_balance, + "Alice's balance should remain unchanged" + ); + }); +} + +#[test] +fn test_xcm_execute_precompile() { + MockNet::reset(); + let amount: u128 = 10 * CENTS; + + ParaA::execute_with(|| { + let initial_alice_balance = ParachainBalances::free_balance(ALICE); + let initial_bob_balance = ParachainBalances::free_balance(BOB); + + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; + + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) + .deposit_asset(assets, beneficiary) + .build(); + + let weight_params = + IXcm::weighMessageCall { message: VersionedXcm::V4(message.clone()).encode().into() }; + let weight_call = IXcm::IXcmCalls::weighMessage(weight_params); + let xcm_weight_results = + bare_call(to_fixed_non_zero(10)).data(weight_call.abi_encode()).build(); + + let weight_result = match xcm_weight_results.result { + Ok(value) => value, + Err(_) => ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }, + }; + + let weight: IXcm::Weight = + IXcm::Weight::abi_decode(&weight_result.data[..], true).expect("Failed to weight"); + + let xcm_execute_params = + IXcm::xcmExecuteCall { message: VersionedXcm::V4(message).encode().into(), weight }; + + let call = IXcm::IXcmCalls::xcmExecute(xcm_execute_params); + let encoded_call = call.abi_encode(); + + bare_call(to_fixed_non_zero(10)).data(encoded_call).build(); + + let final_alice_balance = ParachainBalances::free_balance(ALICE); + let final_bob_balance = ParachainBalances::free_balance(BOB); + + assert_eq!( + final_bob_balance, + initial_bob_balance + amount, + "Bob's balance should increase by the specified amount" + ); + assert_eq!( + final_alice_balance, + initial_alice_balance - amount, + "Alice's balance should decrease by the specified amount" + ); + }); +} + +#[test] +fn test_xcm_send_precompile() { + MockNet::reset(); + let amount = 1_000 * CENTS; + let fee: u128 = parachain::estimate_message_fee(4); + + let sovereign_account_id = ParaA::execute_with(|| { + let sovereign_account_id = parachain_account_sovereign_account_id(1, ALICE.clone()); + let initial_sovereign_balance = + Relay::execute_with(|| relay_chain::Balances::free_balance(&sovereign_account_id)); + + let initial_alice_relay_balance = + Relay::execute_with(|| relay_chain::Balances::free_balance(ALICE)); + + let dest = VersionedLocation::V4(Parent.into()); + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: ALICE.clone().into() }; + + let message: Xcm<()> = Xcm::builder() + .withdraw_asset(assets.clone()) + .buy_execution((Here, fee), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let xcm_send_params = IXcm::xcmSendCall { + destination: dest.encode().into(), + message: VersionedXcm::V4(message).encode().into(), + }; + + let call = IXcm::IXcmCalls::xcmSend(xcm_send_params); + let encoded_call = call.abi_encode(); + let results = bare_call(to_fixed_non_zero(10)).data(encoded_call).build(); + let result = results.result.expect("Transaction should succeed"); + let mut data = &result.data[..]; + XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id"); + + (sovereign_account_id, initial_sovereign_balance, initial_alice_relay_balance) + }); + + Relay::execute_with(|| { + let (sovereign_account_id, initial_sovereign_balance, initial_alice_relay_balance) = + sovereign_account_id; + + let final_sovereign_balance = relay_chain::Balances::free_balance(&sovereign_account_id); + assert_eq!( + final_sovereign_balance, + initial_sovereign_balance - amount, + "Sovereign account balance should decrease by the amount sent" + ); + + let final_alice_balance = relay_chain::Balances::free_balance(ALICE); + assert_eq!( + final_alice_balance, + initial_alice_relay_balance + amount - fee, + "Alice's balance should increase by amount minus fee" + ); + }); +} + +#[test] +fn test_xcm_send_precompile_via_fixture() { + MockNet::reset(); + let amount = 1_000 * CENTS; + let fee: u128 = parachain::estimate_message_fee(4); + let Contract { addr, .. } = instantiate_test_contract("call_and_return"); + + ParaA::execute_with(|| { + let dest = VersionedLocation::V4(Parent.into()); + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: ALICE.clone().into() }; + + let message: Xcm<()> = Xcm::builder() + .withdraw_asset(assets.clone()) + .buy_execution((Here, fee), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let xcm_send_params = IXcm::xcmSendCall { + destination: dest.encode().into(), + message: VersionedXcm::V4(message).encode().into(), + }; + + let call = IXcm::IXcmCalls::xcmSend(xcm_send_params); + let encoded_call = call.abi_encode(); + let result = bare_call(addr) + .data( + (to_fixed_non_zero(10), 5000u64) + .encode() + .into_iter() + .chain(encoded_call) + .collect::>(), + ) + .build_and_unwrap_result(); + + let mut data = &result.data[..]; + XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id"); + }); + + Relay::execute_with(|| { + assert_eq!(INITIAL_BALANCE + amount - fee, relay_chain::Balances::free_balance(ALICE)); + }); +} diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 3ffb88c6b8a87..c6a11c707d5ea 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,6 +41,7 @@ pub mod precompiles; pub mod test_utils; pub mod tracing; pub mod weights; +pub use precompiles::{IXcm, XcmPrecompile}; use crate::{ evm::{ @@ -267,6 +268,12 @@ pub mod pallet { BlockNumberFor, >; + /// The weigher used to calculate XCM execution costs + #[pallet::no_default_bounds] + type XcmWeigher: xcm_executor::traits::WeightBounds< + ::RuntimeCall, + >; + /// The amount of memory in bytes that parachain nodes a lot to the runtime. /// /// This is used in [`Pallet::integrity_test`] to make sure that the runtime has enough @@ -355,6 +362,7 @@ pub mod pallet { type RuntimeCall = (); type CallFilter = (); type Precompiles = (); + type XcmWeigher = (); type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; diff --git a/substrate/frame/revive/src/precompiles.rs b/substrate/frame/revive/src/precompiles.rs index c12527cee6646..d23986fd45f3f 100644 --- a/substrate/frame/revive/src/precompiles.rs +++ b/substrate/frame/revive/src/precompiles.rs @@ -25,7 +25,9 @@ //! //! Use `alloy` through our re-export in this module to implement Eth ABI. -mod builtin; +pub mod builtin; +mod xcm; +pub use xcm::{IXcm, XcmPrecompile}; mod tests; diff --git a/substrate/frame/revive/src/precompiles/xcm.rs b/substrate/frame/revive/src/precompiles/xcm.rs new file mode 100644 index 0000000000000..7e09eacb29c99 --- /dev/null +++ b/substrate/frame/revive/src/precompiles/xcm.rs @@ -0,0 +1,169 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + precompiles::{AddressMatcher, Error, Ext, ExtWithInfo, Precompile}, + Config, Origin, RuntimeCosts, +}; +use alloc::vec::Vec; +use alloy_core::{sol, sol_types::SolValue}; +use codec::{DecodeAll, Encode}; +use core::{marker::PhantomData, num::NonZero}; +use log::error; +use sp_runtime::Weight; +use xcm_builder::{ExecuteController, SendController, SendControllerWeightInfo}; +use xcm_executor::traits::WeightBounds; +pub use IXcm::IXcmCalls; + +pub struct XcmPrecompile(PhantomData); + +sol! { + /// @title Defines all functions that can be used to interact with XCM + /// @author Tiago Bandeira + /// @dev Parameters MUST use SCALE codec serialisation + interface IXcm { + struct Weight { + uint64 refTime; + uint64 proofSize; + } + + /// @notice Execute an XCM message locally with the caller's origin + /// @param message The XCM message to send + /// @param weight The maximum amount of weight to be used to execute the message + function xcmExecute(bytes calldata message, Weight calldata weight) external; + + /// @notice Send an XCM message to a destination chain + /// @param destination The destination location, encoded according to the XCM format + /// @param message The XCM message to send + function xcmSend(bytes calldata destination, bytes calldata message) external; + + /// @notice Given a message estimate the weight cost + /// @param message The XCM message to send + /// @returns weight estimated for sending the message + function weighMessage(bytes calldata message) external view returns(Weight weight); + } +} + +impl Precompile for XcmPrecompile { + type T = T; + const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(10).unwrap()); + const HAS_CONTRACT_INFO: bool = false; + type Interface = IXcm::IXcmCalls; + + fn call( + _address: &[u8; 20], + input: &Self::Interface, + env: &mut impl Ext, + ) -> Result, Error> { + let origin = env.caller(); + let frame_origin = match origin { + Origin::Root => frame_system::RawOrigin::Root.into(), + Origin::Signed(account_id) => + frame_system::RawOrigin::Signed(account_id.clone()).into(), + }; + + match input { + IXcmCalls::xcmSend(IXcm::xcmSendCall { destination, message }) => { + let final_destination = xcm::VersionedLocation::decode_all(&mut &destination[..]) + .map_err(|e| { + error!("XCM send failed: Invalid destination format. Error: {e:?}"); + Error::Revert("Invalid destination format".into()) + })?; + + let final_message = xcm::VersionedXcm::<()>::decode_all(&mut &message[..]) + .map_err(|e| { + error!("XCM send failed: Invalid message format. Error: {e:?}"); + Error::Revert("Invalid message format".into()) + })?; + + let weight = <::Xcm as SendController<_>>::WeightInfo::send(); + env.gas_meter_mut().charge(RuntimeCosts::CallRuntime(weight))?; + + <::Xcm>::send( + frame_origin, + final_destination.into(), + final_message.into(), + ) + .map(|message_id| message_id.encode()) + .map_err(|e| { + error!( + "XCM send failed: destination or message format may be incompatible. \ + Error: {e:?}" + ); + Error::Revert( + "XCM send failed: destination or message format may be incompatible".into(), + ) + }) + }, + IXcmCalls::xcmExecute(IXcm::xcmExecuteCall { message, weight }) => { + let final_message = + xcm::VersionedXcm::decode_all(&mut &message[..]).map_err(|e| { + error!("XCM execute failed: Invalid message format. Error: {e:?}"); + Error::Revert("Invalid message format".into()) + })?; + + let weight = Weight::from_parts(weight.refTime, weight.proofSize); + env.gas_meter_mut().charge(RuntimeCosts::CallXcmExecute(weight.clone()))?; + + <::Xcm>::execute(frame_origin, final_message.into(), weight) + .map(|results| results.encode()) + .map_err(|e| { + error!( + "XCM execute failed: message may be invalid or execution \ + constraints not satisfied. Error: {e:?}" + ); + Error::Revert( + "XCM execute failed: message may be invalid or execution \ + constraints not satisfied" + .into(), + ) + }) + }, + IXcmCalls::weighMessage(IXcm::weighMessageCall { message }) => { + let converted_message = + xcm::VersionedXcm::decode_all(&mut &message[..]).map_err(|error| { + error!("XCM weighMessage: Invalid message format. Error: {error:?}"); + Error::Revert("XCM weighMessage: Invalid message format".into()) + })?; + + let mut final_message = converted_message.try_into().map_err(|e| { + error!("XCM weighMessage: Conversion to Xcm failed with Error: {e:?}"); + Error::Revert("XCM weighMessage: Conversion to Xcm failed".into()) + })?; + + let weight = + <::XcmWeigher>::weight(&mut final_message).map_err(|e| { + error!("XCM weighMessage: Failed to calculate weight. Error: {e:?}"); + Error::Revert("XCM weighMessage: Failed to calculate weight".into()) + })?; + + let final_weight = + IXcm::Weight { proofSize: weight.proof_size(), refTime: weight.ref_time() }; + + Ok(final_weight.abi_encode()) + }, + } + } + + fn call_with_info( + _address: &[u8; 20], + _input: &Self::Interface, + _env: &mut impl ExtWithInfo, + ) -> Result, Error> { + Err(Error::Revert("call_with_info not implemented for XcmPrecompile".into())) + } +}