referrerpolicy=no-referrer-when-downgrade

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(&parachain).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.