Skip to content

Commit d228760

Browse files
authored
change: ETCM-9687 docs and small fixes for native token crates (#809)
1 parent d8e7295 commit d228760

File tree

7 files changed

+209
-16
lines changed

7 files changed

+209
-16
lines changed

demo/node/src/staging.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ pub fn staging_genesis(
150150
main_chain_scripts: sp_session_validator_management::MainChainScripts::read_from_env()?,
151151
},
152152
native_token_management: NativeTokenManagementConfig {
153-
main_chain_scripts: sp_native_token_management::MainChainScripts::read_from_env()?,
153+
main_chain_scripts: Some(sp_native_token_management::MainChainScripts::read_from_env()?),
154154
..Default::default()
155155
},
156156
governed_map: GovernedMapConfig {

demo/node/src/template_chain_spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub fn chain_spec() -> Result<ChainSpec, envy::Error> {
4040
main_chain_scripts: sp_session_validator_management::MainChainScripts::read_from_env()?,
4141
},
4242
native_token_management: NativeTokenManagementConfig {
43-
main_chain_scripts: sp_native_token_management::MainChainScripts::read_from_env()?,
43+
main_chain_scripts: Some(sp_native_token_management::MainChainScripts::read_from_env()?),
4444
..Default::default()
4545
},
4646
governed_map: GovernedMapConfig {

demo/node/src/testnet.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ pub fn testnet_genesis(
202202
main_chain_scripts: sp_session_validator_management::MainChainScripts::read_from_env()?,
203203
},
204204
native_token_management: NativeTokenManagementConfig {
205-
main_chain_scripts: sp_native_token_management::MainChainScripts::read_from_env()?,
205+
main_chain_scripts: Some(sp_native_token_management::MainChainScripts::read_from_env()?),
206206
..Default::default()
207207
},
208208
governed_map: GovernedMapConfig {

toolkit/native-token-management/pallet/src/benchmarking.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use super::*;
66
use crate::Pallet as NativeTokenManagement;
7-
87
use frame_benchmarking::v2::*;
98
use frame_system::RawOrigin;
109

toolkit/native-token-management/pallet/src/lib.rs

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Pallet allowing Partner Chains to support movement of their native token from Cardano.
22
//!
3+
//! # Context and purpose of this pallet
4+
//!
35
//! Partner Chains Smart Contracts establish a notion of liquid and illiquid supply of the native
46
//! token on Cardano, represented as native tokens being either freely available in user accounts
57
//! or locked under a designated illiquid supply address. Movement of native tokens into the illiquid
@@ -9,12 +11,97 @@
911
//! This pallet consumes inherent data containing information on the amount of native tokens newly
1012
//! locked on Cardano and produces an inherent extrinsic to handle their movement. The specific
1113
//! logic releasing the tokens is left for the Partner Chain developer to implement and is configured
12-
//! in the pallet via the `TokenTransferHandler`.
14+
//! in the pallet via the [TokenTransferHandler] trait.
15+
//!
16+
//! *IMPORTANT*: The mechanism implemented by this pallet is only concerned with the amount of tokens
17+
//! moved and does not attach any metadata to the transfers. In particular it is not a
18+
//! fully-featured token bridge and needs to be combined with a separate sender-receiver
19+
//! metadata channel to implement one.
20+
//!
21+
//! # Usage
22+
//!
23+
//! ## Defining a transfer handler
24+
//!
25+
//! The main purpose of the pallet is to trigger user-defined runtime logic whenever a new batch of
26+
//! tokens is observed to have been locked on Cardano. To do that, the Partner Chain builder should
27+
//! define a type implementing the [TokenTransferHandler] trait, eg.:
28+
//!
29+
//! ```rust
30+
//! use sidechain_domain::NativeTokenAmount;
31+
//! use frame_support::pallet_prelude::DispatchResult;
32+
//!
33+
//! pub struct TransferHandler;
34+
//! impl pallet_native_token_management::TokenTransferHandler for TransferHandler {
35+
//! fn handle_token_transfer(token_amount: NativeTokenAmount) -> DispatchResult {
36+
//! log::info!("💸 Registered transfer of {} native tokens", token_amount.0);
37+
//! Ok(())
38+
//! }
39+
//! }
40+
//! ```
41+
//!
42+
//! ## Adding to the runtime
43+
//!
44+
//! Aside from the transfer handler, the pallet requires minimal runtime configuration: the runtime event
45+
//! type, origin for governance calls implementing [EnsureOrigin] and weights:
46+
//!
47+
//! ```rust,ignore
48+
//! impl pallet_native_token_management::Config for Runtime {
49+
//! type RuntimeEvent = RuntimeEvent;
50+
//! type TokenTransferHandler = TransferHandler;
51+
//! type MainChainScriptsOrigin = frame_system::EnsureRoot<Self::AccountId>;
52+
//! type WeightInfo = pallet_native_token_management::weights::SubstrateWeight<Runtime>;
53+
//! }
54+
//! ```
55+
//!
56+
//! Keep in mind that if the handler logic has to perform storage operations, the pallet's benchmarks
57+
//! should be rerun. Otherwise default weights are provided in [pallet_native_token_management::weights].
58+
//!
59+
//! ## Script configuration
60+
//!
61+
//! For token transfers to be observed, the pallet must be configured with correct Cardano addresses and
62+
//! scripts used to idendify them in the ledger. These scripts can be set in two ways: through genesis
63+
//! configuration if the pallet is present in the initial runtime of a chain; or via a governance action
64+
//! using the [set_main_chain_scripts] extrinsic.
65+
//!
66+
//! ### Genesis configuratin
67+
//!
68+
//! Initial main chain scripts can be set in the genesis configuration, like so:
69+
//! ```json
70+
//! {
71+
//! "nativeTokenManagement": {
72+
//! "mainChainScripts": {
73+
//! "illiquid_supply_validator_address": "0x616464725f74657374317772687674767833663067397776397278386b66716336306a7661336530376e71756a6b32637370656b76346d717339726a64767a",
74+
//! "native_token_asset_name": "0x5043546f6b656e44656d6f",
75+
//! "native_token_policy_id": "0xada83ddd029614381f00e28de0922ab0dec6983ea9dd29ae20eef9b4"
76+
//! }
77+
//! }
78+
//! }
79+
//! ```
80+
//!
81+
//! Note that the `mainChainScripts` field is optional. If it is left empty, the pallet will stay inactive
82+
//! until configuration is set later.
83+
//!
84+
//! ### Main chain scripts extrinsic
85+
//!
86+
//! Once the chain is already started, to set initial main chain scripts to a newly added pallet, or to
87+
//! change the existing ones, the [set_main_chain_scripts] extrinsic must be submitted through on-chain
88+
//! governance mechanism like `sudo` or `pallet_democracy`. Who exactly can submit this extrinsic is
89+
//! controlled by the [Config::MainChainScriptsOrigin] field of the pallet's configuration, but for security
90+
//! it must be a trusted entity.
91+
//!
92+
//! #### Initialization state of the pallet
1393
//!
14-
//! IMPORTANT: The mechanism implemented via the pallet in this crate is only concerned with the
15-
//! amount of tokens moved and does not attach any metadata to the transfers. In particular
16-
//! it is not a fully-featured token bridge and needs to be combined with a separate
17-
//! sender-receiver metadata channel to implement one.
94+
//! The pallet tracks its own initialization state through the [Initialized] storage flag. This information
95+
//! is necessary for it to correctly observe historical data and the state is reset every time main chain
96+
//! scripts are changed in the pallet. This allows the Partner Chain governance to switch to new versions
97+
//! of the smart contracts. However, some consideration must be taken while changing the scripts:
98+
//! 1. This mechanism can not handle changing the main chain scripts to values that were used before.
99+
//! Doing so will cause some transfers to be registered again, resulting in potential double-spend.
100+
//! This means that a script version roll-back is not possible.
101+
//! 2. Moving funds from an old illiquid supply address to a new one requires unlocking them and re-locking
102+
//! at the new address, resulting in a new transfer being observed. The logic handling the token movement,
103+
//! including the transfer handler, must be able to handle this unlock-relock behaviour if a Partner Chain
104+
//! governance wishes to migrate tokens to the new address.
18105
//!
19106
#![cfg_attr(not(feature = "std"), no_std)]
20107
#![deny(missing_docs)]
@@ -106,15 +193,15 @@ pub mod pallet {
106193
#[derive(frame_support::DefaultNoBound)]
107194
pub struct GenesisConfig<T: Config> {
108195
/// Initial main chain scripts
109-
pub main_chain_scripts: sp_native_token_management::MainChainScripts,
196+
pub main_chain_scripts: Option<sp_native_token_management::MainChainScripts>,
110197
#[allow(missing_docs)]
111198
pub _marker: PhantomData<T>,
112199
}
113200

114201
#[pallet::genesis_build]
115202
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
116203
fn build(&self) {
117-
MainChainScriptsConfiguration::<T>::put(self.main_chain_scripts.clone());
204+
MainChainScriptsConfiguration::<T>::set(self.main_chain_scripts.clone());
118205
}
119206
}
120207

toolkit/native-token-management/pallet/src/mock.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,14 @@ impl crate::pallet::Config for Test {
8787
impl mock_pallet::Config for Test {}
8888

8989
pub fn new_test_ext() -> sp_io::TestExternalities {
90-
RuntimeGenesisConfig { system: Default::default(), native_token_management: Default::default() }
91-
.build_storage()
92-
.unwrap()
93-
.into()
90+
RuntimeGenesisConfig {
91+
system: Default::default(),
92+
native_token_management: NativeTokenManagementConfig {
93+
main_chain_scripts: Some(Default::default()),
94+
..Default::default()
95+
},
96+
}
97+
.build_storage()
98+
.unwrap()
99+
.into()
94100
}

toolkit/native-token-management/primitives/src/lib.rs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,83 @@
1+
//! Primitives and inherent data provider for the Native Token Management feature
2+
//!
3+
//! # Purpose and context
4+
//!
5+
//! This crate defines shared types used by components that implement the Native Token Management
6+
//! feature of the Partner Chains toolkit, along with an inherent data provider for token transfer
7+
//! data.
8+
//!
9+
//! The Native Token Management feature allows a Partner Chain to keep its token as a native asset
10+
//! on Cardano and have it be transferable to the Partner Chain. This is achieved by the native
11+
//! token being locked at an _illiquid supply_ address on Cardano, and the Partner Chain handling
12+
//! this locking event (a _transfer_) after it has been observed as part of a stable block.
13+
//!
14+
//! The inherent data provider defined in this crate is responsible for providing information about
15+
//! the transfers in form of inherent data, and handling them is the responsibility of the pallet,
16+
//! which allows the Partner Chain builders to define their own transfer handling logic to suit
17+
//! their needs.
18+
//!
19+
//! # Usage
20+
//!
21+
//! ## Prerequisites
22+
//!
23+
//! This features depends on the MC Reference Hash feature to provide Cardano block reference for
24+
//! querying the token transfers. See the documentation of `sidechain_mc_hash` crate for more information.
25+
//!
26+
//! ## Implementing runtime APIs
27+
//!
28+
//! [NativeTokenManagementInherentDataProvider] requires the runtime to implement the
29+
//! [NativeTokenManagementApi] runtime API. This only requires passing relevant values from the pallet:
30+
//! ```rust,ignore
31+
//! impl sp_native_token_management::NativeTokenManagementApi<Block> for Runtime {
32+
//! fn get_main_chain_scripts() -> Option<sp_native_token_management::MainChainScripts> {
33+
//! NativeTokenManagement::get_main_chain_scripts()
34+
//! }
35+
//! fn initialized() -> bool {
36+
//! NativeTokenManagement::initialized()
37+
//! }
38+
//! }
39+
//! ```
40+
//!
41+
//! ## Adding the inherent data provider
42+
//!
43+
//! The inherent data provider requires a data source implementing [NativeTokenManagementDataSource].
44+
//! A Db-Sync implementation is provided by the `partner_chains_db_sync_data_sources` crate.
45+
//!
46+
//! With the data source present, the IDP is straightfoward to create:
47+
//!
48+
//! ```rust
49+
//! use std::sync::Arc;
50+
//! use sp_runtime::traits::Block as BlockT;
51+
//! use sp_native_token_management::*;
52+
//!
53+
//! async fn create_idps<Block: BlockT, C>(
54+
//! parent_hash: Block::Hash,
55+
//! client: Arc<C>,
56+
//! native_token_data_source: &(dyn NativeTokenManagementDataSource + Send + Sync)
57+
//! ) -> Result<(NativeTokenManagementInherentDataProvider /* other IDPs */), Box<dyn std::error::Error + Send + Sync>>
58+
//! where
59+
//! C: sp_api::ProvideRuntimeApi<Block> + Send + Sync,
60+
//! C::Api: NativeTokenManagementApi<Block>
61+
//! {
62+
//! let (mc_hash, previous_mc_hash) = todo!("Should come from the MC Reference Hash feature");
63+
//!
64+
//! let native_token_idp = NativeTokenManagementInherentDataProvider::new(
65+
//! client.clone(),
66+
//! native_token_data_source,
67+
//! mc_hash,
68+
//! previous_mc_hash,
69+
//! parent_hash,
70+
//! )
71+
//! .await?;
72+
//! Ok((native_token_idp /* other IDPs */))
73+
//! }
74+
//! ```
75+
//!
76+
//! The same constructor can be used for both proposal and validation of blocks.
77+
//!
78+
//! [NativeTokenManagementApi]: sp_native_token_management::NativeTokenManagementApi
179
#![cfg_attr(not(feature = "std"), no_std)]
80+
#![deny(missing_docs)]
281

382
#[cfg(feature = "std")]
483
use {core::str::FromStr, sp_runtime::traits::Block as BlockT};
@@ -14,6 +93,7 @@ use sp_runtime::scale_info::TypeInfo;
1493
#[cfg(test)]
1594
mod tests;
1695

96+
/// Inherent identifier used by the Native Token Management pallet
1797
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"nattoken";
1898

1999
/// Values identifying on-chain entities involved in the native token management system on Cardano.
@@ -31,9 +111,15 @@ pub struct MainChainScripts {
31111

32112
#[cfg(feature = "std")]
33113
impl MainChainScripts {
114+
/// Reads the main chain script values from local environment variables
115+
///
116+
/// It expects the following variables to be set:
117+
/// - `NATIVE_TOKEN_POLICY_ID`
118+
/// - `NATIVE_TOKEN_ASSET_NAME`
119+
/// - `ILLIQUID_SUPPLY_VALIDATOR_ADDRESS`
34120
pub fn read_from_env() -> Result<Self, envy::Error> {
35121
#[derive(serde::Serialize, serde::Deserialize)]
36-
pub struct MainChainScriptsEnvConfig {
122+
struct MainChainScriptsEnvConfig {
37123
pub native_token_policy_id: PolicyId,
38124
pub native_token_asset_name: AssetName,
39125
pub illiquid_supply_validator_address: String,
@@ -57,29 +143,37 @@ impl MainChainScripts {
57143
}
58144

59145
sp_api::decl_runtime_apis! {
146+
/// Runtime API exposing configuration and initialization status of the Native Token Management pallet
60147
pub trait NativeTokenManagementApi {
148+
/// Returns the current main chain scripts configured in the pallet or [None] if they are not set.
61149
fn get_main_chain_scripts() -> Option<MainChainScripts>;
62150
/// Gets current initializaion status and set it to `true` afterwards. This check is used to
63151
/// determine whether historical data from the beginning of main chain should be queried.
64152
fn initialized() -> bool;
65153
}
66154
}
67155

156+
/// Data about token transfers in some period of time
68157
#[derive(Decode, Encode)]
69158
pub struct TokenTransferData {
159+
/// Aggregate number of tokens transfered
70160
pub token_amount: NativeTokenAmount,
71161
}
72162

163+
/// Error type returned by the Native Token Management pallet inherent logic
73164
#[derive(Encode, Debug, PartialEq)]
74165
#[cfg_attr(feature = "std", derive(Decode, thiserror::Error))]
75166
pub enum InherentError {
167+
/// Signals that no inherent was submitted despite new token transfers being observed
76168
#[cfg_attr(feature = "std", error("Inherent missing for token transfer of {0} tokens"))]
77169
TokenTransferNotHandled(NativeTokenAmount),
170+
/// Signals that the inherent registered an incorrect number of tokens transfered
78171
#[cfg_attr(
79172
feature = "std",
80173
error("Incorrect token transfer amount: expected {0}, got {1} tokens")
81174
)]
82175
IncorrectTokenNumberTransfered(NativeTokenAmount, NativeTokenAmount),
176+
/// Signals that an inherent was submitted when no token transfers were observed
83177
#[cfg_attr(feature = "std", error("Unexpected transfer of {0} tokens"))]
84178
UnexpectedTokenTransferInherent(NativeTokenAmount),
85179
}
@@ -97,6 +191,7 @@ mod inherent_provider {
97191
use std::error::Error;
98192
use std::sync::Arc;
99193

194+
/// Interface for a data source serving native token transfer data compatible with [NativeTokenManagementInherentDataProvider].
100195
#[async_trait::async_trait]
101196
pub trait NativeTokenManagementDataSource {
102197
/// Retrieves total of native token transfers into the illiquid supply in the range (after_block, to_block]
@@ -113,13 +208,17 @@ mod inherent_provider {
113208
///
114209
/// This IDP will not provide any inherent data if `token_amount` is [None], but *will* provide data for `Some(0)`.
115210
pub struct NativeTokenManagementInherentDataProvider {
211+
/// Aggregate number of tokens transfered
116212
pub token_amount: Option<NativeTokenAmount>,
117213
}
118214

215+
/// Error type returned when creation of [NativeTokenManagementInherentDataProvider] fails
119216
#[derive(thiserror::Error, sp_runtime::RuntimeDebug)]
120217
pub enum IDPCreationError {
218+
/// Signals that the data source used returned an error
121219
#[error("Failed to read native token data from data source: {0:?}")]
122220
DataSourceError(Box<dyn Error + Send + Sync>),
221+
/// Signals that a runtime API call failed
123222
#[error("Failed to call runtime API: {0:?}")]
124223
ApiError(ApiError),
125224
}
@@ -208,6 +307,7 @@ mod inherent_provider {
208307
}
209308
}
210309

310+
/// Mock implementation of the data source
211311
#[cfg(any(test, feature = "mock"))]
212312
pub mod mock {
213313
use crate::{MainChainScripts, NativeTokenManagementDataSource};
@@ -216,6 +316,7 @@ mod inherent_provider {
216316
use sidechain_domain::*;
217317
use std::collections::HashMap;
218318

319+
/// Mock implementation of [NativeTokenManagementDataSource]
219320
#[derive(new, Default)]
220321
pub struct MockNativeTokenDataSource {
221322
transfers: HashMap<(Option<McBlockHash>, McBlockHash), NativeTokenAmount>,

0 commit comments

Comments
 (0)