1pub mod migration;
21
22use alloc::{vec, vec::Vec};
23use core::result;
24use frame_support::{
25 dispatch::DispatchResult,
26 ensure,
27 pallet_prelude::Weight,
28 traits::{Currency, Get, ReservableCurrency},
29};
30use frame_system::{self, ensure_root, ensure_signed};
31use polkadot_primitives::{
32 HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID, MIN_CODE_SIZE,
33};
34use polkadot_runtime_parachains::{
35 configuration, ensure_parachain,
36 paras::{self, ParaGenesisArgs, UpgradeStrategy},
37 Origin, ParaLifecycle,
38};
39
40use crate::traits::{OnSwap, Registrar};
41use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
42pub use pallet::*;
43use polkadot_runtime_parachains::paras::{OnNewHead, ParaKind};
44use scale_info::TypeInfo;
45use sp_runtime::{
46 traits::{CheckedSub, Saturating},
47 RuntimeDebug,
48};
49
50#[derive(
51 Encode,
52 Decode,
53 Clone,
54 PartialEq,
55 Eq,
56 Default,
57 RuntimeDebug,
58 TypeInfo,
59 MaxEncodedLen,
60 DecodeWithMemTracking,
61)]
62pub struct ParaInfo<Account, Balance> {
63 pub manager: Account,
65 pub deposit: Balance,
67 pub locked: Option<bool>,
70}
71
72impl<Account, Balance> ParaInfo<Account, Balance> {
73 pub fn is_locked(&self) -> bool {
75 self.locked.unwrap_or(false)
76 }
77}
78
79type BalanceOf<T> =
80 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
81
82pub trait WeightInfo {
83 fn reserve() -> Weight;
84 fn register() -> Weight;
85 fn force_register() -> Weight;
86 fn deregister() -> Weight;
87 fn swap() -> Weight;
88 fn schedule_code_upgrade(b: u32) -> Weight;
89 fn set_current_head(b: u32) -> Weight;
90}
91
92pub struct TestWeightInfo;
93impl WeightInfo for TestWeightInfo {
94 fn reserve() -> Weight {
95 Weight::zero()
96 }
97 fn register() -> Weight {
98 Weight::zero()
99 }
100 fn force_register() -> Weight {
101 Weight::zero()
102 }
103 fn deregister() -> Weight {
104 Weight::zero()
105 }
106 fn swap() -> Weight {
107 Weight::zero()
108 }
109 fn schedule_code_upgrade(_b: u32) -> Weight {
110 Weight::zero()
111 }
112 fn set_current_head(_b: u32) -> Weight {
113 Weight::zero()
114 }
115}
116
117#[frame_support::pallet]
118pub mod pallet {
119 use super::*;
120 use frame_support::pallet_prelude::*;
121 use frame_system::pallet_prelude::*;
122
123 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
125
126 #[pallet::pallet]
127 #[pallet::without_storage_info]
128 #[pallet::storage_version(STORAGE_VERSION)]
129 pub struct Pallet<T>(_);
130
131 #[pallet::config]
132 #[pallet::disable_frame_system_supertrait_check]
133 pub trait Config: configuration::Config + paras::Config {
134 #[allow(deprecated)]
136 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
137
138 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
143 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
144
145 type Currency: ReservableCurrency<Self::AccountId>;
147
148 type OnSwap: crate::traits::OnSwap;
150
151 #[pallet::constant]
154 type ParaDeposit: Get<BalanceOf<Self>>;
155
156 #[pallet::constant]
158 type DataDepositPerByte: Get<BalanceOf<Self>>;
159
160 type WeightInfo: WeightInfo;
162 }
163
164 #[pallet::event]
165 #[pallet::generate_deposit(pub(super) fn deposit_event)]
166 pub enum Event<T: Config> {
167 Registered { para_id: ParaId, manager: T::AccountId },
168 Deregistered { para_id: ParaId },
169 Reserved { para_id: ParaId, who: T::AccountId },
170 Swapped { para_id: ParaId, other_id: ParaId },
171 }
172
173 #[pallet::error]
174 pub enum Error<T> {
175 NotRegistered,
177 AlreadyRegistered,
179 NotOwner,
181 CodeTooLarge,
183 HeadDataTooLarge,
185 NotParachain,
187 NotParathread,
189 CannotDeregister,
191 CannotDowngrade,
193 CannotUpgrade,
195 ParaLocked,
198 NotReserved,
200 InvalidCode,
202 CannotSwap,
205 }
206
207 #[pallet::storage]
209 pub(super) type PendingSwap<T> = StorageMap<_, Twox64Concat, ParaId, ParaId>;
210
211 #[pallet::storage]
216 pub type Paras<T: Config> =
217 StorageMap<_, Twox64Concat, ParaId, ParaInfo<T::AccountId, BalanceOf<T>>>;
218
219 #[pallet::storage]
221 pub type NextFreeParaId<T> = StorageValue<_, ParaId, ValueQuery>;
222
223 #[pallet::genesis_config]
224 pub struct GenesisConfig<T: Config> {
225 #[serde(skip)]
226 pub _config: core::marker::PhantomData<T>,
227 pub next_free_para_id: ParaId,
228 }
229
230 impl<T: Config> Default for GenesisConfig<T> {
231 fn default() -> Self {
232 GenesisConfig { next_free_para_id: LOWEST_PUBLIC_ID, _config: Default::default() }
233 }
234 }
235
236 #[pallet::genesis_build]
237 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
238 fn build(&self) {
239 NextFreeParaId::<T>::put(self.next_free_para_id);
240 }
241 }
242
243 #[pallet::hooks]
244 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
245
246 #[pallet::call]
247 impl<T: Config> Pallet<T> {
248 #[pallet::call_index(0)]
269 #[pallet::weight(<T as Config>::WeightInfo::register())]
270 pub fn register(
271 origin: OriginFor<T>,
272 id: ParaId,
273 genesis_head: HeadData,
274 validation_code: ValidationCode,
275 ) -> DispatchResult {
276 let who = ensure_signed(origin)?;
277 Self::do_register(who, None, id, genesis_head, validation_code, true)?;
278 Ok(())
279 }
280
281 #[pallet::call_index(1)]
288 #[pallet::weight(<T as Config>::WeightInfo::force_register())]
289 pub fn force_register(
290 origin: OriginFor<T>,
291 who: T::AccountId,
292 deposit: BalanceOf<T>,
293 id: ParaId,
294 genesis_head: HeadData,
295 validation_code: ValidationCode,
296 ) -> DispatchResult {
297 ensure_root(origin)?;
298 Self::do_register(who, Some(deposit), id, genesis_head, validation_code, false)
299 }
300
301 #[pallet::call_index(2)]
306 #[pallet::weight(<T as Config>::WeightInfo::deregister())]
307 pub fn deregister(origin: OriginFor<T>, id: ParaId) -> DispatchResult {
308 Self::ensure_root_para_or_owner(origin, id)?;
309 Self::do_deregister(id)
310 }
311
312 #[pallet::call_index(3)]
325 #[pallet::weight(<T as Config>::WeightInfo::swap())]
326 pub fn swap(origin: OriginFor<T>, id: ParaId, other: ParaId) -> DispatchResult {
327 Self::ensure_root_para_or_owner(origin, id)?;
328
329 if id == other {
332 PendingSwap::<T>::remove(id);
333 return Ok(());
334 }
335
336 let id_lifecycle =
338 paras::Pallet::<T>::lifecycle(id).ok_or(Error::<T>::NotRegistered)?;
339
340 if PendingSwap::<T>::get(other) == Some(id) {
341 let other_lifecycle =
342 paras::Pallet::<T>::lifecycle(other).ok_or(Error::<T>::NotRegistered)?;
343 if id_lifecycle == ParaLifecycle::Parachain &&
346 other_lifecycle == ParaLifecycle::Parathread
347 {
348 Self::do_thread_and_chain_swap(id, other);
349 } else if id_lifecycle == ParaLifecycle::Parathread &&
350 other_lifecycle == ParaLifecycle::Parachain
351 {
352 Self::do_thread_and_chain_swap(other, id);
353 } else if id_lifecycle == ParaLifecycle::Parachain &&
354 other_lifecycle == ParaLifecycle::Parachain
355 {
356 T::OnSwap::on_swap(id, other);
360 } else {
361 return Err(Error::<T>::CannotSwap.into());
362 }
363 Self::deposit_event(Event::<T>::Swapped { para_id: id, other_id: other });
364 PendingSwap::<T>::remove(other);
365 } else {
366 PendingSwap::<T>::insert(id, other);
367 }
368
369 Ok(())
370 }
371
372 #[pallet::call_index(4)]
377 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
378 pub fn remove_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
379 Self::ensure_root_or_para(origin, para)?;
380 <Self as Registrar>::remove_lock(para);
381 Ok(())
382 }
383
384 #[pallet::call_index(5)]
402 #[pallet::weight(<T as Config>::WeightInfo::reserve())]
403 pub fn reserve(origin: OriginFor<T>) -> DispatchResult {
404 let who = ensure_signed(origin)?;
405 let id = NextFreeParaId::<T>::get().max(LOWEST_PUBLIC_ID);
406 Self::do_reserve(who, None, id)?;
407 NextFreeParaId::<T>::set(id + 1);
408 Ok(())
409 }
410
411 #[pallet::call_index(6)]
417 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
418 pub fn add_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
419 Self::ensure_root_para_or_owner(origin, para)?;
420 <Self as Registrar>::apply_lock(para);
421 Ok(())
422 }
423
424 #[pallet::call_index(7)]
436 #[pallet::weight(<T as Config>::WeightInfo::schedule_code_upgrade(new_code.0.len() as u32))]
437 pub fn schedule_code_upgrade(
438 origin: OriginFor<T>,
439 para: ParaId,
440 new_code: ValidationCode,
441 ) -> DispatchResult {
442 Self::ensure_root_para_or_owner(origin, para)?;
443 polkadot_runtime_parachains::schedule_code_upgrade::<T>(
444 para,
445 new_code,
446 UpgradeStrategy::ApplyAtExpectedBlock,
447 )?;
448 Ok(())
449 }
450
451 #[pallet::call_index(8)]
456 #[pallet::weight(<T as Config>::WeightInfo::set_current_head(new_head.0.len() as u32))]
457 pub fn set_current_head(
458 origin: OriginFor<T>,
459 para: ParaId,
460 new_head: HeadData,
461 ) -> DispatchResult {
462 Self::ensure_root_para_or_owner(origin, para)?;
463 polkadot_runtime_parachains::set_current_head::<T>(para, new_head);
464 Ok(())
465 }
466 }
467}
468
469impl<T: Config> Registrar for Pallet<T> {
470 type AccountId = T::AccountId;
471
472 fn manager_of(id: ParaId) -> Option<T::AccountId> {
474 Some(Paras::<T>::get(id)?.manager)
475 }
476
477 fn parachains() -> Vec<ParaId> {
480 paras::Parachains::<T>::get()
481 }
482
483 fn is_parathread(id: ParaId) -> bool {
485 paras::Pallet::<T>::is_parathread(id)
486 }
487
488 fn is_parachain(id: ParaId) -> bool {
490 paras::Pallet::<T>::is_parachain(id)
491 }
492
493 fn apply_lock(id: ParaId) {
495 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(true)));
496 }
497
498 fn remove_lock(id: ParaId) {
500 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(false)));
501 }
502
503 fn register(
508 manager: T::AccountId,
509 id: ParaId,
510 genesis_head: HeadData,
511 validation_code: ValidationCode,
512 ) -> DispatchResult {
513 Self::do_register(manager, None, id, genesis_head, validation_code, false)
514 }
515
516 fn deregister(id: ParaId) -> DispatchResult {
518 Self::do_deregister(id)
519 }
520
521 fn make_parachain(id: ParaId) -> DispatchResult {
523 ensure!(
525 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parathread),
526 Error::<T>::NotParathread
527 );
528 polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(id)
529 .map_err(|_| Error::<T>::CannotUpgrade)?;
530
531 Ok(())
532 }
533
534 fn make_parathread(id: ParaId) -> DispatchResult {
536 ensure!(
538 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parachain),
539 Error::<T>::NotParachain
540 );
541 polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(id)
542 .map_err(|_| Error::<T>::CannotDowngrade)?;
543 Ok(())
544 }
545
546 #[cfg(any(feature = "runtime-benchmarks", test))]
547 fn worst_head_data() -> HeadData {
548 let max_head_size = configuration::ActiveConfig::<T>::get().max_head_data_size;
549 assert!(max_head_size > 0, "max_head_data can't be zero for generating worst head data.");
550 vec![0u8; max_head_size as usize].into()
551 }
552
553 #[cfg(any(feature = "runtime-benchmarks", test))]
554 fn worst_validation_code() -> ValidationCode {
555 let max_code_size = configuration::ActiveConfig::<T>::get().max_code_size;
556 assert!(max_code_size > 0, "max_code_size can't be zero for generating worst code data.");
557 let validation_code = vec![0u8; max_code_size as usize];
558 validation_code.into()
559 }
560
561 #[cfg(any(feature = "runtime-benchmarks", test))]
562 fn execute_pending_transitions() {
563 use polkadot_runtime_parachains::shared;
564 shared::Pallet::<T>::set_session_index(shared::Pallet::<T>::scheduled_session());
565 paras::Pallet::<T>::test_on_new_session();
566 }
567}
568
569impl<T: Config> Pallet<T> {
570 fn ensure_root_para_or_owner(
573 origin: <T as frame_system::Config>::RuntimeOrigin,
574 id: ParaId,
575 ) -> DispatchResult {
576 if let Ok(who) = ensure_signed(origin.clone()) {
577 let para_info = Paras::<T>::get(id).ok_or(Error::<T>::NotRegistered)?;
578
579 if para_info.manager == who {
580 ensure!(!para_info.is_locked(), Error::<T>::ParaLocked);
581 return Ok(())
582 }
583 }
584
585 Self::ensure_root_or_para(origin, id)
586 }
587
588 fn ensure_root_or_para(
590 origin: <T as frame_system::Config>::RuntimeOrigin,
591 id: ParaId,
592 ) -> DispatchResult {
593 if ensure_root(origin.clone()).is_ok() {
594 return Ok(())
595 }
596
597 let caller_id = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin))?;
598 ensure!(caller_id == id, Error::<T>::NotOwner);
600
601 Ok(())
602 }
603
604 fn do_reserve(
605 who: T::AccountId,
606 deposit_override: Option<BalanceOf<T>>,
607 id: ParaId,
608 ) -> DispatchResult {
609 ensure!(!Paras::<T>::contains_key(id), Error::<T>::AlreadyRegistered);
610 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
611
612 let deposit = deposit_override.unwrap_or_else(T::ParaDeposit::get);
613 <T as Config>::Currency::reserve(&who, deposit)?;
614 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
615
616 Paras::<T>::insert(id, info);
617 Self::deposit_event(Event::<T>::Reserved { para_id: id, who });
618 Ok(())
619 }
620
621 fn do_register(
624 who: T::AccountId,
625 deposit_override: Option<BalanceOf<T>>,
626 id: ParaId,
627 genesis_head: HeadData,
628 validation_code: ValidationCode,
629 ensure_reserved: bool,
630 ) -> DispatchResult {
631 let deposited = if let Some(para_data) = Paras::<T>::get(id) {
632 ensure!(para_data.manager == who, Error::<T>::NotOwner);
633 ensure!(!para_data.is_locked(), Error::<T>::ParaLocked);
634 para_data.deposit
635 } else {
636 ensure!(!ensure_reserved, Error::<T>::NotReserved);
637 Default::default()
638 };
639 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
640 let (genesis, deposit) =
641 Self::validate_onboarding_data(genesis_head, validation_code, ParaKind::Parathread)?;
642 let deposit = deposit_override.unwrap_or(deposit);
643
644 if let Some(additional) = deposit.checked_sub(&deposited) {
645 <T as Config>::Currency::reserve(&who, additional)?;
646 } else if let Some(rebate) = deposited.checked_sub(&deposit) {
647 <T as Config>::Currency::unreserve(&who, rebate);
648 };
649 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
650
651 Paras::<T>::insert(id, info);
652 let res = polkadot_runtime_parachains::schedule_para_initialize::<T>(id, genesis);
654 debug_assert!(res.is_ok());
655 Self::deposit_event(Event::<T>::Registered { para_id: id, manager: who });
656 Ok(())
657 }
658
659 fn do_deregister(id: ParaId) -> DispatchResult {
661 match paras::Pallet::<T>::lifecycle(id) {
662 Some(ParaLifecycle::Parathread) | None => {},
664 _ => return Err(Error::<T>::NotParathread.into()),
665 }
666 polkadot_runtime_parachains::schedule_para_cleanup::<T>(id)
667 .map_err(|_| Error::<T>::CannotDeregister)?;
668
669 if let Some(info) = Paras::<T>::take(&id) {
670 <T as Config>::Currency::unreserve(&info.manager, info.deposit);
671 }
672
673 PendingSwap::<T>::remove(id);
674 Self::deposit_event(Event::<T>::Deregistered { para_id: id });
675 Ok(())
676 }
677
678 fn validate_onboarding_data(
682 genesis_head: HeadData,
683 validation_code: ValidationCode,
684 para_kind: ParaKind,
685 ) -> Result<(ParaGenesisArgs, BalanceOf<T>), sp_runtime::DispatchError> {
686 let config = configuration::ActiveConfig::<T>::get();
687 ensure!(validation_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
688 ensure!(validation_code.0.len() <= config.max_code_size as usize, Error::<T>::CodeTooLarge);
689 ensure!(
690 genesis_head.0.len() <= config.max_head_data_size as usize,
691 Error::<T>::HeadDataTooLarge
692 );
693
694 let per_byte_fee = T::DataDepositPerByte::get();
695 let deposit = T::ParaDeposit::get()
696 .saturating_add(per_byte_fee.saturating_mul((genesis_head.0.len() as u32).into()))
697 .saturating_add(per_byte_fee.saturating_mul(config.max_code_size.into()));
698
699 Ok((ParaGenesisArgs { genesis_head, validation_code, para_kind }, deposit))
700 }
701
702 fn do_thread_and_chain_swap(to_downgrade: ParaId, to_upgrade: ParaId) {
705 let res1 = polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(to_downgrade);
706 debug_assert!(res1.is_ok());
707 let res2 = polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(to_upgrade);
708 debug_assert!(res2.is_ok());
709 T::OnSwap::on_swap(to_upgrade, to_downgrade);
710 }
711}
712
713impl<T: Config> OnNewHead for Pallet<T> {
714 fn on_new_head(id: ParaId, _head: &HeadData) -> Weight {
715 let mut writes = 0;
717 if let Some(mut info) = Paras::<T>::get(id) {
718 if info.locked.is_none() {
719 info.locked = Some(true);
720 Paras::<T>::insert(id, info);
721 writes += 1;
722 }
723 }
724 T::DbWeight::get().reads_writes(1, writes)
725 }
726}
727
728#[cfg(test)]
729mod mock;
730
731#[cfg(test)]
732mod tests;
733
734#[cfg(feature = "runtime-benchmarks")]
735mod benchmarking;