referrerpolicy=no-referrer-when-downgrade

Module 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§

network
The network for this example. Mock network
parachain
The parachain runtime for this example
relay_chain
The relay chain runtime for this example. Relay chain runtime mock.