Crate pallet_native_token_management

Source
Expand description

Pallet allowing Partner Chains to support movement of their native token from Cardano.

§Context and purpose of this pallet

Partner Chains Smart Contracts establish a notion of liquid and illiquid supply of the native token on Cardano, represented as native tokens being either freely available in user accounts or locked under a designated illiquid supply address. Movement of native tokens into the illiquid supply on Cardano signals that an equivalent amount of tokens should be made available on the Partner Chain.

This pallet consumes inherent data containing information on the amount of native tokens newly locked on Cardano and produces an inherent extrinsic to handle their movement. The specific logic releasing the tokens is left for the Partner Chain developer to implement and is configured in the pallet via the TokenTransferHandler trait.

IMPORTANT: The mechanism implemented by this pallet is only concerned with the amount of tokens moved and does not attach any metadata to the transfers. In particular it is not a fully-featured token bridge and needs to be combined with a separate sender-receiver metadata channel to implement one.

§Usage

§Defining a transfer handler

The main purpose of the pallet is to trigger user-defined runtime logic whenever a new batch of tokens is observed to have been locked on Cardano. To do that, the Partner Chain builder should define a type implementing the TokenTransferHandler trait, eg.:

use sidechain_domain::NativeTokenAmount;
use frame_support::pallet_prelude::DispatchResult;

pub struct TransferHandler;
impl pallet_native_token_management::TokenTransferHandler for TransferHandler {
	fn handle_token_transfer(token_amount: NativeTokenAmount) -> DispatchResult {
		log::info!("💸 Registered transfer of {} native tokens", token_amount.0);
		Ok(())
	}
}

§Adding to the runtime

Aside from the transfer handler, the pallet requires minimal runtime configuration: the runtime event type, origin for governance calls implementing [EnsureOrigin] and weights:

impl pallet_native_token_management::Config for Runtime {
	type RuntimeEvent = RuntimeEvent;
	type TokenTransferHandler = TransferHandler;
	type MainChainScriptsOrigin = frame_system::EnsureRoot<Self::AccountId>;
	type WeightInfo = pallet_native_token_management::weights::SubstrateWeight<Runtime>;
}

Keep in mind that if the handler logic has to perform storage operations, the pallet’s benchmarks should be rerun. Otherwise default weights are provided in [pallet_native_token_management::weights].

§Script configuration

For token transfers to be observed, the pallet must be configured with correct Cardano addresses and scripts used to idendify them in the ledger. These scripts can be set in two ways: through genesis configuration if the pallet is present in the initial runtime of a chain; or via a governance action using the [set_main_chain_scripts] extrinsic.

§Genesis configuratin

Initial main chain scripts can be set in the genesis configuration, like so:

{
  "nativeTokenManagement": {
    "mainChainScripts": {
      "illiquid_supply_validator_address": "0x616464725f74657374317772687674767833663067397776397278386b66716336306a7661336530376e71756a6b32637370656b76346d717339726a64767a",
      "native_token_asset_name": "0x5043546f6b656e44656d6f",
      "native_token_policy_id": "0xada83ddd029614381f00e28de0922ab0dec6983ea9dd29ae20eef9b4"
    }
  }
}

Note that the mainChainScripts field is optional. If it is left empty, the pallet will stay inactive until configuration is set later.

§Main chain scripts extrinsic

Once the chain is already started, to set initial main chain scripts to a newly added pallet, or to change the existing ones, the [set_main_chain_scripts] extrinsic must be submitted through on-chain governance mechanism like sudo or pallet_democracy. Who exactly can submit this extrinsic is controlled by the Config::MainChainScriptsOrigin field of the pallet’s configuration, but for security it must be a trusted entity.

§Initialization state of the pallet

The pallet tracks its own initialization state through the Initialized storage flag. This information is necessary for it to correctly observe historical data and the state is reset every time main chain scripts are changed in the pallet. This allows the Partner Chain governance to switch to new versions of the smart contracts. However, some consideration must be taken while changing the scripts:

  1. This mechanism can not handle changing the main chain scripts to values that were used before. Doing so will cause some transfers to be registered again, resulting in potential double-spend. This means that a script version roll-back is not possible.
  2. Moving funds from an old illiquid supply address to a new one requires unlocking them and re-locking at the new address, resulting in a new transfer being observed. The logic handling the token movement, including the transfer handler, must be able to handle this unlock-relock behaviour if a Partner Chain governance wishes to migrate tokens to the new address.

Re-exports§

pub use weights::WeightInfo;
pub use pallet::*;

Modules§

pallet
The pallet module in each FRAME pallet hosts the most important items needed to construct this pallet.
weights
Autogenerated weights for pallet_native_token_management

Traits§

TokenTransferHandler
Interface for user-provided logic to handle native token transfers into the illiquid supply on the main chain.