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}