pallet_partner_chains_bridge/
lib.rs

1//! Pallet that tracks information about incoming token bridge transfers observed on Cardano.
2#![cfg_attr(not(feature = "std"), no_std)]
3#![deny(missing_docs)]
4
5extern crate alloc;
6
7#[cfg(test)]
8mod tests;
9
10#[cfg(test)]
11mod mock;
12
13/// Pallet benchmarking code
14#[cfg(feature = "runtime-benchmarks")]
15pub mod benchmarking;
16
17/// Weight types and default weight values
18pub mod weights;
19
20pub use pallet::*;
21use sp_partner_chains_bridge::BridgeTransferV1;
22
23/// Runtime logic for handling incoming token bridge transfers from Cardano
24///
25/// The chain builder should implement in accordance with their particular business rules and
26/// ledger structure. Calls to all functions defined by this trait should not return any errors
27/// as this would fail the block creation. Instead, any validation and business logic errors
28/// should be handled gracefully inside the handler code.
29pub trait TransferHandler<Recipient> {
30	/// Should handle an incoming token transfer of `token_mount` tokens to `recipient`
31	fn handle_incoming_transfer(_transfer: BridgeTransferV1<Recipient>);
32}
33
34/// No-op implementation of `TransferHandler` for unit type.
35impl<Recipient> TransferHandler<Recipient> for () {
36	fn handle_incoming_transfer(_transfer: BridgeTransferV1<Recipient>) {}
37}
38
39#[frame_support::pallet(dev_mode)]
40pub mod pallet {
41	use super::*;
42	use frame_support::pallet_prelude::*;
43	use frame_system::{ensure_none, pallet_prelude::OriginFor};
44	use parity_scale_codec::MaxEncodedLen;
45	use sp_partner_chains_bridge::{BridgeDataCheckpoint, TokenBridgeTransfersV1};
46	use sp_partner_chains_bridge::{INHERENT_IDENTIFIER, InherentError, MainChainScripts};
47
48	/// Current version of the pallet
49	pub const PALLET_VERSION: u32 = 1;
50
51	#[pallet::pallet]
52	pub struct Pallet<T>(_);
53
54	#[pallet::config]
55	pub trait Config: frame_system::Config {
56		/// Origin for governance extrinsic calls.
57		///
58		/// Typically the `EnsureRoot` type can be used unless a non-standard on-chain governance is used.
59		type GovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
60
61		/// Transfer recipient
62		type Recipient: Member + Parameter + MaxEncodedLen;
63
64		/// Handler for incoming token transfers
65		type TransferHandler: TransferHandler<Self::Recipient>;
66
67		/// Maximum number of transfers that can be handled in one block for each transfer type
68		type MaxTransfersPerBlock: Get<u32>;
69
70		/// Extrinsic weight information
71		type WeightInfo: crate::weights::WeightInfo;
72
73		/// Benchmark helper type used for running benchmarks
74		#[cfg(feature = "runtime-benchmarks")]
75		type BenchmarkHelper: benchmarking::BenchmarkHelper<Self>;
76	}
77
78	/// Error type used by the pallet's extrinsics
79	#[pallet::error]
80	pub enum Error<T> {}
81
82	#[pallet::storage]
83	pub type MainChainScriptsConfiguration<T: Config> =
84		StorageValue<_, MainChainScripts, OptionQuery>;
85
86	#[pallet::storage]
87	pub type DataCheckpoint<T: Config> = StorageValue<_, BridgeDataCheckpoint, OptionQuery>;
88
89	/// Genesis configuration of the pallet
90	#[pallet::genesis_config]
91	pub struct GenesisConfig<T: Config> {
92		/// Initial main chain scripts
93		pub main_chain_scripts: Option<MainChainScripts>,
94		#[allow(missing_docs)]
95		pub _marker: PhantomData<T>,
96	}
97
98	impl<T: Config> Default for GenesisConfig<T> {
99		fn default() -> Self {
100			Self { main_chain_scripts: None, _marker: Default::default() }
101		}
102	}
103
104	#[pallet::genesis_build]
105	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
106		fn build(&self) {
107			MainChainScriptsConfiguration::<T>::set(self.main_chain_scripts.clone());
108		}
109	}
110
111	#[pallet::call]
112	impl<T: Config> Pallet<T> {
113		/// Inherent extrinsic that handles all incoming transfers in the current block
114		#[pallet::call_index(0)]
115		#[pallet::weight(0)]
116		pub fn handle_transfers(
117			origin: OriginFor<T>,
118			transfers: BoundedVec<BridgeTransferV1<T::Recipient>, T::MaxTransfersPerBlock>,
119			data_checkpoint: BridgeDataCheckpoint,
120		) -> DispatchResult {
121			ensure_none(origin)?;
122			for transfer in transfers {
123				T::TransferHandler::handle_incoming_transfer(transfer);
124			}
125			DataCheckpoint::<T>::put(data_checkpoint);
126			Ok(())
127		}
128
129		/// Changes the main chain scripts used for observing native token transfers along with a new data checkpoint.
130		///
131		/// This extrinsic must be run either using `sudo` or some other chain governance mechanism.
132		///
133		///
134		#[pallet::call_index(1)]
135		#[pallet::weight(0)]
136		pub fn set_main_chain_scripts(
137			origin: OriginFor<T>,
138			new_scripts: MainChainScripts,
139			data_checkpoint: BridgeDataCheckpoint,
140		) -> DispatchResult {
141			T::GovernanceOrigin::ensure_origin(origin)?;
142			MainChainScriptsConfiguration::<T>::put(new_scripts);
143			DataCheckpoint::<T>::put(data_checkpoint);
144			Ok(())
145		}
146	}
147
148	#[pallet::inherent]
149	impl<T: Config> ProvideInherent for Pallet<T> {
150		type Call = Call<T>;
151		type Error = InherentError;
152		const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
153
154		fn create_inherent(data: &InherentData) -> Option<Self::Call> {
155			let data = Self::decode_inherent_data(data)?;
156			let transfers = data.transfers.try_into().expect(
157				"The number of transfers in the inherent data must be within configured bounds",
158			);
159			Some(Call::handle_transfers { transfers, data_checkpoint: data.data_checkpoint })
160		}
161
162		fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
163			let Some(expected_call) = Self::create_inherent(data) else {
164				return Err(Self::Error::InherentNotExpected);
165			};
166
167			if *call != expected_call {
168				return Err(Self::Error::IncorrectInherent);
169			}
170
171			Ok(())
172		}
173
174		fn is_inherent(call: &Self::Call) -> bool {
175			matches!(call, Call::handle_transfers { .. })
176		}
177
178		fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
179			match Self::decode_inherent_data(data) {
180				None => Ok(None),
181				Some(_) => Ok(Some(Self::Error::InherentRequired)),
182			}
183		}
184	}
185
186	impl<T: Config> Pallet<T> {
187		fn decode_inherent_data(
188			data: &InherentData,
189		) -> Option<TokenBridgeTransfersV1<T::Recipient>> {
190			data.get_data(&INHERENT_IDENTIFIER)
191				.expect("Bridge inherent data is not encoded correctly")
192		}
193	}
194
195	impl<T: Config> Pallet<T> {
196		/// Returns current pallet version
197		pub fn get_pallet_version() -> u32 {
198			PALLET_VERSION
199		}
200
201		/// Returns the currently configured main chain scripts
202		pub fn get_main_chain_scripts() -> Option<MainChainScripts> {
203			MainChainScriptsConfiguration::<T>::get()
204		}
205
206		/// Returns the currently configured transfers per block limit
207		pub fn get_max_transfers_per_block() -> u32 {
208			T::MaxTransfersPerBlock::get()
209		}
210
211		/// Returns the current data checkpoint
212		pub fn get_data_checkpoint() -> Option<BridgeDataCheckpoint> {
213			DataCheckpoint::<T>::get()
214		}
215	}
216}