Skip to main content

Fungible Assets on Asset Hub

September 28, 2023 in Polkadot, Developers, System Parachains, Technology
Avatarby Joe Petrowski

The vast majority of people are used to identifying assets by name or symbol, like “Tether” or “USDT”. If you’ve been around Ethereum for any length of time, you are probably used to zero-ex contract addresses. Then Polkadot’s Asset Hub hosted asset functionality directly in-protocol, with simple integers as asset IDs. “1984”, cheeky naming aside, is certainly easier for humans to remember (and verify) than 0xdAC17F958D2ee523a2206206994597C13D831ec7. And now Polkadot has another parallel instance of this asset functionality, except that this instance identifies assets by an XCM primitive called a “multilocation”.

In writing this explainer, I hope to convey that this opens up expressive and powerful patterns for asset utilization within the Polkadot network and anywhere reachable from it.

Local and foreign assets

When the Asset Hub first launched, it only hosted one instance of the Assets pallet that allowed anyone to claim an available asset ID and create their asset. Rather than a custom contract for each asset, Asset Hub has embedded asset logic that treats them as first class primitives. Every asset has the same functionality.

These assets with claimable, integer-based asset IDs, are called “local assets”. Asset Hub functioned primarily for the creators of these assets, often reserve-backed stablecoins like USDT. However, the protocol only enforces uniqueness of the asset ID (in this case, an integer). The creator can set metadata like the asset symbol. Therefore, users still have some due diligence to perform on their assets; anyone can call their asset USDT, but the user probably wants the one created by Tether.

The Asset Hub serves as the “management portal” for the asset creator, allowing them to mint and burn tokens and get an overview of their total issuance in the entire Polkadot network, including those tokens that have been sent to other locations in the network.

But the asset ID itself is not very expressive. Although easier to verify than a contract address, the ID does not tell the user anything about the asset. Enter, XCM.

Multilocations express relative paths. They are relative in that they depend on the location of interpretation. “How do I get to the supermarket” will have different directions depending on the starting position. At the most basic level, these paths express directions to other chains. But they can express directions to just about anything: an asset, a contract, a pallet index, a governing body, an account, etc.

Multilocations have a series of junctions, typically expressed in two parts: first, a number of “parents” and second, a path to follow from there. An example of a multilocation is “parents: 1, interior: Parachain(9,000)”. This says, “go to my parent, and from there go to Parachain 9,000”. A “parent” in this context is a containing consensus system. For example, the Relay Chain is the containing consensus system of parachains. A parachain could be the containing consensus system of a smart contract. It would make sense that, in this example, the multilocation is from another parachain, say the Asset Hub. Parachain 9,000 would be a sibling, since they both share the same parent, namely, the Relay Chain.

As asset identifiers, multilocations proffer significant advantages over absolute (e.g. address, hash, integer) identifiers. Primarily, an asset’s multilocation itself identifies the controlling entity. In the above example, that would be the governance of Parachain 9,000. When looking at absolute identifiers, the user must trust the issuing entity and its claims, for example that the on-chain tokens are backed in one-to-one correspondence with off-chain assets. The multilocation, terminating at a parachain, smart contract, or other protocol actually indicates the logic that controls the asset. That does not absolve the user of all necessary diligence, perhaps for example Parachain 9,000 has a trusted “superuser”. But the multilocation tells the user that this asset is controlled by a protocol, and which one.

Beyond the terminus of the multilocation, it actually makes explicit a “chain of command”. Take a longer example, say an asset with ID 42 within a pallet on parachain 9,000 – “parents: 1, interior: Parachain(9,000), PalletIndex(99), GeneralIndex(42)”. This asset is controlled by the pallet, which is within the consensus of the parachain, which is within the consensus of a shared parent (the Relay Chain). Multilocations can even express entirely foreign consensus systems, for example, “parents: 2, interior: GlobalConsensus(Ethereum)”. From a parachain’s perspective, this would say, “go up two levels (as in, one above the Relay Chain) and then go into Ethereum’s consensus”.

Note that these locations are very similar to Unix file paths, e.g. “../Parachain(9000)/PalletIndex(99)/GeneralIndex(42)” or “../../GlobalConsensus(Ethereum)”.

The net result is that Polkadot’s Asset Hub can represent any asset that is reachable from Polkadot. Whether by local pallet or contract calls, XCMP, or by bridge, whether protocol native token or local asset from another chain, the Asset Hub provides a common interface for all where an asset’s identifier actually tells you its sovereign location.

Relationships

The XCM language has two ways of expressing asset transfer relationships for a location/asset pair: teleports and reserves. These define the relationships that Asset Hub has with other chains and how they interact.

Teleports are simple. When two chains trust each other with a given asset, the sender can simply destroy it and issue an instruction to the receiver to mint it. As long as the sender trusts that the receiver will not mint more than it has been sent, the sender can accept the same teleport instruction in return.

Reserves are more complicated. When the chain from which the asset originates does not trust another chain, it can place an asset into the destination chain’s sovereign account locally and send a message to the destination chain that it has credited its local account with the assets. The destination chain can then mint derivative assets for its users. When it’s done with the assets, the destination chain can send a message back to instruct the originating chain to move the assets out of its account (presumably it has destroyed the corresponding derivative issuance).

The trust in the reserve scenario is one-way. The chain minting derivative assets trusts the issuing chain to maintain its sovereign account balance and respect redemptions. But the issuing chain does not trust the chain where those assets are going to handle them faithfully.

An important note here is that the trust relationship exists for a location/asset pair: That is, a chain can trust another chain as a teleporter for some assets but not for others.

So, who trusts whom? And with what? Entities always trust their “parent” in the multilocation paradigm. For example, a smart contract on Parachain 8,000 trusts the governance of Parachain 8,000, which trusts the Polkadot Relay Chain. The Polkadot Relay Chain is governed by the “root origin”, which can execute any instruction, including kicking out a parachain. Polkadot’s root origin also governs all of its system parachains (in fact, the Relay Chain plus all system parachains could be thought of as the singular “Polkadot protocol”).

All chains and sub-protocols (e.g. smart contracts) in the Polkadot network trust the Polkadot protocol, so they should be fine to teleport assets with it. In fact, using reserves would be downright silly: If the Polkadot protocol did not like its reserve balance on the originating chain, it could, via root origin referendum, just overwrite the balance to something it prefers.

The Polkadot protocol, on the other hand, cannot extend such universal trust to the members within it. But it can trust a location to manage the assets that originate from that location. It can trust Parachain 9,000 with its native token (PNT, “pints”?) and assets created within it, for example tokens issued locally. Therefore, when interacting with Parachain 9,000, Asset Hub would teleport PNT, respecting that PNT originates from that parachain. But, still with Parachain 9,000, Asset Hub would use reserve transfers for PET (Parachain 8,000’s token, pronunciation less ambiguous).

Choosing reserve locations

The creation of PET is under the governance of Parachain 8,000, which accepts the governance of the Polkadot protocol. Therefore, Polkadot naturally trusts Parachain 8,000 with PET because PET forms part of the Parachain 8,000 protocol. But neither Polkadot nor Parachain 8,000 trust any other parachain* to act as a reserve location for PET.

(*Sidenote: Although they could. Parachain 8,000 might have other siblings that respect its governance origins, much like many system chains respect the Polkadot OpenGov origins. In this regard, it’s better to consider sovereign systems, which could comprise multiple chains, rather than individual chains.)

This concept continues down the chain of command, to other assets created within Parachain 8,000. In fact, it has nothing to do with separate chains or asynchrony; two smart contracts on the same chain may not trust each other to manage each other’s assets, but they both trust the chain they exist within.

Given this bidirectional trust relationship, Asset Hub can act as a reserve location for assets that were teleported to it. Parachain 8,000 can teleport its PET to the Asset Hub, which can then act as a reserve location for transfers to and amongst other locations. Meaning, Parachain 9,000 can use Asset Hub for its reserve of PET to send to other parachains.

But these other locations now could see both Parachain 8,000 and the Asset Hub as reserve locations of PET.

In practice, protocols (parachains, smart contracts, etc.) that want to use Asset Hub in this way will need to manage the idea of multiple reserve locations for given assets. Pragmatically, it probably means choosing one reserve location for each asset. Common agreements and standards amongst parachains and other protocols will likewise simplify their interactions.

With thousands of protocols in the Polkadot network, establishing communication channels with all of them is unwieldy, undesirable, or impractical. Just because one protocol does not want channels with every other protocol, it may still want free access to assets. And because Asset Hub can represent and act as a reserve location for any asset reachable from, not just within, the Polkadot network, the Asset Hub can serve as a single reserve location from which a protocol can manage and interact with a virtually unlimited number of assets.

In practice

Let’s look at an example of how to write an XCM program that will teleport a parachain’s asset to Asset Hub. Two things should stand out to developers who want to add this logic to their parachain. First, that XCM program execution occurs from the perspective of the executor instance, not the program’s origin. This means that applications should send programs that reference assets and locations from the perspective of Asset Hub.

Second, paying fees may not be trivial. When teleporting DOT between system chains or using reserve-based instructions for chains that have DOT on their sovereign account, those applications can use the asset they are transacting in for fee payment. However, Asset Hub may not accept an application’s asset for fee payment, so the program needs to pay for fees in an acceptable asset. The addition of Asset Conversion will make this simpler and more flexible, but a chain will still need to initiate the pairs that enable fee payment.

Start our program by defining some assets, namely DOT and the native asset of parachain 9,000 (PINTs), as well as a beneficiary to receive the assets:

// Define the native asset, from Asset Hub's point of view.
let dot = MultiAsset {
	id: AssetId::Concrete(MultiLocation::parent()),
	fun: Fungibility::Fungible(10_000_000_000), // 1 DOT
};

// The location of parachain 9,000 from Asset Hub's perspective. From a parachain's
// perspective it would probably reference its native asset and reanchor.
let para_nine_thousand = MultiLocation::new(1, Parachain(9000));
let hundred_pints = MultiAsset {
	id: AssetId::Concrete(para_nine_thousand),
	fun: Fungibility::Fungible(100),
};

// The beneficiary where we want all assets to go. Namely, the sender must specify the
// `AccountId` where they want to deposit the assets (`target_account`).
let beneficiary = MultiLocation {
	parents: 0,
	interior: X1(AccountId32 { network: None, id: target_account.into() }),
};

Before constructing the program to send to Asset Hub, the sender will need to account for the assets they are teleporting. A chain could also configure its XCM Executor to handle this more gracefully.

let assets: MultiAssets = hundred_pints.into();
let mut local_program_to_execute: Xcm<<T as frame_system::Config>::RuntimeCall> = Xcm(vec![
	// Withdraw the asset (PINTs) that you're teleporting.
	WithdrawAsset(assets.clone()),
	// Use the local default asset to pay for execution fees.
	SetFeesMode { jit_withdraw: true },
	// Burn the PINTs you are teleporting.
	BurnAsset(assets),
	// Withdraw and burn the DOT used for fees. The asset used here depends on how your
	// local chain represents reserve-backed DOT.
	WithdrawAsset(/* local representation of DOT */),
	BurnAsset(/* local representation of DOT */),
]);
let outcome = T::XcmExecutor::execute_xcm_in_credit(local_program_to_execute, ..);
outcome.clone().ensure_complete().map_err(|_| Error::<T>::FailedToExecute)?;

Now, move on to constructing the XCM program to send to Asset Hub:

// Finally, construct the XCM program to send to Asset Hub.
let xcm_to_send = Xcm(vec![
	// Withdraw DOT from the sender's sovereign account. The user should have to pay
	// for this on the sending side, and your parachain needs to keep its sovereign
	// account topped up.
	WithdrawAsset(dot.clone().into()),
	// Buy some execution with this.
	BuyExecution { fees: dot.into(), weight_limit: Unlimited },
	// Now we can receive the teleported foreign asset.
	ReceiveTeleportedAsset(hundred_pints.into()),
	// Refund any weight surplus.
	RefundSurplus,
	// Deposit PINTs and remaining DOT to the same beneficiary.
	DepositAsset { assets: AllCounted(2).into(), beneficiary },
]);

This program will withdraw DOT from the parachain’s sovereign account, use this DOT to buy the weight needed to execute this program, receive the teleported PINTs, refund any unused weight, and finally deposit both assets (any change from the DOT withdrawn plus the PINTs) into the beneficiary account.

Remember that the sender probably has some accounting to do prior to sending this message. This type of program construction should also not be directly available to users, but rather behind extrinsics with proper checks in place. Almost certainly, the sender is not a trusted teleporter of DOT, otherwise they would teleport both assets and probably would not have DOT in their sovereign account to withdraw from. Meaning, they probably have a derivative, reserve-backed DOT on their local chain. Withdrawing this DOT from their sovereign account and moving it into fee payment and a beneficiary will reduce their reserves. Therefore, the sender should burn this reserve-backed representation prior to sending this message lest its chain not have full collateral in the reserve. The sender could debit it from the user who initiates the teleport, or keep its own treasury of DOT to draw from (occasionally topping up its reserves). For a more complete example, see the accounting done in Trappist.

Wrapping Up

The addition of foreign assets to Asset Hub introduces new paradigms like multilocations as asset identifiers and multiple reserve locations, but these allow expressive and convenient interactions within the network.

Parity will be releasing more examples and tutorials in the coming months to demonstrate some common patterns of working with foreign assets. Parachain developers should keep an eye on Trappist on Rococo and wallet/integration developers should look at the Asset Transfer API.

For the brave, Rococo’s, Kusama’s, and Polkadot’s Asset Hubs already support foreign assets, so you can get to work developing.

From the blog

Technology

Key Metrics and Insights: June 2024

Stay updated with the latest Polkadot tech updates, metrics, and insights from June 2024, presented by the Parity Success Team.

Decentralization

Introducing the New Polkadot Ledger App

Discover the new Polkadot Ledger app for seamless, secure transactions. Now available on Ledger Live, it supports Polkadot, Kusama, and more.

Ecosystem

Polkadot’s May Ecosystem Insights

Welcome to the latest edition of your go-to source for the latest tech updates, key metrics, and discussions happening across the Polkadot Ecosystem from the Parity Success Team. In this blog series, we cover a range of topics from sources such as Canny.io / GitHub / project teams and the Polkadot Forum. Core Metrics OpenGov Activity This month, once again, the community and its DOT holders have shown their passion for OpenGov, the platform where anyone can contribute and have their say in

Subscribe to the newsletter to hear about updates and events.