Parachain XCM Configuration
How to use the slides - Full screen (new tab)
Parachain XCM Configuration
---v
At the end of this lecture, you will be able to:
- Understand the different XCM configurable parts of a chain
- Construct different XCM configurations for chains with different needs
🛠️ Configurables in XcmConfig
Notes:
The XCM Configuration has many configurable items
EXERCISE: ask the class to raise hands and postulate on what they think should be configurable.
---v
🛠️ Configurables in XcmConfig
#![allow(unused)] fn main() { // How we convert locations into account ids type SovereignAccountOf = SovereignAccountOf; pub struct XcmConfig; impl Config for XcmConfig { // The absolute Location of the current system type UniversalLocation = UniversalLocation; // Pre-execution filters type Barrier = Barrier; // How we withdraw/deposit assets type AssetTransactor = LocalAssetTransactor; // How we convert a Location to a FRAME dispatch origin type OriginConverter = LocalOriginConverter; // How we route the XCM outside this chain type XcmSender = XcmRouter; // Who we trust as reserve chains type IsReserve = ?; // Who do we trust as teleporters type IsTeleporter = ?; // How we weigh a message type Weigher = ?; // How we charge for fees type Trader = ?; // How we handle responses type ResponseHandler = ?; // How we handle asset traps type AssetTrap = ?; // How we handle asset claims type AssetClaims = ?; // How we handle version subscriptions type SubscriptionService = ?; } }
Notes:
-
SovereignAccountOf
: Means of converting aLocation
into an account ID Used later for:OriginConverter
,AssetTransactor
-
xcm-pallet
is a pallet that not only allows sending and executing XCM messages, but rather it also implements several of the configuration traits and thus can be used perform several XCM configuration actions.
---v
🛠️ xcm-builder
xcm-builder
is a crate containing common configuration shims to facilitate XCM configuration.
Most pre-built configuration items can be found in xcm-builder
.
It allows to use the XCM executor in FRAME.
🤔 Grab your chain's requirements before starting
Questions that you should have answers for:
-
Is my chain going to transfer just the native token? Is my chain going to receive several other kinds of assets?
-
Is my chain going to allow free execution? Maybe only limited to some parachains/relay chain?
-
Is my chain a 20 byte account chain? a 32 byte account chain?
-
How will my chain accept fee payment? In one asset? In several?
Notes:
- Some of the answers to these questions might imply you need to use your own custom primitives.
---v
Our starting example setup requirements
- Parachain that does not charge for relay incoming messages.
- Parachain that trusts the relay as the reserve chain for the relay chain tokens.
- Parachain that mints in
pallet-balances
when it receives relay chain tokens. - Users can execute XCMs locally.
📁 SovereignAccountOf
via xcm-builder
- Defines how we convert a
Location
into a local account ID. - Useful when we want to withdraw/deposit tokens from a
Location
defined origin - Useful when we want to dispatch as signed origins from a
Location
defined origin.
Notes:
- This will define how we convert a
Location
into a local account ID. - This is useful when we want to withdraw/deposit tokens from a
Location
defined origin or when we want to dispatch as signed origins from aLocation
defined origin.
---v
📁 SovereignAccountOf
via xcm-builder
HashedDescription
: Hashes the description of aLocation
and converts that into anAccountId
.
#![allow(unused)] fn main() { pub struct HashedDescription<AccountId, Describe>(PhantomData<(AccountId, Describe)>); impl< AccountId: From<[u8; 32]> + Clone, Describe: DescribeLocation > ConvertLocation<AccountId> for HashedDescription<AccountId, Describe> { fn convert_location(value: &Location) -> Option<AccountId> { Some(blake2_256(&Describe::describe_location(value)?).into()) } } }
---v
📁 SovereignAccountOf
via xcm-builder
HashedDescription
. An example of a converter definition:
#![allow(unused)] fn main() { pub type LocationToAccount = HashedDescription<AccountId, ( LegacyDescribeForeignChainAccount, // Legacy conversion - MUST BE FIRST! DescribeTerminus, DescribePalletTerminal )>; }
---v
📁 SovereignAccountOf
via xcm-builder
DescribeLocation
: Means of converting a location into a stable and unique descriptive identifier.
#![allow(unused)] fn main() { pub trait DescribeLocation { /// Create a description of the given `location` if possible. No two locations should have the /// same descriptor. fn describe_location(location: &Location) -> Option<Vec<u8>>; } }
Notes:
---v
📁 SovereignAccountOf
via xcm-builder
DescribeAccountId32Terminal
#![allow(unused)] fn main() { fn describe_location(l: &Location) -> Option<Vec<u8>> { match (l.parents, &l.interior) { (0, X1(AccountId32 { id, .. })) => Some((b"AccountId32", id).encode()), _ => return None, } } }
---v
📁 SovereignAccountOf
via xcm-builder
DescribeTerminus
#![allow(unused)] fn main() { fn describe_location(l: &Location) -> Option<Vec<u8>> { match (l.parents, &l.interior) { (0, Here) => Some(Vec::new()), _ => return None, } } }
---v
📁 SovereignAccountOf
via xcm-builder
DescribePalletTerminal
#![allow(unused)] fn main() { fn describe_location(l: &Location) -> Option<Vec<u8>> { match (l.parents, &l.interior) { (0, X1(PalletInstance(i))) => Some((b"Pallet", Compact::<u32>::from(*i as u32)).encode()), _ => return None, } } }
---v
📁 SovereignAccountOf
via xcm-builder
DescribeAccountKey20Terminal
#![allow(unused)] fn main() { fn describe_location(l: &Location) -> Option<Vec<u8>> { match (l.parents, &l.interior) { (0, X1(AccountKey20 { key, .. })) => Some((b"AccountKey20", key).encode()), _ => return None, } } }
---v
📁 SovereignAccountOf
via xcm-builder
-
AccountId32Aliases
: Converts a localAccountId32
Location
into an account ID of 32 bytes. -
Account32Hash
: Hashes theLocation
and takes the lowest 32 bytes as account. -
ParentIsPreset
: Converts the parentLocation
into an account of the formb'Parent' + trailing 0s
---v
📁 SovereignAccountOf
via xcm-builder
-
ChildParachainConvertsVia
: Converts the child parachainLocation
into an account of the formb'para' + para_id_as_u32 + trailing 0s
-
SiblingParachainConvertsVia
: Convert the sibling parachainLocation
into an account of the formb'sibl' + para_id_as_u32 + trailing 0s
UniversalLocation
The absolute location of the consensus system being configured.
#![allow(unused)] fn main() { parameter_types! { pub const UniversalLocation: InteriorLocation = GlobalConsensus(NetworkId::Polkadot).into(); } }
🚧 Barrier
via xcm-builder
- Barriers specify whether or not an XCM is allowed to be executed on the local consensus system.
- They are checked before the actual XCM instruction execution.
- Barriers should not involve any heavy computation.
Notes:
At the point at which barriers are checked nothing has yet been paid for its execution.
---v
🚧 Barrier
via xcm-builder
Physical vs Computed origin
- Physical origin: the consensus system that built this particular XCM and sent it to the recipient
- Computed origin: the entity that ultimately instructed the consensus system to build the XCM
Notes:
If an EOA transfers some funds via XCM, then the computed origin would be its account, but the physical origin would be the platform that was used (e.g. parachain).
---v
🚧 Barrier
via xcm-builder
Barriers that operate upon computed origins must be put inside of WithComputedOrigin
.
Allows for origin altering instructions at the start.
#![allow(unused)] fn main() { pub struct WithComputedOrigin<InnerBarrier, LocalUniversal, MaxPrefixes>; }
---v
🚧 Barrier
via xcm-builder
TakeWeightCredit
: Subtracts the maximum weight the message can consume from the available weight credit. Usually configured for localxcm-execution
---v
🚧 Barrier
via xcm-builder
AllowTopLevelPaidExecutionFrom<T>
: For origins contained inT
, it makes sure the first instruction puts asset into the holding register, followed by aBuyExecution
instruction capable of buying sufficient weight. Critical to avoid free DOS.
Notes:
-
A chain without
AllowTopLevelPaidExecutionFrom
could potentially receive several heavy-computation instructions without paying for it. Checking that the first instructions are indeed paying for execution helps to quick-discard them. -
While
BuyExecution
is crucial for messages coming from other consensus systems, local XCM execution fees are paid as any other substrate extrinsic.
---v
🚧 Barrier
via xcm-builder
AllowExplicitUnpaidExecutionFrom<T>
: Allows free execution iforigin
is contained inT
and the first instruction isUnpaidExecution
.
Notes:
- This fulfills our requirements
- To meet our example use case, we only need the relay to have free execution.
---v
🚧 Barrier
via xcm-builder
AllowKnownQueryResponses
: Allows the execution of the message if it contains only an expectedQueryResponse
AllowSubscriptionsFrom<T>
: If theorigin
that sent the message is contained inT
, it allows the execution of the message if it contains only aSubscribeVersion
orUnsubscribeVersion
instruction
🪙 AssetTransactor
via xcm-builder
- Define how we are going to withdraw and deposit assets
- Heavily dependant on the assets we want our chain to transfer
Notes:
- The relay chain is a clear example of a chain that handles a single token.
- AssetHub on the contrary acts as an asset-reserve chain, and it needs to handle several assets
---v
🪙 AssetTransactor
via xcm-builder
FungiblesAdapter
: Used for depositing/withdrawing from a set of defined fungible tokens. An example of these would bepallet-assets
tokens.NonFungiblesAdapter
: Used for depositing/withdrowing NFTs. For examplepallet-nfts
.
Notes:
-
Matcher: Matches the
Asset
against some filters and returns the amount to be deposited/withdrawn -
AccountIdConverter: Means of converting a
Location
into an account -
For our example, it suffices to uses
CurrencyAdapter
, as all we are going to do is mint in a single currency (Balances) whenever we receive the relay token.
---v
🪙 AssetTransactor
via xcm-builder
#![allow(unused)] fn main() { fn withdraw_asset( what: &Asset, who: &Location, _maybe_context: Option<&XcmContext>, ) -> result::Result<xcm_executor::Assets, XcmError> { let (asset_id, amount) = Matcher::matches_fungibles(what)?; let who = AccountIdConverter::convert_location(who) .ok_or(MatchError::AccountIdConversionFailed)?; Assets::burn_from(asset_id, &who, amount, Exact, Polite) .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; Ok(what.clone().into()) } }
---v
🪙 AssetTransactor
via xcm-builder
#![allow(unused)] fn main() { fn deposit_asset( what: &Asset, who: &Location, _context: &XcmContext ) -> XcmResult { let (asset_id, amount) = Matcher::matches_fungibles(what)?; let who = AccountIdConverter::convert_location(who) .ok_or(MatchError::AccountIdConversionFailed)?; Assets::mint_into(asset_id, &who, amount) .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; Ok(()) } }
📍 OriginConverter
via xcm-builder
- Defines how to convert an XCM origin, defined by a
Location
, into a frame dispatch origin. - Used in the
Transact
instruction.
Notes:
Transact
needs to dispatch from a frame dispatch origin. However thexcm-executor
works with XCM origins which are defined byLocation
s.OriginConverter
is the component that converts one into the other
---v
📍 List of origin converters
-
SovereignSignedViaLocation
: Converts theLocation
origin (typically, a parachain origin) into a signed origin. -
SignedAccountId32AsNative
: Converts a local 32 byte accountLocation
into a signed origin using the same 32 byte account. -
ParentAsSuperuser
: Converts the parent origin into the root origin. -
SignedAccountKey20AsNative
: Converts a local 20 byte accountLocation
into a signed origin using the same 20 byte account.
Notes:
ParentAsSuperuser
can be used in common-good chains as they do not have a local root origin and instead allow the relay chain root origin to act as the root origin.
🛠️ XcmRouter in XcmConfig
ParentAsUmp
routes XCM to relay chain through UMP.XcmpQueue
routes XCM to other parachains through XCMP.
#![allow(unused)] fn main() { pub type XcmRouter = ( // Two routers - use UMP to communicate with the relay chain: cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm>, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); }
Notes:
ParachainSystem
is a pallet in cumulus that handles incoming DMP messages and queues, among other miscellaneous parachain-related matters.- If the destination location matches the form of
Location { parents: 1, interior: Here }
, the message will be routed through UMP. The UMP channel is available by default. - If the destination matches the form of
Location { parents: 1, interior: X1(Parachain(para_id)) }
, the message will be routed through XCMP. As of today, an HRMP channel should be established before the message can be routed. - The tuple implementation of this item means the executor will try using the items in order.
---v
Router
#![allow(unused)] fn main() { pub trait SendXcm { type Ticket; fn validate( destination: &mut Option<Location>, message: &mut Option<Xcm<()>>, ) -> SendResult<Self::Ticket>; fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError>; } }
Notes:
It's important to validate that the message can indeed be sent before sending it. This ensures you pay for sending fees and you actually do send it.
Summary
In this lecture, we learnt: