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, Encode};
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(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, TypeInfo)]
51pub struct ParaInfo<Account, Balance> {
52 pub(crate) manager: Account,
54 deposit: Balance,
56 locked: Option<bool>,
59}
60
61impl<Account, Balance> ParaInfo<Account, Balance> {
62 pub fn is_locked(&self) -> bool {
64 self.locked.unwrap_or(false)
65 }
66}
67
68type BalanceOf<T> =
69 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
70
71pub trait WeightInfo {
72 fn reserve() -> Weight;
73 fn register() -> Weight;
74 fn force_register() -> Weight;
75 fn deregister() -> Weight;
76 fn swap() -> Weight;
77 fn schedule_code_upgrade(b: u32) -> Weight;
78 fn set_current_head(b: u32) -> Weight;
79}
80
81pub struct TestWeightInfo;
82impl WeightInfo for TestWeightInfo {
83 fn reserve() -> Weight {
84 Weight::zero()
85 }
86 fn register() -> Weight {
87 Weight::zero()
88 }
89 fn force_register() -> Weight {
90 Weight::zero()
91 }
92 fn deregister() -> Weight {
93 Weight::zero()
94 }
95 fn swap() -> Weight {
96 Weight::zero()
97 }
98 fn schedule_code_upgrade(_b: u32) -> Weight {
99 Weight::zero()
100 }
101 fn set_current_head(_b: u32) -> Weight {
102 Weight::zero()
103 }
104}
105
106#[frame_support::pallet]
107pub mod pallet {
108 use super::*;
109 use frame_support::pallet_prelude::*;
110 use frame_system::pallet_prelude::*;
111
112 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
114
115 #[pallet::pallet]
116 #[pallet::without_storage_info]
117 #[pallet::storage_version(STORAGE_VERSION)]
118 pub struct Pallet<T>(_);
119
120 #[pallet::config]
121 #[pallet::disable_frame_system_supertrait_check]
122 pub trait Config: configuration::Config + paras::Config {
123 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
125
126 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
131 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
132
133 type Currency: ReservableCurrency<Self::AccountId>;
135
136 type OnSwap: crate::traits::OnSwap;
138
139 #[pallet::constant]
142 type ParaDeposit: Get<BalanceOf<Self>>;
143
144 #[pallet::constant]
146 type DataDepositPerByte: Get<BalanceOf<Self>>;
147
148 type WeightInfo: WeightInfo;
150 }
151
152 #[pallet::event]
153 #[pallet::generate_deposit(pub(super) fn deposit_event)]
154 pub enum Event<T: Config> {
155 Registered { para_id: ParaId, manager: T::AccountId },
156 Deregistered { para_id: ParaId },
157 Reserved { para_id: ParaId, who: T::AccountId },
158 Swapped { para_id: ParaId, other_id: ParaId },
159 }
160
161 #[pallet::error]
162 pub enum Error<T> {
163 NotRegistered,
165 AlreadyRegistered,
167 NotOwner,
169 CodeTooLarge,
171 HeadDataTooLarge,
173 NotParachain,
175 NotParathread,
177 CannotDeregister,
179 CannotDowngrade,
181 CannotUpgrade,
183 ParaLocked,
186 NotReserved,
188 InvalidCode,
190 CannotSwap,
193 }
194
195 #[pallet::storage]
197 pub(super) type PendingSwap<T> = StorageMap<_, Twox64Concat, ParaId, ParaId>;
198
199 #[pallet::storage]
204 pub type Paras<T: Config> =
205 StorageMap<_, Twox64Concat, ParaId, ParaInfo<T::AccountId, BalanceOf<T>>>;
206
207 #[pallet::storage]
209 pub type NextFreeParaId<T> = StorageValue<_, ParaId, ValueQuery>;
210
211 #[pallet::genesis_config]
212 pub struct GenesisConfig<T: Config> {
213 #[serde(skip)]
214 pub _config: core::marker::PhantomData<T>,
215 pub next_free_para_id: ParaId,
216 }
217
218 impl<T: Config> Default for GenesisConfig<T> {
219 fn default() -> Self {
220 GenesisConfig { next_free_para_id: LOWEST_PUBLIC_ID, _config: Default::default() }
221 }
222 }
223
224 #[pallet::genesis_build]
225 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
226 fn build(&self) {
227 NextFreeParaId::<T>::put(self.next_free_para_id);
228 }
229 }
230
231 #[pallet::hooks]
232 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
233
234 #[pallet::call]
235 impl<T: Config> Pallet<T> {
236 #[pallet::call_index(0)]
257 #[pallet::weight(<T as Config>::WeightInfo::register())]
258 pub fn register(
259 origin: OriginFor<T>,
260 id: ParaId,
261 genesis_head: HeadData,
262 validation_code: ValidationCode,
263 ) -> DispatchResult {
264 let who = ensure_signed(origin)?;
265 Self::do_register(who, None, id, genesis_head, validation_code, true)?;
266 Ok(())
267 }
268
269 #[pallet::call_index(1)]
276 #[pallet::weight(<T as Config>::WeightInfo::force_register())]
277 pub fn force_register(
278 origin: OriginFor<T>,
279 who: T::AccountId,
280 deposit: BalanceOf<T>,
281 id: ParaId,
282 genesis_head: HeadData,
283 validation_code: ValidationCode,
284 ) -> DispatchResult {
285 ensure_root(origin)?;
286 Self::do_register(who, Some(deposit), id, genesis_head, validation_code, false)
287 }
288
289 #[pallet::call_index(2)]
294 #[pallet::weight(<T as Config>::WeightInfo::deregister())]
295 pub fn deregister(origin: OriginFor<T>, id: ParaId) -> DispatchResult {
296 Self::ensure_root_para_or_owner(origin, id)?;
297 Self::do_deregister(id)
298 }
299
300 #[pallet::call_index(3)]
313 #[pallet::weight(<T as Config>::WeightInfo::swap())]
314 pub fn swap(origin: OriginFor<T>, id: ParaId, other: ParaId) -> DispatchResult {
315 Self::ensure_root_para_or_owner(origin, id)?;
316
317 if id == other {
320 PendingSwap::<T>::remove(id);
321 return Ok(());
322 }
323
324 let id_lifecycle =
326 paras::Pallet::<T>::lifecycle(id).ok_or(Error::<T>::NotRegistered)?;
327
328 if PendingSwap::<T>::get(other) == Some(id) {
329 let other_lifecycle =
330 paras::Pallet::<T>::lifecycle(other).ok_or(Error::<T>::NotRegistered)?;
331 if id_lifecycle == ParaLifecycle::Parachain &&
334 other_lifecycle == ParaLifecycle::Parathread
335 {
336 Self::do_thread_and_chain_swap(id, other);
337 } else if id_lifecycle == ParaLifecycle::Parathread &&
338 other_lifecycle == ParaLifecycle::Parachain
339 {
340 Self::do_thread_and_chain_swap(other, id);
341 } else if id_lifecycle == ParaLifecycle::Parachain &&
342 other_lifecycle == ParaLifecycle::Parachain
343 {
344 T::OnSwap::on_swap(id, other);
348 } else {
349 return Err(Error::<T>::CannotSwap.into());
350 }
351 Self::deposit_event(Event::<T>::Swapped { para_id: id, other_id: other });
352 PendingSwap::<T>::remove(other);
353 } else {
354 PendingSwap::<T>::insert(id, other);
355 }
356
357 Ok(())
358 }
359
360 #[pallet::call_index(4)]
365 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
366 pub fn remove_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
367 Self::ensure_root_or_para(origin, para)?;
368 <Self as Registrar>::remove_lock(para);
369 Ok(())
370 }
371
372 #[pallet::call_index(5)]
390 #[pallet::weight(<T as Config>::WeightInfo::reserve())]
391 pub fn reserve(origin: OriginFor<T>) -> DispatchResult {
392 let who = ensure_signed(origin)?;
393 let id = NextFreeParaId::<T>::get().max(LOWEST_PUBLIC_ID);
394 Self::do_reserve(who, None, id)?;
395 NextFreeParaId::<T>::set(id + 1);
396 Ok(())
397 }
398
399 #[pallet::call_index(6)]
405 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
406 pub fn add_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
407 Self::ensure_root_para_or_owner(origin, para)?;
408 <Self as Registrar>::apply_lock(para);
409 Ok(())
410 }
411
412 #[pallet::call_index(7)]
424 #[pallet::weight(<T as Config>::WeightInfo::schedule_code_upgrade(new_code.0.len() as u32))]
425 pub fn schedule_code_upgrade(
426 origin: OriginFor<T>,
427 para: ParaId,
428 new_code: ValidationCode,
429 ) -> DispatchResult {
430 Self::ensure_root_para_or_owner(origin, para)?;
431 polkadot_runtime_parachains::schedule_code_upgrade::<T>(
432 para,
433 new_code,
434 UpgradeStrategy::ApplyAtExpectedBlock,
435 )?;
436 Ok(())
437 }
438
439 #[pallet::call_index(8)]
444 #[pallet::weight(<T as Config>::WeightInfo::set_current_head(new_head.0.len() as u32))]
445 pub fn set_current_head(
446 origin: OriginFor<T>,
447 para: ParaId,
448 new_head: HeadData,
449 ) -> DispatchResult {
450 Self::ensure_root_para_or_owner(origin, para)?;
451 polkadot_runtime_parachains::set_current_head::<T>(para, new_head);
452 Ok(())
453 }
454 }
455}
456
457impl<T: Config> Registrar for Pallet<T> {
458 type AccountId = T::AccountId;
459
460 fn manager_of(id: ParaId) -> Option<T::AccountId> {
462 Some(Paras::<T>::get(id)?.manager)
463 }
464
465 fn parachains() -> Vec<ParaId> {
468 paras::Parachains::<T>::get()
469 }
470
471 fn is_parathread(id: ParaId) -> bool {
473 paras::Pallet::<T>::is_parathread(id)
474 }
475
476 fn is_parachain(id: ParaId) -> bool {
478 paras::Pallet::<T>::is_parachain(id)
479 }
480
481 fn apply_lock(id: ParaId) {
483 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(true)));
484 }
485
486 fn remove_lock(id: ParaId) {
488 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(false)));
489 }
490
491 fn register(
496 manager: T::AccountId,
497 id: ParaId,
498 genesis_head: HeadData,
499 validation_code: ValidationCode,
500 ) -> DispatchResult {
501 Self::do_register(manager, None, id, genesis_head, validation_code, false)
502 }
503
504 fn deregister(id: ParaId) -> DispatchResult {
506 Self::do_deregister(id)
507 }
508
509 fn make_parachain(id: ParaId) -> DispatchResult {
511 ensure!(
513 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parathread),
514 Error::<T>::NotParathread
515 );
516 polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(id)
517 .map_err(|_| Error::<T>::CannotUpgrade)?;
518
519 Ok(())
520 }
521
522 fn make_parathread(id: ParaId) -> DispatchResult {
524 ensure!(
526 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parachain),
527 Error::<T>::NotParachain
528 );
529 polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(id)
530 .map_err(|_| Error::<T>::CannotDowngrade)?;
531 Ok(())
532 }
533
534 #[cfg(any(feature = "runtime-benchmarks", test))]
535 fn worst_head_data() -> HeadData {
536 let max_head_size = configuration::ActiveConfig::<T>::get().max_head_data_size;
537 assert!(max_head_size > 0, "max_head_data can't be zero for generating worst head data.");
538 vec![0u8; max_head_size as usize].into()
539 }
540
541 #[cfg(any(feature = "runtime-benchmarks", test))]
542 fn worst_validation_code() -> ValidationCode {
543 let max_code_size = configuration::ActiveConfig::<T>::get().max_code_size;
544 assert!(max_code_size > 0, "max_code_size can't be zero for generating worst code data.");
545 let validation_code = vec![0u8; max_code_size as usize];
546 validation_code.into()
547 }
548
549 #[cfg(any(feature = "runtime-benchmarks", test))]
550 fn execute_pending_transitions() {
551 use polkadot_runtime_parachains::shared;
552 shared::Pallet::<T>::set_session_index(shared::Pallet::<T>::scheduled_session());
553 paras::Pallet::<T>::test_on_new_session();
554 }
555}
556
557impl<T: Config> Pallet<T> {
558 fn ensure_root_para_or_owner(
561 origin: <T as frame_system::Config>::RuntimeOrigin,
562 id: ParaId,
563 ) -> DispatchResult {
564 if let Ok(who) = ensure_signed(origin.clone()) {
565 let para_info = Paras::<T>::get(id).ok_or(Error::<T>::NotRegistered)?;
566
567 if para_info.manager == who {
568 ensure!(!para_info.is_locked(), Error::<T>::ParaLocked);
569 return Ok(())
570 }
571 }
572
573 Self::ensure_root_or_para(origin, id)
574 }
575
576 fn ensure_root_or_para(
578 origin: <T as frame_system::Config>::RuntimeOrigin,
579 id: ParaId,
580 ) -> DispatchResult {
581 if ensure_root(origin.clone()).is_ok() {
582 return Ok(())
583 }
584
585 let caller_id = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin))?;
586 ensure!(caller_id == id, Error::<T>::NotOwner);
588
589 Ok(())
590 }
591
592 fn do_reserve(
593 who: T::AccountId,
594 deposit_override: Option<BalanceOf<T>>,
595 id: ParaId,
596 ) -> DispatchResult {
597 ensure!(!Paras::<T>::contains_key(id), Error::<T>::AlreadyRegistered);
598 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
599
600 let deposit = deposit_override.unwrap_or_else(T::ParaDeposit::get);
601 <T as Config>::Currency::reserve(&who, deposit)?;
602 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
603
604 Paras::<T>::insert(id, info);
605 Self::deposit_event(Event::<T>::Reserved { para_id: id, who });
606 Ok(())
607 }
608
609 fn do_register(
612 who: T::AccountId,
613 deposit_override: Option<BalanceOf<T>>,
614 id: ParaId,
615 genesis_head: HeadData,
616 validation_code: ValidationCode,
617 ensure_reserved: bool,
618 ) -> DispatchResult {
619 let deposited = if let Some(para_data) = Paras::<T>::get(id) {
620 ensure!(para_data.manager == who, Error::<T>::NotOwner);
621 ensure!(!para_data.is_locked(), Error::<T>::ParaLocked);
622 para_data.deposit
623 } else {
624 ensure!(!ensure_reserved, Error::<T>::NotReserved);
625 Default::default()
626 };
627 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
628 let (genesis, deposit) =
629 Self::validate_onboarding_data(genesis_head, validation_code, ParaKind::Parathread)?;
630 let deposit = deposit_override.unwrap_or(deposit);
631
632 if let Some(additional) = deposit.checked_sub(&deposited) {
633 <T as Config>::Currency::reserve(&who, additional)?;
634 } else if let Some(rebate) = deposited.checked_sub(&deposit) {
635 <T as Config>::Currency::unreserve(&who, rebate);
636 };
637 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
638
639 Paras::<T>::insert(id, info);
640 let res = polkadot_runtime_parachains::schedule_para_initialize::<T>(id, genesis);
642 debug_assert!(res.is_ok());
643 Self::deposit_event(Event::<T>::Registered { para_id: id, manager: who });
644 Ok(())
645 }
646
647 fn do_deregister(id: ParaId) -> DispatchResult {
649 match paras::Pallet::<T>::lifecycle(id) {
650 Some(ParaLifecycle::Parathread) | None => {},
652 _ => return Err(Error::<T>::NotParathread.into()),
653 }
654 polkadot_runtime_parachains::schedule_para_cleanup::<T>(id)
655 .map_err(|_| Error::<T>::CannotDeregister)?;
656
657 if let Some(info) = Paras::<T>::take(&id) {
658 <T as Config>::Currency::unreserve(&info.manager, info.deposit);
659 }
660
661 PendingSwap::<T>::remove(id);
662 Self::deposit_event(Event::<T>::Deregistered { para_id: id });
663 Ok(())
664 }
665
666 fn validate_onboarding_data(
670 genesis_head: HeadData,
671 validation_code: ValidationCode,
672 para_kind: ParaKind,
673 ) -> Result<(ParaGenesisArgs, BalanceOf<T>), sp_runtime::DispatchError> {
674 let config = configuration::ActiveConfig::<T>::get();
675 ensure!(validation_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
676 ensure!(validation_code.0.len() <= config.max_code_size as usize, Error::<T>::CodeTooLarge);
677 ensure!(
678 genesis_head.0.len() <= config.max_head_data_size as usize,
679 Error::<T>::HeadDataTooLarge
680 );
681
682 let per_byte_fee = T::DataDepositPerByte::get();
683 let deposit = T::ParaDeposit::get()
684 .saturating_add(per_byte_fee.saturating_mul((genesis_head.0.len() as u32).into()))
685 .saturating_add(per_byte_fee.saturating_mul(config.max_code_size.into()));
686
687 Ok((ParaGenesisArgs { genesis_head, validation_code, para_kind }, deposit))
688 }
689
690 fn do_thread_and_chain_swap(to_downgrade: ParaId, to_upgrade: ParaId) {
693 let res1 = polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(to_downgrade);
694 debug_assert!(res1.is_ok());
695 let res2 = polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(to_upgrade);
696 debug_assert!(res2.is_ok());
697 T::OnSwap::on_swap(to_upgrade, to_downgrade);
698 }
699}
700
701impl<T: Config> OnNewHead for Pallet<T> {
702 fn on_new_head(id: ParaId, _head: &HeadData) -> Weight {
703 let mut writes = 0;
705 if let Some(mut info) = Paras::<T>::get(id) {
706 if info.locked.is_none() {
707 info.locked = Some(true);
708 Paras::<T>::insert(id, info);
709 writes += 1;
710 }
711 }
712 T::DbWeight::get().reads_writes(1, writes)
713 }
714}
715
716#[cfg(test)]
717mod mock;
718
719#[cfg(test)]
720mod tests;
721
722#[cfg(feature = "runtime-benchmarks")]
723mod benchmarking;