-
Notifications
You must be signed in to change notification settings - Fork 739
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor XCM Simulator Example (#4220)
This PR does a "developer experience" refactor of the XCM Simulator Example. I was looking for existing code / documentation where developers could better learn about working with and configuring XCM. The XCM Simulator was a natural starting point due to the fact that it can emulate end to end XCM scenarios, without needing to spawn multiple real chains. However, the XCM Simulator Example was just 3 giant files with a ton of configurations, runtime, pallets, and tests mashed together. This PR breaks down the XCM Simulator Example in a way that I believe is more approachable by a new developer who is looking to navigate the various components of the end to end example, and modify it themselves. The basic structure is: - xcm simulator example - lib (tries to only use the xcm simulator macros) - tests - relay-chain - mod (basic runtime that developers should be familiar with) - xcm-config - mod (contains the `XcmConfig` type - various files for each custom configuration - parachain - mock_msg_queue (custom pallet for simulator example) - mod (basic runtime that developers should be familiar with) - xcm-config - mod (contains the `XcmConfig` type - various files for each custom configuration I would like to add more documentation to this too, but I think this is a first step to be accepted which will affect how documentation is added to the example --------- Co-authored-by: Francisco Aguirre <[email protected]> Co-authored-by: Kian Paimani <[email protected]>
- Loading branch information
1 parent
68929dd
commit f120193
Showing
23 changed files
with
1,435 additions
and
1,118 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
185 changes: 185 additions & 0 deletions
185
polkadot/xcm/xcm-simulator/example/src/parachain/mock_msg_queue.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// Copyright (C) Parity Technologies (UK) Ltd. | ||
// This file is part of Polkadot. | ||
|
||
// Polkadot is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// Polkadot is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
pub use pallet::*; | ||
use polkadot_core_primitives::BlockNumber as RelayBlockNumber; | ||
use polkadot_parachain_primitives::primitives::{ | ||
DmpMessageHandler, Id as ParaId, XcmpMessageFormat, XcmpMessageHandler, | ||
}; | ||
use sp_runtime::traits::{Get, Hash}; | ||
use xcm::{latest::prelude::*, VersionedXcm}; | ||
|
||
#[frame_support::pallet] | ||
pub mod pallet { | ||
use super::*; | ||
use frame_support::pallet_prelude::*; | ||
|
||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; | ||
type XcmExecutor: ExecuteXcm<Self::RuntimeCall>; | ||
} | ||
|
||
#[pallet::call] | ||
impl<T: Config> Pallet<T> {} | ||
|
||
#[pallet::pallet] | ||
#[pallet::without_storage_info] | ||
pub struct Pallet<T>(_); | ||
|
||
#[pallet::storage] | ||
pub(super) type ParachainId<T: Config> = StorageValue<_, ParaId, ValueQuery>; | ||
|
||
#[pallet::storage] | ||
/// A queue of received DMP messages | ||
pub(super) type ReceivedDmp<T: Config> = StorageValue<_, Vec<Xcm<T::RuntimeCall>>, ValueQuery>; | ||
|
||
impl<T: Config> Get<ParaId> for Pallet<T> { | ||
fn get() -> ParaId { | ||
Self::parachain_id() | ||
} | ||
} | ||
|
||
pub type MessageId = [u8; 32]; | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
// XCMP | ||
/// Some XCM was executed OK. | ||
Success(Option<T::Hash>), | ||
/// Some XCM failed. | ||
Fail(Option<T::Hash>, XcmError), | ||
/// Bad XCM version used. | ||
BadVersion(Option<T::Hash>), | ||
/// Bad XCM format used. | ||
BadFormat(Option<T::Hash>), | ||
|
||
// DMP | ||
/// Downward message is invalid XCM. | ||
InvalidFormat(MessageId), | ||
/// Downward message is unsupported version of XCM. | ||
UnsupportedVersion(MessageId), | ||
/// Downward message executed with the given outcome. | ||
ExecutedDownward(MessageId, Outcome), | ||
} | ||
|
||
impl<T: Config> Pallet<T> { | ||
/// Get the Parachain Id. | ||
pub fn parachain_id() -> ParaId { | ||
ParachainId::<T>::get() | ||
} | ||
|
||
/// Set the Parachain Id. | ||
pub fn set_para_id(para_id: ParaId) { | ||
ParachainId::<T>::put(para_id); | ||
} | ||
|
||
/// Get the queue of receieved DMP messages. | ||
pub fn received_dmp() -> Vec<Xcm<T::RuntimeCall>> { | ||
ReceivedDmp::<T>::get() | ||
} | ||
|
||
fn handle_xcmp_message( | ||
sender: ParaId, | ||
_sent_at: RelayBlockNumber, | ||
xcm: VersionedXcm<T::RuntimeCall>, | ||
max_weight: Weight, | ||
) -> Result<Weight, XcmError> { | ||
let hash = Encode::using_encoded(&xcm, T::Hashing::hash); | ||
let mut message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); | ||
let (result, event) = match Xcm::<T::RuntimeCall>::try_from(xcm) { | ||
Ok(xcm) => { | ||
let location = (Parent, Parachain(sender.into())); | ||
match T::XcmExecutor::prepare_and_execute( | ||
location, | ||
xcm, | ||
&mut message_hash, | ||
max_weight, | ||
Weight::zero(), | ||
) { | ||
Outcome::Error { error } => (Err(error), Event::Fail(Some(hash), error)), | ||
Outcome::Complete { used } => (Ok(used), Event::Success(Some(hash))), | ||
// As far as the caller is concerned, this was dispatched without error, so | ||
// we just report the weight used. | ||
Outcome::Incomplete { used, error } => | ||
(Ok(used), Event::Fail(Some(hash), error)), | ||
} | ||
}, | ||
Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), | ||
}; | ||
Self::deposit_event(event); | ||
result | ||
} | ||
} | ||
|
||
impl<T: Config> XcmpMessageHandler for Pallet<T> { | ||
fn handle_xcmp_messages<'a, I: Iterator<Item = (ParaId, RelayBlockNumber, &'a [u8])>>( | ||
iter: I, | ||
max_weight: Weight, | ||
) -> Weight { | ||
for (sender, sent_at, data) in iter { | ||
let mut data_ref = data; | ||
let _ = XcmpMessageFormat::decode(&mut data_ref) | ||
.expect("Simulator encodes with versioned xcm format; qed"); | ||
|
||
let mut remaining_fragments = data_ref; | ||
while !remaining_fragments.is_empty() { | ||
if let Ok(xcm) = | ||
VersionedXcm::<T::RuntimeCall>::decode(&mut remaining_fragments) | ||
{ | ||
let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); | ||
} else { | ||
debug_assert!(false, "Invalid incoming XCMP message data"); | ||
} | ||
} | ||
} | ||
max_weight | ||
} | ||
} | ||
|
||
impl<T: Config> DmpMessageHandler for Pallet<T> { | ||
fn handle_dmp_messages( | ||
iter: impl Iterator<Item = (RelayBlockNumber, Vec<u8>)>, | ||
limit: Weight, | ||
) -> Weight { | ||
for (_i, (_sent_at, data)) in iter.enumerate() { | ||
let mut id = sp_io::hashing::blake2_256(&data[..]); | ||
let maybe_versioned = VersionedXcm::<T::RuntimeCall>::decode(&mut &data[..]); | ||
match maybe_versioned { | ||
Err(_) => { | ||
Self::deposit_event(Event::InvalidFormat(id)); | ||
}, | ||
Ok(versioned) => match Xcm::try_from(versioned) { | ||
Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), | ||
Ok(x) => { | ||
let outcome = T::XcmExecutor::prepare_and_execute( | ||
Parent, | ||
x.clone(), | ||
&mut id, | ||
limit, | ||
Weight::zero(), | ||
); | ||
<ReceivedDmp<T>>::append(x); | ||
Self::deposit_event(Event::ExecutedDownward(id, outcome)); | ||
}, | ||
}, | ||
} | ||
} | ||
limit | ||
} | ||
} | ||
} |
182 changes: 182 additions & 0 deletions
182
polkadot/xcm/xcm-simulator/example/src/parachain/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
// Copyright (C) Parity Technologies (UK) Ltd. | ||
// This file is part of Polkadot. | ||
|
||
// Polkadot is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// Polkadot is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
//! Parachain runtime mock. | ||
mod mock_msg_queue; | ||
mod xcm_config; | ||
pub use xcm_config::*; | ||
|
||
use core::marker::PhantomData; | ||
use frame_support::{ | ||
construct_runtime, derive_impl, parameter_types, | ||
traits::{ConstU128, ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, Nothing}, | ||
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, | ||
}; | ||
use frame_system::EnsureRoot; | ||
use sp_core::ConstU32; | ||
use sp_runtime::{ | ||
traits::{Get, IdentityLookup}, | ||
AccountId32, | ||
}; | ||
use sp_std::prelude::*; | ||
use xcm::latest::prelude::*; | ||
use xcm_builder::{EnsureXcmOrigin, SignedToAccountId32}; | ||
use xcm_executor::{traits::ConvertLocation, XcmExecutor}; | ||
|
||
pub type AccountId = AccountId32; | ||
pub type Balance = u128; | ||
|
||
parameter_types! { | ||
pub const BlockHashCount: u64 = 250; | ||
} | ||
|
||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] | ||
impl frame_system::Config for Runtime { | ||
type AccountId = AccountId; | ||
type Lookup = IdentityLookup<Self::AccountId>; | ||
type Block = Block; | ||
type AccountData = pallet_balances::AccountData<Balance>; | ||
} | ||
|
||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] | ||
impl pallet_balances::Config for Runtime { | ||
type Balance = Balance; | ||
type ExistentialDeposit = ConstU128<1>; | ||
type AccountStore = System; | ||
} | ||
|
||
#[cfg(feature = "runtime-benchmarks")] | ||
pub struct UniquesHelper; | ||
#[cfg(feature = "runtime-benchmarks")] | ||
impl pallet_uniques::BenchmarkHelper<Location, AssetInstance> for UniquesHelper { | ||
fn collection(i: u16) -> Location { | ||
GeneralIndex(i as u128).into() | ||
} | ||
fn item(i: u16) -> AssetInstance { | ||
AssetInstance::Index(i as u128) | ||
} | ||
} | ||
|
||
impl pallet_uniques::Config for Runtime { | ||
type RuntimeEvent = RuntimeEvent; | ||
type CollectionId = Location; | ||
type ItemId = AssetInstance; | ||
type Currency = Balances; | ||
type CreateOrigin = ForeignCreators; | ||
type ForceOrigin = frame_system::EnsureRoot<AccountId>; | ||
type CollectionDeposit = frame_support::traits::ConstU128<1_000>; | ||
type ItemDeposit = frame_support::traits::ConstU128<1_000>; | ||
type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; | ||
type AttributeDepositBase = frame_support::traits::ConstU128<1_000>; | ||
type DepositPerByte = frame_support::traits::ConstU128<1>; | ||
type StringLimit = ConstU32<64>; | ||
type KeyLimit = ConstU32<64>; | ||
type ValueLimit = ConstU32<128>; | ||
type Locker = (); | ||
type WeightInfo = (); | ||
#[cfg(feature = "runtime-benchmarks")] | ||
type Helper = UniquesHelper; | ||
} | ||
|
||
// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins | ||
// which are locations containing the class location. | ||
pub struct ForeignCreators; | ||
impl EnsureOriginWithArg<RuntimeOrigin, Location> for ForeignCreators { | ||
type Success = AccountId; | ||
|
||
fn try_origin( | ||
o: RuntimeOrigin, | ||
a: &Location, | ||
) -> sp_std::result::Result<Self::Success, RuntimeOrigin> { | ||
let origin_location = pallet_xcm::EnsureXcm::<Everything>::try_origin(o.clone())?; | ||
if !a.starts_with(&origin_location) { | ||
return Err(o); | ||
} | ||
xcm_config::location_converter::LocationConverter::convert_location(&origin_location) | ||
.ok_or(o) | ||
} | ||
|
||
#[cfg(feature = "runtime-benchmarks")] | ||
fn try_successful_origin(a: &Location) -> Result<RuntimeOrigin, ()> { | ||
Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) | ||
} | ||
} | ||
|
||
parameter_types! { | ||
pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); | ||
pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); | ||
} | ||
|
||
impl mock_msg_queue::Config for Runtime { | ||
type RuntimeEvent = RuntimeEvent; | ||
type XcmExecutor = XcmExecutor<XcmConfig>; | ||
} | ||
|
||
pub type LocalOriginToLocation = | ||
SignedToAccountId32<RuntimeOrigin, AccountId, constants::RelayNetwork>; | ||
|
||
pub struct TrustedLockerCase<T>(PhantomData<T>); | ||
impl<T: Get<(Location, AssetFilter)>> ContainsPair<Location, Asset> for TrustedLockerCase<T> { | ||
fn contains(origin: &Location, asset: &Asset) -> bool { | ||
let (o, a) = T::get(); | ||
a.matches(asset) && &o == origin | ||
} | ||
} | ||
|
||
parameter_types! { | ||
pub RelayTokenForRelay: (Location, AssetFilter) = (Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible })); | ||
} | ||
|
||
pub type TrustedLockers = TrustedLockerCase<RelayTokenForRelay>; | ||
|
||
impl pallet_xcm::Config for Runtime { | ||
type RuntimeEvent = RuntimeEvent; | ||
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>; | ||
type XcmRouter = XcmRouter; | ||
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>; | ||
type XcmExecuteFilter = Everything; | ||
type XcmExecutor = XcmExecutor<XcmConfig>; | ||
type XcmTeleportFilter = Nothing; | ||
type XcmReserveTransferFilter = Everything; | ||
type Weigher = weigher::Weigher; | ||
type UniversalLocation = constants::UniversalLocation; | ||
type RuntimeOrigin = RuntimeOrigin; | ||
type RuntimeCall = RuntimeCall; | ||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; | ||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; | ||
type Currency = Balances; | ||
type CurrencyMatcher = (); | ||
type TrustedLockers = TrustedLockers; | ||
type SovereignAccountOf = location_converter::LocationConverter; | ||
type MaxLockers = ConstU32<8>; | ||
type MaxRemoteLockConsumers = ConstU32<0>; | ||
type RemoteLockConsumerIdentifier = (); | ||
type WeightInfo = pallet_xcm::TestWeightInfo; | ||
type AdminOrigin = EnsureRoot<AccountId>; | ||
} | ||
|
||
type Block = frame_system::mocking::MockBlock<Runtime>; | ||
|
||
construct_runtime!( | ||
pub struct Runtime { | ||
System: frame_system, | ||
Balances: pallet_balances, | ||
MsgQueue: mock_msg_queue, | ||
PolkadotXcm: pallet_xcm, | ||
ForeignUniques: pallet_uniques, | ||
} | ||
); |
Oops, something went wrong.