Module xcm_docs::cookbook::relay_token_transactor
source · Expand description
Configuring a parachain that only uses the Relay Chain native token. In the case of Polkadot, this recipe will show you how to launch a parachain with no native token – dealing only on DOT.
§Relay Asset Transactor
This example shows how to configure a parachain to only deal with the Relay Chain token.
The first step is using the [xcm_builder::FungibleAdapter
] to create an AssetTransactor
that
can handle the relay chain token.
mod asset_transactor {
use super::*;
parameter_types! {
pub ParentRelayLocation: Location = Location::parent();
}
/// AssetTransactor for handling the relay chain token
pub type FungibleTransactor = FungibleAdapter<
// Use this implementation of the `fungible::*` traits.
// `Balances` is the name given to the balances pallet in this particular recipe.
// Any implementation of the traits would suffice.
Balances,
// This transactor deals with the native token of the Relay Chain.
// This token is referenced by the Location of the Relay Chain relative to this chain
// -- Location::parent().
IsConcrete<ParentRelayLocation>,
// How to convert an XCM Location into a local account id.
// This is also something that's configured in the XCM executor.
LocationToAccountId,
// The type for account ids, only needed because `fungible` is generic over it.
AccountId,
// Not tracking teleports.
// This recipe only uses reserve asset transfers to handle the Relay Chain token.
(),
>;
/// Actual configuration item that'll be set in the XCM config.
/// A tuple could be used here to have multiple transactors, each (potentially) handling
/// different assets.
/// In this recipe, we only have one.
pub type AssetTransactor = FungibleTransactor;
}
The second step is to configure IsReserve
to recognize the relay chain as a reserve for its
own asset.
With this, you’ll be able to easily mint a derivative asset, backed one-to-one from the Relay
Chain, by using the xcm pallet’s transfer_assets
extrinsic.
The IsReserve
type takes a type that implements ContainsPair<MultiAsset, MultiLocation>
.
In this case, we want a type that contains the pair (relay_chain_native_token, relay_chain)
.
mod is_reserve {
use super::*;
parameter_types! {
/// Reserves are specified using a pair `(AssetFilter, Location)`.
/// Each pair means that the specified Location is a reserve for all the assets in AssetsFilter.
/// Here, we are specifying that the Relay Chain is the reserve location for its native token.
pub RelayTokenForRelay: (AssetFilter, Location) =
(Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible }), Parent.into());
}
/// The wrapper type xcm_builder::Case is needed in order to use this in the configuration.
pub type IsReserve = xcm_builder::Case<RelayTokenForRelay>;
}
With this setup, we are able to do a reserve asset transfer to and from the parachain and relay chain.
#[test]
fn reserve_asset_transfers_work() {
// Scenario:
// ALICE on the relay chain holds some of Relay Chain's native tokens.
// She transfers them to BOB's account on the parachain using a reserve transfer.
// BOB receives Relay Chain native token derivatives on the parachain,
// which are backed one-to-one with the real tokens on the Relay Chain.
//
// NOTE: We could've used ALICE on both chains because it's a different account,
// but using ALICE and BOB makes it clearer.
// We restart the mock network.
MockNet::reset();
// ALICE starts with INITIAL_BALANCE on the relay chain
Relay::execute_with(|| {
assert_eq!(relay_chain::Balances::free_balance(&ALICE), INITIAL_BALANCE);
});
// BOB starts with 0 on the parachain
ParaA::execute_with(|| {
assert_eq!(parachain::Balances::free_balance(&BOB), 0);
});
// ALICE on the Relay Chain sends some Relay Chain native tokens to BOB on the parachain.
// The transfer is done with the `transfer_assets` extrinsic in the XCM pallet.
// The extrinsic figures out it should do a reserve asset transfer
// with the local chain as reserve.
Relay::execute_with(|| {
// The parachain id is specified in the network.rs file in this recipe.
let destination: Location = Parachain(2222).into();
let beneficiary: Location =
AccountId32 { id: BOB.clone().into(), network: Some(NetworkId::Polkadot) }.into();
// We need to use `u128` here for the conversion to work properly.
// If we don't specify anything, it will be a `u64`, which the conversion
// will turn into a non-fungible token instead of a fungible one.
let assets: Assets = (Here, 50u128 * CENTS as u128).into();
assert_ok!(relay_chain::XcmPallet::transfer_assets(
relay_chain::RuntimeOrigin::signed(ALICE),
Box::new(VersionedLocation::from(destination.clone())),
Box::new(VersionedLocation::from(beneficiary)),
Box::new(VersionedAssets::from(assets)),
0,
WeightLimit::Unlimited,
));
// ALICE now has less Relay Chain tokens.
assert_eq!(relay_chain::Balances::free_balance(&ALICE), INITIAL_BALANCE - 50 * CENTS);
// The funds of the sovereign account of the parachain increase by 50 cents,
// the ones transferred over to BOB.
// The funds in this sovereign account represent how many Relay Chain tokens
// have been sent to this parachain.
// If the parachain wants to send those assets somewhere else they have to go
// via the reserve, and this balance is updated accordingly.
// This is why the derivatives are backed one-to-one.
let parachains_sovereign_account =
relay_chain::LocationToAccountId::convert_location(&destination).unwrap();
assert_eq!(relay_chain::Balances::free_balance(parachains_sovereign_account), 50 * CENTS);
});
ParaA::execute_with(|| {
// On the parachain, BOB has received the derivative tokens
assert_eq!(parachain::Balances::free_balance(&BOB), 50 * CENTS);
// BOB gives back half to ALICE in the relay chain
let destination: Location = Parent.into();
let beneficiary: Location =
AccountId32 { id: ALICE.clone().into(), network: Some(NetworkId::Polkadot) }.into();
// We specify `Parent` because we are referencing the Relay Chain token.
// This chain doesn't have a token of its own, so we always refer to this token,
// and we do so by the Location of the Relay Chain.
let assets: Assets = (Parent, 25u128 * CENTS as u128).into();
assert_ok!(parachain::XcmPallet::transfer_assets(
parachain::RuntimeOrigin::signed(BOB),
Box::new(VersionedLocation::from(destination)),
Box::new(VersionedLocation::from(beneficiary)),
Box::new(VersionedAssets::from(assets)),
0,
WeightLimit::Unlimited,
));
// BOB's balance decreased
assert_eq!(parachain::Balances::free_balance(&BOB), 25 * CENTS);
});
Relay::execute_with(|| {
// ALICE's balance increases
assert_eq!(
relay_chain::Balances::free_balance(&ALICE),
INITIAL_BALANCE - 50 * CENTS + 25 * CENTS
);
// The funds in the parachain's sovereign account decrease.
let parachain: Location = Parachain(2222).into();
let parachains_sovereign_account =
relay_chain::LocationToAccountId::convert_location(¶chain).unwrap();
assert_eq!(relay_chain::Balances::free_balance(parachains_sovereign_account), 25 * CENTS);
});
}
For the rest of the code, be sure to check the contents of this module.
Modules§
- The network for this example. Mock network
- The parachain runtime for this example
- The relay chain runtime for this example. Relay chain runtime mock.