pallet_address_associations/
lib.rs

1//! Pallet storing associations from main chain public key to parter chain address.
2//!
3//! ## Purpose of this pallet
4//!
5//! This pallet establishes a many-to-one mapping from Cardano staking keys to Partner Chain addresses.
6//! The purpose of this mapping is primarily to indicate the local PC address to be the recipient of any
7//! block production rewards or cross-chain token transfers credited to a Cardano key holders on a Partner
8//! Chain. Some intended scenarios inlude:
9//! 1. ADA delegators become eligible for block rewards due to their stake poool's operator participating
10//!    in a Partner Chain network. The on-chain payout mechanism uses data provided by this pallet to
11//!    identify each delegator's Partner Chain address based on their Cardano staking key.
12//! 2. A Partner Chain develops its own cross-chain bridge from Cardano. A Cardano user associates their
13//!    Cardano public key with a Partner Chain address that they control. The user then uses the bridge
14//!    to send some tokens to themselves. The receiving logic in the Partner Chain ledger then uses this
15//!    pallet's data to identify the user's PC account and comlete the transfer.
16//!
17//! ## Usage - PC Builder
18//!
19//! This pallet is self-contained and doesn't need any node support.
20//! To include the pallet in the runtime, one must only provide its configuration.
21//! Consult documentation of [pallet::Config] for explanation of all configuration fields.
22//!
23//! For the pallet to verify the validity of address associations submitted by Users, it requires a signature
24//! created using the Cardano private key corresponding to the public key being associated. A dedicated
25//! CLI command for creating the signature is provided by `cli_commands` crate. Consult the crate's
26//! documentation for more information.
27//!
28//! ## Usage - PC User
29//!
30//! This pallet expoes a single extrinsic `associate_address` accepting the Cardano public key
31//! and Partner Chain address to be associated together with a signature confirming that the submitter is
32//! the owner of the associated Cardano public key.
33//!
34//! To obtain the signature, the User should use the dedicated signing command wired into the Partner Chain
35//! node executable.
36//!
37//! *Important*: For compliance reasons, all address associations are final and can not be changed.
38//!
39
40#![cfg_attr(not(feature = "std"), no_std)]
41#![deny(missing_docs)]
42
43extern crate alloc;
44
45pub use pallet::*;
46
47mod benchmarking;
48pub mod weights;
49
50#[cfg(test)]
51mod mock;
52
53#[cfg(test)]
54mod tests;
55
56use parity_scale_codec::Encode;
57use sidechain_domain::MainchainKeyHash;
58use sidechain_domain::{StakePublicKey, UtxoId};
59
60/// Schema of the message signed by a User to verify validity of submitted address association
61#[derive(Debug, Clone, Encode)]
62pub struct AddressAssociationSignedMessage<PartnerChainAddress> {
63	/// Cardano stake public key to be associated
64	pub stake_public_key: StakePublicKey,
65	/// Partner Chain address to be associated
66	pub partnerchain_address: PartnerChainAddress,
67	/// Genesis UTXO of the Partner Chain on which the association is to be registered
68	pub genesis_utxo: UtxoId,
69}
70
71/// Handler for new associations
72pub trait OnNewAssociation<PartnerChainAddress> {
73	/// Function called every time a new address association is created
74	fn on_new_association(
75		partner_chain_address: PartnerChainAddress,
76		main_chain_key_hash: MainchainKeyHash,
77	);
78}
79
80impl<PartnerChainAddress> OnNewAssociation<PartnerChainAddress> for () {
81	fn on_new_association(
82		_partner_chain_address: PartnerChainAddress,
83		_main_chain_key_hash: MainchainKeyHash,
84	) {
85	}
86}
87#[frame_support::pallet]
88pub mod pallet {
89	use super::*;
90	use crate::weights::WeightInfo;
91	use frame_support::pallet_prelude::*;
92	use frame_system::pallet_prelude::OriginFor;
93	use sidechain_domain::{MainchainKeyHash, StakeKeySignature, StakePublicKey, UtxoId};
94
95	/// Current version of the pallet
96	pub const PALLET_VERSION: u32 = 1;
97
98	#[pallet::pallet]
99	pub struct Pallet<T>(_);
100
101	#[pallet::config]
102	pub trait Config: frame_system::Config {
103		/// Weight information on extrinsic in the pallet. For convenience weights in [weights] module can be used.
104		type WeightInfo: crate::weights::WeightInfo;
105
106		/// Type representing a local PC address. This can be a standard Substrate address, an
107		/// account ID, or some address type specific to the Partner Chain.
108		type PartnerChainAddress: Member + Parameter + MaxEncodedLen;
109
110		/// Function returning the genesis UTXO of the Partner Chain.
111		/// This typically should be wired with the `genesis_utxo` function exposed by `pallet_sidechain`.
112		fn genesis_utxo() -> UtxoId;
113
114		/// Handler that is called for each new address association.
115		///
116		/// If no handling logic is needed, [()] can be used for a no-op implementation.
117		type OnNewAssociation: OnNewAssociation<Self::PartnerChainAddress>;
118	}
119
120	/// Storage of address association
121	#[pallet::storage]
122	pub type AddressAssociations<T: Config> = StorageMap<
123		Hasher = Blake2_128Concat,
124		Key = MainchainKeyHash,
125		Value = T::PartnerChainAddress,
126		QueryKind = OptionQuery,
127	>;
128
129	/// Error type returned by the pallet's extrinsic
130	#[pallet::error]
131	pub enum Error<T> {
132		/// Signals that the Cardano key is already associated
133		MainchainKeyAlreadyAssociated,
134		/// Signals an invalid Cardano key signature
135		InvalidMainchainSignature,
136	}
137
138	#[pallet::call]
139	impl<T: Config> Pallet<T> {
140		/// Extrinsic creating a new address association.
141		///
142		/// `signature` is expected to be a signature of the Cardano private key corresponding to `stake_public_key`
143		/// of [AddressAssociationSignedMessage] created using the associated public key, address and the genesis UTXO
144		/// of the particular Partner Chain it is being submitted to.
145		#[pallet::call_index(0)]
146		#[pallet::weight(T::WeightInfo::associate_address())]
147		pub fn associate_address(
148			_origin: OriginFor<T>,
149			partnerchain_address: T::PartnerChainAddress,
150			signature: StakeKeySignature,
151			stake_public_key: StakePublicKey,
152		) -> DispatchResult {
153			let genesis_utxo = T::genesis_utxo();
154
155			let stake_key_hash = stake_public_key.hash();
156
157			ensure!(
158				!AddressAssociations::<T>::contains_key(&stake_key_hash),
159				Error::<T>::MainchainKeyAlreadyAssociated
160			);
161
162			let address_association_message = AddressAssociationSignedMessage {
163				stake_public_key: stake_public_key.clone(),
164				partnerchain_address: partnerchain_address.clone(),
165				genesis_utxo,
166			};
167
168			let is_valid_signature =
169				signature.verify(&stake_public_key, &address_association_message.encode());
170
171			ensure!(is_valid_signature, Error::<T>::InvalidMainchainSignature);
172
173			AddressAssociations::<T>::insert(stake_key_hash, partnerchain_address.clone());
174
175			T::OnNewAssociation::on_new_association(partnerchain_address, stake_key_hash);
176
177			Ok(())
178		}
179	}
180
181	impl<T: Config> Pallet<T> {
182		/// Returns the current pallet version.
183		pub fn get_version() -> u32 {
184			PALLET_VERSION
185		}
186
187		/// Retrieves all main chain - partner chain address associations from the runtime storage.
188		pub fn get_all_address_associations()
189		-> impl Iterator<Item = (MainchainKeyHash, T::PartnerChainAddress)> {
190			AddressAssociations::<T>::iter()
191		}
192
193		/// Retrieves the partner chain address for a given main chain public key if the association for it exists.
194		pub fn get_partner_chain_address_for(
195			stake_public_key: &StakePublicKey,
196		) -> Option<T::PartnerChainAddress> {
197			AddressAssociations::<T>::get(stake_public_key.hash())
198		}
199	}
200}