Crate pallet_partner_chains_bridge

Crate pallet_partner_chains_bridge 

Source
Expand description

Pallet that tracks information about incoming token bridge transfers observed on Cardano.

§Purpose of this pallet

This pallet implements runtime logic necessary for Partner Chains to receive token transfers from Cardano using the trustless token bridge. It exposes a callback API for chain builders to hook their own transfer handling logic into the pallet according to their business and ledger rules.

§Working overview

Bridge transfers are initiated by transactions on Cardano that create UTXOs on the illiquid circulating supply (ICP) validator address, each containing a datum which marks them as transfer UTXOs. The observability layer of a Partner Chain node registers creation of these UTXOs and classifies them either as user transfers, ie. transfers sent by normal chain users to a Partner Chain address specified by the user; or special reserve transfers, which are a mechanism for a Partner Chain to gradually move their token reserve from Cardano to its own ledger.

Newly observed and classified bridge transfers are provided to the runtime as inherent data. Based on this data, the pallet creates an inherent extrinsic to handle them in the runtime during block production. This inherent does not process the transfers directly, and instead calls the handler provided by the particular Partner Chain’s builders. This allows the pallet to not assume anything about the ledger structure and logic of the Partner Chain.

§Usage

§Define the recipient type

All user transfers handler by the pallet are addressed to a recipient specified in the datum of the transfer UTXO. This recipient can be any type that can be encoded and decoded as a Plutus byte string. A natural choice would be the account address used in the Partner Chain runtime, but a different type can be chosen as needed.

§Implement the transfer handler

Because the Substrate framework leaves the developers a lot of freedom in structuring their ledger and defining runtime logic, the pallet does not handle the transfers by itself. Instead, it must be configured with a TransferHandler instance by the Partner Chain builder.

This handler is expected to never fail and handle any errors internally, unless there exists a case in which the chain should very deliberately be unable to produce a block. In practice, this means that any invalid transfers should be either discarded or saved for reprocessing later.

A minimal example for a runtime that uses pallet_balances and AccountId32 as its recipient type could look like this:

pub struct BridgeTransferHelper;

impl pallet_partner_chains_bridge::TransferHandler<AccountId32> for BridgeTransferHelper {
	fn handle_incoming_transfer(transfer: BridgeTransferV1<AccountId32>) {
		match transfer {
			BridgeTransferV1::InvalidTransfer { token_amount, utxo_id } => {
				log::warn!("⚠️ Discarded an invalid transfer of {token_amount} (utxo {utxo_id})");
			},
			BridgeTransferV1::UserTransfer { token_amount, recipient } => {
				log::info!("💸 Registered a tranfer of {token_amount} to {recipient:?}");
				let _ = Balances::deposit_creating(&recipient, token_amount.into());
			},
			BridgeTransferV1::ReserveTransfer { token_amount } => {
				log::info!("🏦 Registered a reserve transfer of {token_amount}.");
				let _ = Balances::deposit_creating(&T::ReserveAccount::get(), token_amount.into());
			},
		}
	}
}
```rust

For runtimes that require more complex transfer handling logic, it is a good
practice to create a dedicated pallet in the runtime and have it implement
[TransferHandler], so that any relevant state and configuration can be stored
together.

# Adding the pallet to the runtime

Add the pallet to your runtime's [construct_runtime] and configure it by supplying
all relevant types from your runtime:

```rust,ignore
parameter_types! {
    pub const MaxTransfersPerBlock: u32 = 256;
}

impl pallet_partner_chains_bridge::Config for Runtime {
	type GovernanceOrigin = EnsureRoot<Runtime>;
	type Recipient = AccountId;
	type TransferHandler = BridgeTransferHelper;
	type MaxTransfersPerBlock = MaxTransfersPerBlock;
	type WeightInfo = ();

	#[cfg(feature = "runtime-benchmarks")]
	type BenchmarkHelper = ();
}

In particular, the pallet needs to be configured with the value MaxTransfersPerBlock, which determines the upper bound on the number of transactions that can be processed per block. All outstanding transfers beyond that limit will be processed in subsequent block. It is important to select a value high enough to guarantee that the chain will be able to keep up with the volume of transfers coming in.

The last thing to implement in the runtime is the runtime API used by the observability layer to access the configuration stored in the pallet. This is straightforward and involves only calling methods defined on the pallet:

impl sp_partner_chains_bridge::TokenBridgeIDPRuntimeApi<Block> for Runtime {
	fn get_pallet_version() -> u32 {
		Bridge::get_pallet_version()
	}
	fn get_main_chain_scripts() -> Option<BridgeMainChainScripts> {
		Bridge::get_main_chain_scripts()
	}
	fn get_max_transfers_per_block() -> u32 {
		Bridge::get_max_transfers_per_block()
	}
	fn get_last_data_checkpoint() -> Option<BridgeDataCheckpoint> {
		Bridge::get_data_checkpoint()
	}
}

§Providing genesis configuration

The pallet’s genesis configuration only consists of optional values of the main chain scripts, that can be set after chain start. These scripts point the observability layer to the correct addresses and token asset to observe on Cardano. If they are left empty, the pallet and the observability components will be incactive until they are supplied in the future.

An example of a bridge pallet section in a genesis config JSON would look like this:

{
    "bridge": {
      "mainChainScripts": {
        "illiquid_circulation_supply_validator_address": "addr_test1wzzyc3mcqh4phq0pa827dn756lfd045lzh3tgr9mt5p2ayqpxp55c",
        "token_asset_name": "0x5043546f6b656e44656d6f",
        "token_policy_id": "0xada83ddd029614381f00e28de0922ab0dec6983ea9dd29ae20eef9b4"
      }
	}
}

When programmatically assembling the genesis config, a utility function is supplied for reading the main chain script values from environment:

	GenesisConfig {
		main_chain_scripts: MainChainScripts::read_from_env().ok(),
		..Default::default()
	}

See [sp_partner_chains_bridge::MainChainScripts::read_from_env] for details.

§Supplying observability data

See documentation of [sp_partner_chains_bridge] for instructions on adding the observability data source to your node and connecting it to the pallet.

Re-exports§

pub use pallet::*;

Modules§

pallet
The pallet module in each FRAME pallet hosts the most important items needed to construct this pallet.
weights
Weight types and default weight values Autogenerated weights for pallet_partner_chains_bridge

Traits§

TransferHandler
Runtime logic for handling incoming token bridge transfers from Cardano