1pub mod migration;
25
26use alloc::vec::Vec;
27use core::result;
28use frame_support::{
29 dispatch::DispatchResult,
30 ensure,
31 pallet_prelude::Weight,
32 traits::{Currency, Get, ReservableCurrency},
33};
34use frame_system::{self, ensure_root, ensure_signed, pallet_prelude::BlockNumberFor};
35use polkadot_primitives::{
36 HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID, MIN_CODE_SIZE,
37};
38use polkadot_runtime_parachains::{
39 configuration, ensure_parachain,
40 paras::{self, ParaGenesisArgs, UpgradeStrategy},
41 Origin, ParaLifecycle,
42};
43
44use crate::traits::{OnSwap, Registrar};
45use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
46pub use pallet::*;
47use polkadot_runtime_parachains::paras::{OnNewHead, ParaKind};
48use scale_info::TypeInfo;
49use sp_runtime::{
50 traits::{CheckedSub, Saturating, Zero},
51 Debug,
52};
53
54#[derive(
55 Encode,
56 Decode,
57 Clone,
58 PartialEq,
59 Eq,
60 Default,
61 Debug,
62 TypeInfo,
63 MaxEncodedLen,
64 DecodeWithMemTracking,
65)]
66pub struct ParaInfo<Account, Balance> {
67 pub manager: Account,
69 pub deposit: Balance,
71 pub locked: Option<bool>,
74}
75
76impl<Account, Balance> ParaInfo<Account, Balance> {
77 pub fn is_locked(&self) -> bool {
79 self.locked.unwrap_or(false)
80 }
81}
82
83type BalanceOf<T> =
84 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
85
86pub trait WeightInfo {
87 fn reserve() -> Weight;
88 fn register() -> Weight;
89 fn force_register() -> Weight;
90 fn deregister() -> Weight;
91 fn swap() -> Weight;
92 fn schedule_code_upgrade(b: u32) -> Weight;
93 fn set_current_head(b: u32) -> Weight;
94}
95
96pub struct TestWeightInfo;
97impl WeightInfo for TestWeightInfo {
98 fn reserve() -> Weight {
99 Weight::zero()
100 }
101 fn register() -> Weight {
102 Weight::zero()
103 }
104 fn force_register() -> Weight {
105 Weight::zero()
106 }
107 fn deregister() -> Weight {
108 Weight::zero()
109 }
110 fn swap() -> Weight {
111 Weight::zero()
112 }
113 fn schedule_code_upgrade(_b: u32) -> Weight {
114 Weight::zero()
115 }
116 fn set_current_head(_b: u32) -> Weight {
117 Weight::zero()
118 }
119}
120
121#[frame_support::pallet]
122pub mod pallet {
123 use super::*;
124 use frame_support::pallet_prelude::*;
125 use frame_system::pallet_prelude::*;
126
127 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
129
130 #[pallet::pallet]
131 #[pallet::without_storage_info]
132 #[pallet::storage_version(STORAGE_VERSION)]
133 pub struct Pallet<T>(_);
134
135 #[pallet::config]
136 #[pallet::disable_frame_system_supertrait_check]
137 pub trait Config: configuration::Config + paras::Config {
138 #[allow(deprecated)]
140 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
141
142 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
147 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
148
149 type Currency: ReservableCurrency<Self::AccountId>;
151
152 type OnSwap: crate::traits::OnSwap;
154
155 #[pallet::constant]
158 type ParaDeposit: Get<BalanceOf<Self>>;
159
160 #[pallet::constant]
162 type DataDepositPerByte: Get<BalanceOf<Self>>;
163
164 type WeightInfo: WeightInfo;
166 }
167
168 #[pallet::event]
169 #[pallet::generate_deposit(pub(super) fn deposit_event)]
170 pub enum Event<T: Config> {
171 Registered { para_id: ParaId, manager: T::AccountId },
172 Deregistered { para_id: ParaId },
173 Reserved { para_id: ParaId, who: T::AccountId },
174 Swapped { para_id: ParaId, other_id: ParaId },
175 }
176
177 #[pallet::error]
178 pub enum Error<T> {
179 NotRegistered,
181 AlreadyRegistered,
183 NotOwner,
185 CodeTooLarge,
187 HeadDataTooLarge,
189 NotParachain,
191 NotParathread,
193 CannotDeregister,
195 CannotDowngrade,
197 CannotUpgrade,
199 ParaLocked,
202 NotReserved,
204 InvalidCode,
206 CannotSwap,
209 }
210
211 #[pallet::storage]
213 pub(super) type PendingSwap<T> = StorageMap<_, Twox64Concat, ParaId, ParaId>;
214
215 #[pallet::storage]
220 pub type Paras<T: Config> =
221 StorageMap<_, Twox64Concat, ParaId, ParaInfo<T::AccountId, BalanceOf<T>>>;
222
223 #[pallet::storage]
225 pub type NextFreeParaId<T> = StorageValue<_, ParaId, ValueQuery>;
226
227 #[pallet::genesis_config]
228 pub struct GenesisConfig<T: Config> {
229 #[serde(skip)]
230 pub _config: core::marker::PhantomData<T>,
231 pub next_free_para_id: ParaId,
232 }
233
234 impl<T: Config> Default for GenesisConfig<T> {
235 fn default() -> Self {
236 GenesisConfig { next_free_para_id: LOWEST_PUBLIC_ID, _config: Default::default() }
237 }
238 }
239
240 #[pallet::genesis_build]
241 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
242 fn build(&self) {
243 NextFreeParaId::<T>::put(self.next_free_para_id);
244 }
245 }
246
247 #[pallet::hooks]
248 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
249
250 #[pallet::call]
251 impl<T: Config> Pallet<T> {
252 #[pallet::call_index(0)]
273 #[pallet::weight(<T as Config>::WeightInfo::register())]
274 pub fn register(
275 origin: OriginFor<T>,
276 id: ParaId,
277 genesis_head: HeadData,
278 validation_code: ValidationCode,
279 ) -> DispatchResult {
280 let who = ensure_signed(origin)?;
281 Self::do_register(who, None, id, genesis_head, validation_code, true)?;
282 Ok(())
283 }
284
285 #[pallet::call_index(1)]
292 #[pallet::weight(<T as Config>::WeightInfo::force_register())]
293 pub fn force_register(
294 origin: OriginFor<T>,
295 who: T::AccountId,
296 deposit: BalanceOf<T>,
297 id: ParaId,
298 genesis_head: HeadData,
299 validation_code: ValidationCode,
300 ) -> DispatchResult {
301 ensure_root(origin)?;
302 Self::do_register(who, Some(deposit), id, genesis_head, validation_code, false)
303 }
304
305 #[pallet::call_index(2)]
310 #[pallet::weight(<T as Config>::WeightInfo::deregister())]
311 pub fn deregister(origin: OriginFor<T>, id: ParaId) -> DispatchResult {
312 Self::ensure_root_para_or_owner(origin, id)?;
313 Self::do_deregister(id)
314 }
315
316 #[pallet::call_index(3)]
329 #[pallet::weight(<T as Config>::WeightInfo::swap())]
330 pub fn swap(origin: OriginFor<T>, id: ParaId, other: ParaId) -> DispatchResult {
331 Self::ensure_root_para_or_owner(origin, id)?;
332
333 if id == other {
336 PendingSwap::<T>::remove(id);
337 return Ok(());
338 }
339
340 let id_lifecycle =
342 paras::Pallet::<T>::lifecycle(id).ok_or(Error::<T>::NotRegistered)?;
343
344 if PendingSwap::<T>::get(other) == Some(id) {
345 let other_lifecycle =
346 paras::Pallet::<T>::lifecycle(other).ok_or(Error::<T>::NotRegistered)?;
347 if id_lifecycle == ParaLifecycle::Parachain &&
350 other_lifecycle == ParaLifecycle::Parathread
351 {
352 Self::do_thread_and_chain_swap(id, other);
353 } else if id_lifecycle == ParaLifecycle::Parathread &&
354 other_lifecycle == ParaLifecycle::Parachain
355 {
356 Self::do_thread_and_chain_swap(other, id);
357 } else if id_lifecycle == ParaLifecycle::Parachain &&
358 other_lifecycle == ParaLifecycle::Parachain
359 {
360 T::OnSwap::on_swap(id, other);
364 } else {
365 return Err(Error::<T>::CannotSwap.into());
366 }
367 Self::deposit_event(Event::<T>::Swapped { para_id: id, other_id: other });
368 PendingSwap::<T>::remove(other);
369 } else {
370 PendingSwap::<T>::insert(id, other);
371 }
372
373 Ok(())
374 }
375
376 #[pallet::call_index(4)]
381 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
382 pub fn remove_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
383 Self::ensure_root_or_para(origin, para)?;
384 <Self as Registrar>::remove_lock(para);
385 Ok(())
386 }
387
388 #[pallet::call_index(5)]
406 #[pallet::weight(<T as Config>::WeightInfo::reserve())]
407 pub fn reserve(origin: OriginFor<T>) -> DispatchResult {
408 let who = ensure_signed(origin)?;
409 let id = NextFreeParaId::<T>::get().max(LOWEST_PUBLIC_ID);
410 Self::do_reserve(who, None, id)?;
411 NextFreeParaId::<T>::set(id + 1);
412 Ok(())
413 }
414
415 #[pallet::call_index(6)]
421 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
422 pub fn add_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
423 Self::ensure_root_para_or_owner(origin, para)?;
424 <Self as Registrar>::apply_lock(para);
425 Ok(())
426 }
427
428 #[pallet::call_index(7)]
440 #[pallet::weight(<T as Config>::WeightInfo::schedule_code_upgrade(new_code.0.len() as u32))]
441 pub fn schedule_code_upgrade(
442 origin: OriginFor<T>,
443 para: ParaId,
444 new_code: ValidationCode,
445 ) -> DispatchResult {
446 Self::ensure_root_para_or_owner(origin, para)?;
447 polkadot_runtime_parachains::schedule_code_upgrade::<T>(
448 para,
449 new_code,
450 UpgradeStrategy::ApplyAtExpectedBlock,
451 )?;
452 Ok(())
453 }
454
455 #[pallet::call_index(8)]
460 #[pallet::weight(<T as Config>::WeightInfo::set_current_head(new_head.0.len() as u32))]
461 pub fn set_current_head(
462 origin: OriginFor<T>,
463 para: ParaId,
464 new_head: HeadData,
465 ) -> DispatchResult {
466 Self::ensure_root_para_or_owner(origin, para)?;
467 polkadot_runtime_parachains::set_current_head::<T>(para, new_head);
468 Ok(())
469 }
470 }
471}
472
473impl<T: Config> Registrar for Pallet<T> {
474 type AccountId = T::AccountId;
475
476 fn manager_of(id: ParaId) -> Option<T::AccountId> {
478 Some(Paras::<T>::get(id)?.manager)
479 }
480
481 fn parachains() -> Vec<ParaId> {
484 paras::Parachains::<T>::get()
485 }
486
487 fn is_parathread(id: ParaId) -> bool {
489 paras::Pallet::<T>::is_parathread(id)
490 }
491
492 fn is_parachain(id: ParaId) -> bool {
494 paras::Pallet::<T>::is_parachain(id)
495 }
496
497 fn apply_lock(id: ParaId) {
499 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(true)));
500 }
501
502 fn remove_lock(id: ParaId) {
504 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(false)));
505 }
506
507 fn register(
512 manager: T::AccountId,
513 id: ParaId,
514 genesis_head: HeadData,
515 validation_code: ValidationCode,
516 ) -> DispatchResult {
517 Self::do_register(manager, None, id, genesis_head, validation_code, false)
518 }
519
520 fn deregister(id: ParaId) -> DispatchResult {
522 Self::do_deregister(id)
523 }
524
525 fn make_parachain(id: ParaId) -> DispatchResult {
527 ensure!(
529 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parathread),
530 Error::<T>::NotParathread
531 );
532 polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(id)
533 .map_err(|_| Error::<T>::CannotUpgrade)?;
534
535 Ok(())
536 }
537
538 fn make_parathread(id: ParaId) -> DispatchResult {
540 ensure!(
542 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parachain),
543 Error::<T>::NotParachain
544 );
545 polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(id)
546 .map_err(|_| Error::<T>::CannotDowngrade)?;
547 Ok(())
548 }
549
550 #[cfg(any(feature = "runtime-benchmarks", test))]
551 fn worst_head_data() -> HeadData {
552 let max_head_size = configuration::ActiveConfig::<T>::get().max_head_data_size;
553 assert!(max_head_size > 0, "max_head_data can't be zero for generating worst head data.");
554 alloc::vec![0u8; max_head_size as usize].into()
555 }
556
557 #[cfg(any(feature = "runtime-benchmarks", test))]
558 fn worst_validation_code() -> ValidationCode {
559 let max_code_size = configuration::ActiveConfig::<T>::get().max_code_size;
560 assert!(max_code_size > 0, "max_code_size can't be zero for generating worst code data.");
561 let validation_code = alloc::vec![0u8; max_code_size as usize];
562 validation_code.into()
563 }
564
565 #[cfg(any(feature = "runtime-benchmarks", test))]
566 fn execute_pending_transitions() {
567 use polkadot_runtime_parachains::shared;
568 shared::Pallet::<T>::set_session_index(shared::Pallet::<T>::scheduled_session());
569 paras::Pallet::<T>::test_on_new_session();
570 }
571}
572
573impl<T: Config> registrar_primitives::ParachainRegistrar for Pallet<T> {
584 type AccountId = T::AccountId;
585
586 fn check_onboarding(head_len: u32, code_len: u32) -> Result<(), ()> {
587 let config = configuration::ActiveConfig::<T>::get();
588 Self::validate_onboarding_sizes(&config, head_len as usize, code_len as usize)
589 .map_err(|_| ())
590 }
591
592 fn is_registered(para_id: u32) -> bool {
593 let id = ParaId::from(para_id);
594 Paras::<T>::contains_key(id) || paras::Pallet::<T>::lifecycle(id).is_some()
595 }
596
597 fn register(
598 manager: T::AccountId,
599 para_id: u32,
600 genesis_head: Vec<u8>,
601 validation_code: Vec<u8>,
602 ) -> DispatchResult {
603 Self::do_register(
604 manager,
605 Some(BalanceOf::<T>::zero()),
606 ParaId::from(para_id),
607 HeadData(genesis_head),
608 ValidationCode(validation_code),
609 false,
610 )
611 }
612}
613
614impl<T: Config> Pallet<T> {
615 fn ensure_root_para_or_owner(
618 origin: <T as frame_system::Config>::RuntimeOrigin,
619 id: ParaId,
620 ) -> DispatchResult {
621 if let Ok(who) = ensure_signed(origin.clone()) {
622 let para_info = Paras::<T>::get(id).ok_or(Error::<T>::NotRegistered)?;
623
624 if para_info.manager == who {
625 ensure!(!para_info.is_locked(), Error::<T>::ParaLocked);
626 return Ok(());
627 }
628 }
629
630 Self::ensure_root_or_para(origin, id)
631 }
632
633 fn ensure_root_or_para(
635 origin: <T as frame_system::Config>::RuntimeOrigin,
636 id: ParaId,
637 ) -> DispatchResult {
638 if ensure_root(origin.clone()).is_ok() {
639 return Ok(());
640 }
641
642 let caller_id = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin))?;
643 ensure!(caller_id == id, Error::<T>::NotOwner);
645
646 Ok(())
647 }
648
649 fn do_reserve(
650 who: T::AccountId,
651 deposit_override: Option<BalanceOf<T>>,
652 id: ParaId,
653 ) -> DispatchResult {
654 ensure!(!Paras::<T>::contains_key(id), Error::<T>::AlreadyRegistered);
655 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
656
657 let deposit = deposit_override.unwrap_or_else(T::ParaDeposit::get);
658 <T as Config>::Currency::reserve(&who, deposit)?;
659 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
660
661 Paras::<T>::insert(id, info);
662 Self::deposit_event(Event::<T>::Reserved { para_id: id, who });
663 Ok(())
664 }
665
666 fn do_register(
669 who: T::AccountId,
670 deposit_override: Option<BalanceOf<T>>,
671 id: ParaId,
672 genesis_head: HeadData,
673 validation_code: ValidationCode,
674 ensure_reserved: bool,
675 ) -> DispatchResult {
676 let deposited = if let Some(para_data) = Paras::<T>::get(id) {
677 ensure!(para_data.manager == who, Error::<T>::NotOwner);
678 ensure!(!para_data.is_locked(), Error::<T>::ParaLocked);
679 para_data.deposit
680 } else {
681 ensure!(!ensure_reserved, Error::<T>::NotReserved);
682 Default::default()
683 };
684 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
685 let (genesis, deposit) =
686 Self::validate_onboarding_data(genesis_head, validation_code, ParaKind::Parathread)?;
687 let deposit = deposit_override.unwrap_or(deposit);
688
689 if let Some(additional) = deposit.checked_sub(&deposited) {
690 <T as Config>::Currency::reserve(&who, additional)?;
691 } else if let Some(rebate) = deposited.checked_sub(&deposit) {
692 <T as Config>::Currency::unreserve(&who, rebate);
693 };
694 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
695
696 Paras::<T>::insert(id, info);
697 let res = polkadot_runtime_parachains::schedule_para_initialize::<T>(id, genesis);
699 debug_assert!(res.is_ok());
700 Self::deposit_event(Event::<T>::Registered { para_id: id, manager: who });
701 Ok(())
702 }
703
704 fn do_deregister(id: ParaId) -> DispatchResult {
706 match paras::Pallet::<T>::lifecycle(id) {
707 Some(ParaLifecycle::Parathread) | None => {},
709 _ => return Err(Error::<T>::NotParathread.into()),
710 }
711 polkadot_runtime_parachains::schedule_para_cleanup::<T>(id)
712 .map_err(|_| Error::<T>::CannotDeregister)?;
713
714 if let Some(info) = Paras::<T>::take(&id) {
715 <T as Config>::Currency::unreserve(&info.manager, info.deposit);
716 }
717
718 PendingSwap::<T>::remove(id);
719 Self::deposit_event(Event::<T>::Deregistered { para_id: id });
720 Ok(())
721 }
722
723 fn validate_onboarding_data(
727 genesis_head: HeadData,
728 validation_code: ValidationCode,
729 para_kind: ParaKind,
730 ) -> Result<(ParaGenesisArgs, BalanceOf<T>), sp_runtime::DispatchError> {
731 let config = configuration::ActiveConfig::<T>::get();
732 Self::validate_onboarding_sizes(&config, genesis_head.0.len(), validation_code.0.len())?;
733
734 let per_byte_fee = T::DataDepositPerByte::get();
735 let deposit = T::ParaDeposit::get()
736 .saturating_add(per_byte_fee.saturating_mul((genesis_head.0.len() as u32).into()))
737 .saturating_add(per_byte_fee.saturating_mul(config.max_code_size.into()));
738
739 Ok((ParaGenesisArgs { genesis_head, validation_code, para_kind }, deposit))
740 }
741
742 fn validate_onboarding_sizes(
744 config: &configuration::HostConfiguration<BlockNumberFor<T>>,
745 head_len: usize,
746 code_len: usize,
747 ) -> DispatchResult {
748 ensure!(code_len >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
749 ensure!(code_len <= config.max_code_size as usize, Error::<T>::CodeTooLarge);
750 ensure!(head_len <= config.max_head_data_size as usize, Error::<T>::HeadDataTooLarge);
751 Ok(())
752 }
753
754 fn do_thread_and_chain_swap(to_downgrade: ParaId, to_upgrade: ParaId) {
757 let res1 = polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(to_downgrade);
758 debug_assert!(res1.is_ok());
759 let res2 = polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(to_upgrade);
760 debug_assert!(res2.is_ok());
761 T::OnSwap::on_swap(to_upgrade, to_downgrade);
762 }
763}
764
765impl<T: Config> OnNewHead for Pallet<T> {
766 fn on_new_head(id: ParaId, _head: &HeadData) -> Weight {
767 let mut writes = 0;
769 if let Some(mut info) = Paras::<T>::get(id) {
770 if info.locked.is_none() {
771 info.locked = Some(true);
772 Paras::<T>::insert(id, info);
773 writes += 1;
774 }
775 }
776 T::DbWeight::get().reads_writes(1, writes)
777 }
778}
779
780#[cfg(test)]
781mod mock;
782
783#[cfg(test)]
784mod tests;
785
786#[cfg(feature = "runtime-benchmarks")]
787mod benchmarking;