pallet-xcm: Deprecate execute and send in favor of execute_blob and send_blob (#3749)

`execute` and `send` try to decode the xcm in the parameters before
reaching the filter line.
The new extrinsics decode only after the filter line.
These should be used instead of the old ones.

## TODO
- [x] Tests
- [x] Generate weights
- [x] Deprecation issue ->
https://github.com/paritytech/polkadot-sdk/issues/3771
- [x] PRDoc
- [x] Handle error in pallet-contracts

This would make writing XCMs in PJS Apps more difficult, but here's the
fix for that: https://github.com/polkadot-js/apps/pull/10350.
Already deployed! https://polkadot.js.org/apps/#/utilities/xcm

Supersedes https://github.com/paritytech/polkadot-sdk/pull/1798/

---------

Co-authored-by: PG Herveou <pgherveou@gmail.com>
Co-authored-by: command-bot <>
Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
Francisco Aguirre
2024-03-27 09:31:01 +01:00
committed by GitHub
parent 66051adb61
commit feee773d15
36 changed files with 1133 additions and 642 deletions
@@ -16,6 +16,7 @@
use super::*;
use bounded_collections::{ConstU32, WeakBoundedVec};
use codec::Encode;
use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult};
use frame_support::{
traits::fungible::{Inspect, Mutate},
@@ -108,6 +109,21 @@ benchmarks! {
let versioned_msg = VersionedXcm::from(msg);
}: _<RuntimeOrigin<T>>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg))
send_blob {
let send_origin =
T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
}
let msg = Xcm::<()>(vec![ClearOrigin]);
let versioned_dest: VersionedLocation = T::reachable_dest().ok_or(
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
)?
.into();
let versioned_msg = VersionedXcm::from(msg);
let encoded_versioned_msg = versioned_msg.encode().try_into().unwrap();
}: _<RuntimeOrigin<T>>(send_origin, Box::new(versioned_dest), encoded_versioned_msg)
teleport_assets {
let (asset, destination) = T::teleportable_asset_and_dest().ok_or(
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
@@ -227,6 +243,19 @@ benchmarks! {
let versioned_msg = VersionedXcm::from(msg);
}: _<RuntimeOrigin<T>>(execute_origin, Box::new(versioned_msg), Weight::MAX)
execute_blob {
let execute_origin =
T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone())
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
let msg = Xcm(vec![ClearOrigin]);
if !T::XcmExecuteFilter::contains(&(origin_location, msg.clone())) {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
}
let versioned_msg = VersionedXcm::from(msg);
let encoded_versioned_msg = versioned_msg.encode().try_into().unwrap();
}: _<RuntimeOrigin<T>>(execute_origin, encoded_versioned_msg, Weight::MAX)
force_xcm_version {
let loc = T::reachable_dest().ok_or(
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
+148 -54
View File
@@ -50,8 +50,8 @@ use sp_runtime::{
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
use xcm::{latest::QueryResponseInfo, prelude::*};
use xcm_builder::{
ExecuteController, ExecuteControllerWeightInfo, QueryController, QueryControllerWeightInfo,
SendController, SendControllerWeightInfo,
ExecuteController, ExecuteControllerWeightInfo, MaxXcmEncodedSize, QueryController,
QueryControllerWeightInfo, SendController, SendControllerWeightInfo,
};
use xcm_executor::{
traits::{
@@ -87,6 +87,8 @@ pub trait WeightInfo {
fn new_query() -> Weight;
fn take_response() -> Weight;
fn claim_assets() -> Weight;
fn execute_blob() -> Weight;
fn send_blob() -> Weight;
}
/// fallback implementation
@@ -171,6 +173,14 @@ impl WeightInfo for TestWeightInfo {
fn claim_assets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn execute_blob() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn send_blob() -> Weight {
Weight::from_parts(100_000_000, 0)
}
}
#[frame_support::pallet]
@@ -286,76 +296,49 @@ pub mod pallet {
}
impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
fn execute() -> Weight {
T::WeightInfo::execute()
fn execute_blob() -> Weight {
T::WeightInfo::execute_blob()
}
}
impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
type WeightInfo = Self;
fn execute(
fn execute_blob(
origin: OriginFor<T>,
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
max_weight: Weight,
) -> Result<Weight, DispatchErrorWithPostInfo> {
log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight);
let outcome = (|| {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let value = (origin_location, message);
ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, message) = value;
Ok(T::XcmExecutor::prepare_and_execute(
origin_location,
message,
&mut hash,
max_weight,
max_weight,
))
})()
.map_err(|e: DispatchError| {
e.with_weight(<Self::WeightInfo as ExecuteControllerWeightInfo>::execute())
})?;
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
let weight_used = outcome.weight_used();
outcome.ensure_complete().map_err(|error| {
log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error);
Error::<T>::LocalExecutionIncomplete.with_weight(
weight_used.saturating_add(
<Self::WeightInfo as ExecuteControllerWeightInfo>::execute(),
),
)
})?;
Ok(weight_used)
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let message =
VersionedXcm::<<T as Config>::RuntimeCall>::decode(&mut &encoded_message[..])
.map_err(|error| {
log::error!(target: "xcm::execute_blob", "Unable to decode XCM, error: {:?}", error);
Error::<T>::UnableToDecode
})?;
Self::execute_base(origin_location, Box::new(message), max_weight)
}
}
impl<T: Config> SendControllerWeightInfo for Pallet<T> {
fn send() -> Weight {
T::WeightInfo::send()
fn send_blob() -> Weight {
T::WeightInfo::send_blob()
}
}
impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
type WeightInfo = Self;
fn send(
fn send_blob(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
message: Box<VersionedXcm<()>>,
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
) -> Result<XcmHash, DispatchError> {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
let interior: Junctions =
origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = Location::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let message_id = Self::send_xcm(interior, dest.clone(), message.clone())
.map_err(Error::<T>::from)?;
let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
Self::deposit_event(e);
Ok(message_id)
let message =
VersionedXcm::<()>::decode(&mut &encoded_message[..]).map_err(|error| {
log::error!(target: "xcm::send_blob", "Unable to decode XCM, error: {:?}", error);
Error::<T>::UnableToDecode
})?;
Self::send_base(origin_location, dest, Box::new(message))
}
}
@@ -562,6 +545,11 @@ pub mod pallet {
TooManyReserves,
/// Local XCM execution incomplete.
LocalExecutionIncomplete,
/// Could not decode XCM.
UnableToDecode,
/// XCM encoded length is too large.
/// Returned when an XCM encoded length is larger than `MaxXcmEncodedSize`.
XcmTooLarge,
}
impl<T: Config> From<SendError> for Error<T> {
@@ -899,8 +887,64 @@ pub mod pallet {
}
}
impl<T: Config> Pallet<T> {
/// Underlying logic for both [`execute_blob`] and [`execute`].
fn execute_base(
origin_location: Location,
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
max_weight: Weight,
) -> Result<Weight, DispatchErrorWithPostInfo> {
log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight);
let outcome = (|| {
let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let value = (origin_location, message);
ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, message) = value;
Ok(T::XcmExecutor::prepare_and_execute(
origin_location,
message,
&mut hash,
max_weight,
max_weight,
))
})()
.map_err(|e: DispatchError| e.with_weight(T::WeightInfo::execute()))?;
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
let weight_used = outcome.weight_used();
outcome.ensure_complete().map_err(|error| {
log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error);
Error::<T>::LocalExecutionIncomplete
.with_weight(weight_used.saturating_add(T::WeightInfo::execute()))
})?;
Ok(weight_used)
}
/// Underlying logic for both [`send_blob`] and [`send`].
fn send_base(
origin_location: Location,
dest: Box<VersionedLocation>,
message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError> {
let interior: Junctions =
origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = Location::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let message_id = Self::send_xcm(interior, dest.clone(), message.clone())
.map_err(Error::<T>::from)?;
let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
Self::deposit_event(e);
Ok(message_id)
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// WARNING: DEPRECATED. `send` will be removed after June 2024. Use `send_blob` instead.
#[allow(deprecated)]
#[deprecated(note = "`send` will be removed after June 2024. Use `send_blob` instead.")]
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::send())]
pub fn send(
@@ -908,7 +952,8 @@ pub mod pallet {
dest: Box<VersionedLocation>,
message: Box<VersionedXcm<()>>,
) -> DispatchResult {
<Self as SendController<_>>::send(origin, dest, message)?;
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Self::send_base(origin_location, dest, message)?;
Ok(())
}
@@ -1031,6 +1076,13 @@ pub mod pallet {
/// No more than `max_weight` will be used in its attempted execution. If this is less than
/// the maximum amount of weight that the message could take to be executed, then no
/// execution attempt will be made.
///
/// WARNING: DEPRECATED. `execute` will be removed after June 2024. Use `execute_blob`
/// instead.
#[allow(deprecated)]
#[deprecated(
note = "`execute` will be removed after June 2024. Use `execute_blob` instead."
)]
#[pallet::call_index(3)]
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
pub fn execute(
@@ -1038,8 +1090,8 @@ pub mod pallet {
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
max_weight: Weight,
) -> DispatchResultWithPostInfo {
let weight_used =
<Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let weight_used = Self::execute_base(origin_location, message, max_weight)?;
Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
}
@@ -1450,6 +1502,48 @@ pub mod pallet {
})?;
Ok(())
}
/// Execute an XCM from a local, signed, origin.
///
/// An event is deposited indicating whether the message could be executed completely
/// or only partially.
///
/// No more than `max_weight` will be used in its attempted execution. If this is less than
/// the maximum amount of weight that the message could take to be executed, then no
/// execution attempt will be made.
///
/// The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`].
#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::execute_blob())]
pub fn execute_blob(
origin: OriginFor<T>,
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
max_weight: Weight,
) -> DispatchResultWithPostInfo {
let weight_used = <Self as ExecuteController<_, _>>::execute_blob(
origin,
encoded_message,
max_weight,
)?;
Ok(Some(weight_used.saturating_add(T::WeightInfo::execute_blob())).into())
}
/// Send an XCM from a local, signed, origin.
///
/// The destination, `dest`, will receive this message with a `DescendOrigin` instruction
/// that makes the origin of the message be the origin on this system.
///
/// The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`].
#[pallet::call_index(14)]
#[pallet::weight(T::WeightInfo::send_blob())]
pub fn send_blob(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
) -> DispatchResult {
<Self as SendController<_>>::send_blob(origin, dest, encoded_message)?;
Ok(())
}
}
}
+47 -30
View File
@@ -20,10 +20,10 @@ pub(crate) mod assets_transfer;
use crate::{
mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error,
ExecuteControllerWeightInfo, LatestVersionedLocation, Pallet, Queries, QueryStatus,
VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets,
WeightInfo,
LatestVersionedLocation, Pallet, Queries, QueryStatus, VersionDiscoveryQueue,
VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, WeightInfo,
};
use codec::Encode;
use frame_support::{
assert_err_ignore_postinfo, assert_noop, assert_ok,
traits::{Currency, Hooks},
@@ -305,11 +305,12 @@ fn send_works() {
]);
let versioned_dest = Box::new(RelayLocation::get().into());
let versioned_message = Box::new(VersionedXcm::from(message.clone()));
assert_ok!(XcmPallet::send(
let versioned_message = VersionedXcm::from(message.clone());
let encoded_versioned_message = versioned_message.encode().try_into().unwrap();
assert_ok!(XcmPallet::send_blob(
RuntimeOrigin::signed(ALICE),
versioned_dest,
versioned_message
encoded_versioned_message
));
let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap()))
.into_iter()
@@ -341,16 +342,16 @@ fn send_fails_when_xcm_router_blocks() {
];
new_test_ext_with_balances(balances).execute_with(|| {
let sender: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
let message = Xcm(vec![
let message = Xcm::<()>(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender },
]);
assert_noop!(
XcmPallet::send(
XcmPallet::send_blob(
RuntimeOrigin::signed(ALICE),
Box::new(Location::ancestor(8).into()),
Box::new(VersionedXcm::from(message.clone())),
VersionedXcm::from(message.clone()).encode().try_into().unwrap(),
),
crate::Error::<Test>::SendFailure
);
@@ -371,13 +372,16 @@ fn execute_withdraw_to_deposit_works() {
let weight = BaseXcmWeight::get() * 3;
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
assert_ok!(XcmPallet::execute(
assert_ok!(XcmPallet::execute_blob(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
VersionedXcm::from(Xcm::<RuntimeCall>(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
]))),
]))
.encode()
.try_into()
.unwrap(),
weight
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
@@ -399,18 +403,21 @@ fn trapped_assets_can_be_claimed() {
let weight = BaseXcmWeight::get() * 6;
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_ok!(XcmPallet::execute(
assert_ok!(XcmPallet::execute_blob(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
VersionedXcm::from(Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
// Don't propagated the error into the result.
SetErrorHandler(Xcm(vec![ClearError])),
SetErrorHandler(Xcm::<RuntimeCall>(vec![ClearError])),
// This will make an error.
Trap(0),
// This would succeed, but we never get to it.
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() },
]))),
]))
.encode()
.try_into()
.unwrap(),
weight
));
let source: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
@@ -437,13 +444,16 @@ fn trapped_assets_can_be_claimed() {
assert_eq!(trapped, expected);
let weight = BaseXcmWeight::get() * 3;
assert_ok!(XcmPallet::execute(
assert_ok!(XcmPallet::execute_blob(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
VersionedXcm::from(Xcm::<RuntimeCall>(vec![
ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() },
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() },
]))),
]))
.encode()
.try_into()
.unwrap(),
weight
));
@@ -453,13 +463,16 @@ fn trapped_assets_can_be_claimed() {
// Can't claim twice.
assert_err_ignore_postinfo!(
XcmPallet::execute(
XcmPallet::execute_blob(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
VersionedXcm::from(Xcm::<RuntimeCall>(vec![
ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() },
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
]))),
]))
.encode()
.try_into()
.unwrap(),
weight
),
Error::<Test>::LocalExecutionIncomplete
@@ -473,12 +486,13 @@ fn claim_assets_works() {
let balances = vec![(ALICE, INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
// First trap some assets.
let trapping_program =
Xcm::builder_unsafe().withdraw_asset((Here, SEND_AMOUNT).into()).build();
let trapping_program = Xcm::<RuntimeCall>::builder_unsafe()
.withdraw_asset((Here, SEND_AMOUNT).into())
.build();
// Even though assets are trapped, the extrinsic returns success.
assert_ok!(XcmPallet::execute(
assert_ok!(XcmPallet::execute_blob(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::V4(trapping_program)),
VersionedXcm::V4(trapping_program).encode().try_into().unwrap(),
BaseXcmWeight::get() * 2,
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
@@ -531,9 +545,9 @@ fn incomplete_execute_reverts_side_effects() {
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
let amount_to_send = INITIAL_BALANCE - ExistentialDeposit::get();
let assets: Assets = (Here, amount_to_send).into();
let result = XcmPallet::execute(
let result = XcmPallet::execute_blob(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
VersionedXcm::from(Xcm::<RuntimeCall>(vec![
// Withdraw + BuyExec + Deposit should work
WithdrawAsset(assets.clone()),
buy_execution(assets.inner()[0].clone()),
@@ -541,7 +555,10 @@ fn incomplete_execute_reverts_side_effects() {
// Withdrawing once more will fail because of InsufficientBalance, and we expect to
// revert the effects of the above instructions as well
WithdrawAsset(assets),
]))),
]))
.encode()
.try_into()
.unwrap(),
weight,
);
// all effects are reverted and balances unchanged for either sender or receiver
@@ -553,7 +570,7 @@ fn incomplete_execute_reverts_side_effects() {
Err(sp_runtime::DispatchErrorWithPostInfo {
post_info: frame_support::dispatch::PostDispatchInfo {
actual_weight: Some(
<Pallet<Test> as ExecuteControllerWeightInfo>::execute() + weight
<<Test as crate::Config>::WeightInfo>::execute_blob() + weight
),
pays_fee: frame_support::dispatch::Pays::Yes,
},