Skip to content

Commit

Permalink
Contracts: Add XCM traits to interface with contracts (#2086)
Browse files Browse the repository at this point in the history
We are introducing a new set of `XcmController` traits (final name yet
to be determined).
These traits are implemented by `pallet-xcm` and allows other pallets,
such as `pallet_contracts`, to rely on these traits instead of tight
coupling them to `pallet-xcm`.

Using only the existing Xcm traits would mean duplicating the logic from
`pallet-xcm` in these other pallets, which we aim to avoid. Our
objective is to ensure that when these APIs are called from
`pallet-contracts`, they produce the exact same outcomes as if called
directly from `pallet-xcm`.

The other benefits is that we can also expose return values to
`pallet-contracts` instead of just calling `pallet-xcm` dispatchable and
getting a `DispatchResult` back.

See traits integration in this PR
paritytech/polkadot-sdk#1248, where the traits
are used as follow to define and implement `pallet-contracts` Config.
```rs
// Contracts config:
pub trait Config: frame_system::Config {
  // ...

  /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and
  /// execute XCM programs.
  type Xcm: xcm_executor::traits::Controller<
	  OriginFor<Self>,
	  <Self as frame_system::Config>::RuntimeCall,
	  BlockNumberFor<Self>,
  >;
}

// implementation
impl pallet_contracts::Config for Runtime {
        // ...

	type Xcm = pallet_xcm::Pallet<Self>;
}
```

---------

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: command-bot <>
  • Loading branch information
pgherveou and athei authored Nov 10, 2023
1 parent 206c597 commit 47226e6
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 46 deletions.
3 changes: 2 additions & 1 deletion pallet-xcm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ sp-std = { path = "../../../substrate/primitives/std", default-features = false}

xcm = { package = "staging-xcm", path = "..", default-features = false }
xcm-executor = { package = "staging-xcm-executor", path = "../xcm-executor", default-features = false }
xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder", default-features = false }

[dev-dependencies]
pallet-balances = { path = "../../../substrate/frame/balances" }
polkadot-runtime-parachains = { path = "../../runtime/parachains" }
polkadot-parachain-primitives = { path = "../../parachain" }
xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder" }

[features]
default = [ "std" ]
Expand All @@ -45,6 +45,7 @@ std = [
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
]
Expand Down
27 changes: 27 additions & 0 deletions pallet-xcm/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,33 @@ benchmarks! {
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
}

new_query {
let responder = MultiLocation::from(Parent);
let timeout = 1u32.into();
let match_querier = MultiLocation::from(Here);
}: {
Pallet::<T>::new_query(responder, timeout, match_querier);
}

take_response {
let responder = MultiLocation::from(Parent);
let timeout = 1u32.into();
let match_querier = MultiLocation::from(Here);
let query_id = Pallet::<T>::new_query(responder, timeout, match_querier);
let infos = (0 .. xcm::v3::MaxPalletsInfo::get()).map(|_| PalletInfo::new(
u32::MAX,
(0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::<Vec<_>>().try_into().unwrap(),
(0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::<Vec<_>>().try_into().unwrap(),
u32::MAX,
u32::MAX,
u32::MAX,
).unwrap()).collect::<Vec<_>>();
Pallet::<T>::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap()));

}: {
<Pallet::<T> as QueryHandler>::take_response(query_id);
}

impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext_with_balances(Vec::new()),
Expand Down
157 changes: 118 additions & 39 deletions pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,17 @@ mod tests;
pub mod migration;

use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use frame_support::traits::{
Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, OriginTrait,
use frame_support::{
dispatch::GetDispatchInfo,
pallet_prelude::*,
traits::{
Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency,
OriginTrait, WithdrawReasons,
},
PalletId,
};
use frame_system::pallet_prelude::{BlockNumberFor, *};
pub use pallet::*;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
Expand All @@ -41,17 +49,15 @@ use sp_runtime::{
};
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
use xcm::{latest::QueryResponseInfo, prelude::*};
use xcm_executor::traits::{ConvertOrigin, Properties};

use frame_support::{
dispatch::GetDispatchInfo, pallet_prelude::*, traits::WithdrawReasons, PalletId,
use xcm_builder::{
ExecuteController, ExecuteControllerWeightInfo, QueryController, QueryControllerWeightInfo,
SendController, SendControllerWeightInfo,
};
use frame_system::pallet_prelude::*;
pub use pallet::*;
use xcm_executor::{
traits::{
CheckSuspension, ClaimAssets, ConvertLocation, DropAssets, MatchesFungible, OnResponse,
QueryHandler, QueryResponseStatus, VersionChangeNotifier, WeightBounds,
CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin, DropAssets, MatchesFungible,
OnResponse, Properties, QueryHandler, QueryResponseStatus, VersionChangeNotifier,
WeightBounds,
},
Assets,
};
Expand All @@ -73,6 +79,8 @@ pub trait WeightInfo {
fn notify_target_migration_fail() -> Weight;
fn migrate_version_notify_targets() -> Weight;
fn migrate_and_notify_old_targets() -> Weight;
fn new_query() -> Weight;
fn take_response() -> Weight;
}

/// fallback implementation
Expand Down Expand Up @@ -141,6 +149,14 @@ impl WeightInfo for TestWeightInfo {
fn migrate_and_notify_old_targets() -> Weight {
Weight::from_parts(100_000_000, 0)
}

fn new_query() -> Weight {
Weight::from_parts(100_000_000, 0)
}

fn take_response() -> Weight {
Weight::from_parts(100_000_000, 0)
}
}

#[frame_support::pallet]
Expand Down Expand Up @@ -267,6 +283,93 @@ pub mod pallet {
type ReachableDest: Get<Option<MultiLocation>>;
}

impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
fn execute() -> Weight {
T::WeightInfo::execute()
}
}

impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
type WeightInfo = Self;
fn execute(
origin: OriginFor<T>,
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
max_weight: Weight,
) -> Result<Outcome, DispatchError> {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let 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;
let outcome = T::XcmExecutor::execute_xcm_in_credit(
origin_location,
message,
hash,
max_weight,
max_weight,
);
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
Ok(outcome)
}
}

impl<T: Config> SendControllerWeightInfo for Pallet<T> {
fn send() -> Weight {
T::WeightInfo::send()
}
}

impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
type WeightInfo = Self;
fn send(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError> {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
let interior: Junctions =
origin_location.try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = MultiLocation::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, 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)
}
}

impl<T: Config> QueryControllerWeightInfo for Pallet<T> {
fn query() -> Weight {
T::WeightInfo::new_query()
}
fn take_response() -> Weight {
T::WeightInfo::take_response()
}
}

impl<T: Config> QueryController<OriginFor<T>, BlockNumberFor<T>> for Pallet<T> {
type WeightInfo = Self;

fn query(
origin: OriginFor<T>,
timeout: BlockNumberFor<T>,
match_querier: VersionedMultiLocation,
) -> Result<Self::QueryId, DispatchError> {
let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
let query_id = <Self as QueryHandler>::new_query(
responder,
timeout,
MultiLocation::try_from(match_querier)
.map_err(|_| Into::<DispatchError>::into(Error::<T>::BadVersion))?,
);

Ok(query_id)
}
}

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Expand Down Expand Up @@ -771,16 +874,7 @@ pub mod pallet {
dest: Box<VersionedMultiLocation>,
message: Box<VersionedXcm<()>>,
) -> DispatchResult {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
let interior: Junctions =
origin_location.try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = MultiLocation::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, message.clone()).map_err(Error::<T>::from)?;
let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
Self::deposit_event(e);
<Self as SendController<_>>::send(origin, dest, message)?;
Ok(())
}

Expand Down Expand Up @@ -896,31 +990,16 @@ pub mod pallet {
/// execution attempt will be made.
///
/// NOTE: A successful return to this does *not* imply that the `msg` was executed
/// successfully to completion; only that *some* of it was executed.
/// successfully to completion; only that it was attempted.
#[pallet::call_index(3)]
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
pub fn execute(
origin: OriginFor<T>,
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
max_weight: Weight,
) -> DispatchResultWithPostInfo {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let 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;
let outcome = T::XcmExecutor::execute_xcm_in_credit(
origin_location,
message,
hash,
max_weight,
max_weight,
);
let result =
Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into());
Self::deposit_event(Event::Attempted { outcome });
result
let outcome = <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into())
}

/// Extoll that a particular destination can be communicated with through a particular
Expand Down Expand Up @@ -1145,7 +1224,7 @@ impl<T: Config> QueryHandler for Pallet<T> {
timeout: BlockNumberFor<T>,
match_querier: impl Into<MultiLocation>,
) -> Self::QueryId {
Self::do_new_query(responder, None, timeout, match_querier).into()
Self::do_new_query(responder, None, timeout, match_querier)
}

/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
Expand Down
Loading

0 comments on commit 47226e6

Please sign in to comment.