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 #[allow(deprecated)]
125 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
126
127 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
132 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
133
134 type Currency: ReservableCurrency<Self::AccountId>;
136
137 type OnSwap: crate::traits::OnSwap;
139
140 #[pallet::constant]
143 type ParaDeposit: Get<BalanceOf<Self>>;
144
145 #[pallet::constant]
147 type DataDepositPerByte: Get<BalanceOf<Self>>;
148
149 type WeightInfo: WeightInfo;
151 }
152
153 #[pallet::event]
154 #[pallet::generate_deposit(pub(super) fn deposit_event)]
155 pub enum Event<T: Config> {
156 Registered { para_id: ParaId, manager: T::AccountId },
157 Deregistered { para_id: ParaId },
158 Reserved { para_id: ParaId, who: T::AccountId },
159 Swapped { para_id: ParaId, other_id: ParaId },
160 }
161
162 #[pallet::error]
163 pub enum Error<T> {
164 NotRegistered,
166 AlreadyRegistered,
168 NotOwner,
170 CodeTooLarge,
172 HeadDataTooLarge,
174 NotParachain,
176 NotParathread,
178 CannotDeregister,
180 CannotDowngrade,
182 CannotUpgrade,
184 ParaLocked,
187 NotReserved,
189 InvalidCode,
191 CannotSwap,
194 }
195
196 #[pallet::storage]
198 pub(super) type PendingSwap<T> = StorageMap<_, Twox64Concat, ParaId, ParaId>;
199
200 #[pallet::storage]
205 pub type Paras<T: Config> =
206 StorageMap<_, Twox64Concat, ParaId, ParaInfo<T::AccountId, BalanceOf<T>>>;
207
208 #[pallet::storage]
210 pub type NextFreeParaId<T> = StorageValue<_, ParaId, ValueQuery>;
211
212 #[pallet::genesis_config]
213 pub struct GenesisConfig<T: Config> {
214 #[serde(skip)]
215 pub _config: core::marker::PhantomData<T>,
216 pub next_free_para_id: ParaId,
217 }
218
219 impl<T: Config> Default for GenesisConfig<T> {
220 fn default() -> Self {
221 GenesisConfig { next_free_para_id: LOWEST_PUBLIC_ID, _config: Default::default() }
222 }
223 }
224
225 #[pallet::genesis_build]
226 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
227 fn build(&self) {
228 NextFreeParaId::<T>::put(self.next_free_para_id);
229 }
230 }
231
232 #[pallet::hooks]
233 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
234
235 #[pallet::call]
236 impl<T: Config> Pallet<T> {
237 #[pallet::call_index(0)]
258 #[pallet::weight(<T as Config>::WeightInfo::register())]
259 pub fn register(
260 origin: OriginFor<T>,
261 id: ParaId,
262 genesis_head: HeadData,
263 validation_code: ValidationCode,
264 ) -> DispatchResult {
265 let who = ensure_signed(origin)?;
266 Self::do_register(who, None, id, genesis_head, validation_code, true)?;
267 Ok(())
268 }
269
270 #[pallet::call_index(1)]
277 #[pallet::weight(<T as Config>::WeightInfo::force_register())]
278 pub fn force_register(
279 origin: OriginFor<T>,
280 who: T::AccountId,
281 deposit: BalanceOf<T>,
282 id: ParaId,
283 genesis_head: HeadData,
284 validation_code: ValidationCode,
285 ) -> DispatchResult {
286 ensure_root(origin)?;
287 Self::do_register(who, Some(deposit), id, genesis_head, validation_code, false)
288 }
289
290 #[pallet::call_index(2)]
295 #[pallet::weight(<T as Config>::WeightInfo::deregister())]
296 pub fn deregister(origin: OriginFor<T>, id: ParaId) -> DispatchResult {
297 Self::ensure_root_para_or_owner(origin, id)?;
298 Self::do_deregister(id)
299 }
300
301 #[pallet::call_index(3)]
314 #[pallet::weight(<T as Config>::WeightInfo::swap())]
315 pub fn swap(origin: OriginFor<T>, id: ParaId, other: ParaId) -> DispatchResult {
316 Self::ensure_root_para_or_owner(origin, id)?;
317
318 if id == other {
321 PendingSwap::<T>::remove(id);
322 return Ok(());
323 }
324
325 let id_lifecycle =
327 paras::Pallet::<T>::lifecycle(id).ok_or(Error::<T>::NotRegistered)?;
328
329 if PendingSwap::<T>::get(other) == Some(id) {
330 let other_lifecycle =
331 paras::Pallet::<T>::lifecycle(other).ok_or(Error::<T>::NotRegistered)?;
332 if id_lifecycle == ParaLifecycle::Parachain &&
335 other_lifecycle == ParaLifecycle::Parathread
336 {
337 Self::do_thread_and_chain_swap(id, other);
338 } else if id_lifecycle == ParaLifecycle::Parathread &&
339 other_lifecycle == ParaLifecycle::Parachain
340 {
341 Self::do_thread_and_chain_swap(other, id);
342 } else if id_lifecycle == ParaLifecycle::Parachain &&
343 other_lifecycle == ParaLifecycle::Parachain
344 {
345 T::OnSwap::on_swap(id, other);
349 } else {
350 return Err(Error::<T>::CannotSwap.into());
351 }
352 Self::deposit_event(Event::<T>::Swapped { para_id: id, other_id: other });
353 PendingSwap::<T>::remove(other);
354 } else {
355 PendingSwap::<T>::insert(id, other);
356 }
357
358 Ok(())
359 }
360
361 #[pallet::call_index(4)]
366 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
367 pub fn remove_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
368 Self::ensure_root_or_para(origin, para)?;
369 <Self as Registrar>::remove_lock(para);
370 Ok(())
371 }
372
373 #[pallet::call_index(5)]
391 #[pallet::weight(<T as Config>::WeightInfo::reserve())]
392 pub fn reserve(origin: OriginFor<T>) -> DispatchResult {
393 let who = ensure_signed(origin)?;
394 let id = NextFreeParaId::<T>::get().max(LOWEST_PUBLIC_ID);
395 Self::do_reserve(who, None, id)?;
396 NextFreeParaId::<T>::set(id + 1);
397 Ok(())
398 }
399
400 #[pallet::call_index(6)]
406 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
407 pub fn add_lock(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
408 Self::ensure_root_para_or_owner(origin, para)?;
409 <Self as Registrar>::apply_lock(para);
410 Ok(())
411 }
412
413 #[pallet::call_index(7)]
425 #[pallet::weight(<T as Config>::WeightInfo::schedule_code_upgrade(new_code.0.len() as u32))]
426 pub fn schedule_code_upgrade(
427 origin: OriginFor<T>,
428 para: ParaId,
429 new_code: ValidationCode,
430 ) -> DispatchResult {
431 Self::ensure_root_para_or_owner(origin, para)?;
432 polkadot_runtime_parachains::schedule_code_upgrade::<T>(
433 para,
434 new_code,
435 UpgradeStrategy::ApplyAtExpectedBlock,
436 )?;
437 Ok(())
438 }
439
440 #[pallet::call_index(8)]
445 #[pallet::weight(<T as Config>::WeightInfo::set_current_head(new_head.0.len() as u32))]
446 pub fn set_current_head(
447 origin: OriginFor<T>,
448 para: ParaId,
449 new_head: HeadData,
450 ) -> DispatchResult {
451 Self::ensure_root_para_or_owner(origin, para)?;
452 polkadot_runtime_parachains::set_current_head::<T>(para, new_head);
453 Ok(())
454 }
455 }
456}
457
458impl<T: Config> Registrar for Pallet<T> {
459 type AccountId = T::AccountId;
460
461 fn manager_of(id: ParaId) -> Option<T::AccountId> {
463 Some(Paras::<T>::get(id)?.manager)
464 }
465
466 fn parachains() -> Vec<ParaId> {
469 paras::Parachains::<T>::get()
470 }
471
472 fn is_parathread(id: ParaId) -> bool {
474 paras::Pallet::<T>::is_parathread(id)
475 }
476
477 fn is_parachain(id: ParaId) -> bool {
479 paras::Pallet::<T>::is_parachain(id)
480 }
481
482 fn apply_lock(id: ParaId) {
484 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(true)));
485 }
486
487 fn remove_lock(id: ParaId) {
489 Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(false)));
490 }
491
492 fn register(
497 manager: T::AccountId,
498 id: ParaId,
499 genesis_head: HeadData,
500 validation_code: ValidationCode,
501 ) -> DispatchResult {
502 Self::do_register(manager, None, id, genesis_head, validation_code, false)
503 }
504
505 fn deregister(id: ParaId) -> DispatchResult {
507 Self::do_deregister(id)
508 }
509
510 fn make_parachain(id: ParaId) -> DispatchResult {
512 ensure!(
514 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parathread),
515 Error::<T>::NotParathread
516 );
517 polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(id)
518 .map_err(|_| Error::<T>::CannotUpgrade)?;
519
520 Ok(())
521 }
522
523 fn make_parathread(id: ParaId) -> DispatchResult {
525 ensure!(
527 paras::Pallet::<T>::lifecycle(id) == Some(ParaLifecycle::Parachain),
528 Error::<T>::NotParachain
529 );
530 polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(id)
531 .map_err(|_| Error::<T>::CannotDowngrade)?;
532 Ok(())
533 }
534
535 #[cfg(any(feature = "runtime-benchmarks", test))]
536 fn worst_head_data() -> HeadData {
537 let max_head_size = configuration::ActiveConfig::<T>::get().max_head_data_size;
538 assert!(max_head_size > 0, "max_head_data can't be zero for generating worst head data.");
539 vec![0u8; max_head_size as usize].into()
540 }
541
542 #[cfg(any(feature = "runtime-benchmarks", test))]
543 fn worst_validation_code() -> ValidationCode {
544 let max_code_size = configuration::ActiveConfig::<T>::get().max_code_size;
545 assert!(max_code_size > 0, "max_code_size can't be zero for generating worst code data.");
546 let validation_code = vec![0u8; max_code_size as usize];
547 validation_code.into()
548 }
549
550 #[cfg(any(feature = "runtime-benchmarks", test))]
551 fn execute_pending_transitions() {
552 use polkadot_runtime_parachains::shared;
553 shared::Pallet::<T>::set_session_index(shared::Pallet::<T>::scheduled_session());
554 paras::Pallet::<T>::test_on_new_session();
555 }
556}
557
558impl<T: Config> Pallet<T> {
559 fn ensure_root_para_or_owner(
562 origin: <T as frame_system::Config>::RuntimeOrigin,
563 id: ParaId,
564 ) -> DispatchResult {
565 if let Ok(who) = ensure_signed(origin.clone()) {
566 let para_info = Paras::<T>::get(id).ok_or(Error::<T>::NotRegistered)?;
567
568 if para_info.manager == who {
569 ensure!(!para_info.is_locked(), Error::<T>::ParaLocked);
570 return Ok(())
571 }
572 }
573
574 Self::ensure_root_or_para(origin, id)
575 }
576
577 fn ensure_root_or_para(
579 origin: <T as frame_system::Config>::RuntimeOrigin,
580 id: ParaId,
581 ) -> DispatchResult {
582 if ensure_root(origin.clone()).is_ok() {
583 return Ok(())
584 }
585
586 let caller_id = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin))?;
587 ensure!(caller_id == id, Error::<T>::NotOwner);
589
590 Ok(())
591 }
592
593 fn do_reserve(
594 who: T::AccountId,
595 deposit_override: Option<BalanceOf<T>>,
596 id: ParaId,
597 ) -> DispatchResult {
598 ensure!(!Paras::<T>::contains_key(id), Error::<T>::AlreadyRegistered);
599 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
600
601 let deposit = deposit_override.unwrap_or_else(T::ParaDeposit::get);
602 <T as Config>::Currency::reserve(&who, deposit)?;
603 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
604
605 Paras::<T>::insert(id, info);
606 Self::deposit_event(Event::<T>::Reserved { para_id: id, who });
607 Ok(())
608 }
609
610 fn do_register(
613 who: T::AccountId,
614 deposit_override: Option<BalanceOf<T>>,
615 id: ParaId,
616 genesis_head: HeadData,
617 validation_code: ValidationCode,
618 ensure_reserved: bool,
619 ) -> DispatchResult {
620 let deposited = if let Some(para_data) = Paras::<T>::get(id) {
621 ensure!(para_data.manager == who, Error::<T>::NotOwner);
622 ensure!(!para_data.is_locked(), Error::<T>::ParaLocked);
623 para_data.deposit
624 } else {
625 ensure!(!ensure_reserved, Error::<T>::NotReserved);
626 Default::default()
627 };
628 ensure!(paras::Pallet::<T>::lifecycle(id).is_none(), Error::<T>::AlreadyRegistered);
629 let (genesis, deposit) =
630 Self::validate_onboarding_data(genesis_head, validation_code, ParaKind::Parathread)?;
631 let deposit = deposit_override.unwrap_or(deposit);
632
633 if let Some(additional) = deposit.checked_sub(&deposited) {
634 <T as Config>::Currency::reserve(&who, additional)?;
635 } else if let Some(rebate) = deposited.checked_sub(&deposit) {
636 <T as Config>::Currency::unreserve(&who, rebate);
637 };
638 let info = ParaInfo { manager: who.clone(), deposit, locked: None };
639
640 Paras::<T>::insert(id, info);
641 let res = polkadot_runtime_parachains::schedule_para_initialize::<T>(id, genesis);
643 debug_assert!(res.is_ok());
644 Self::deposit_event(Event::<T>::Registered { para_id: id, manager: who });
645 Ok(())
646 }
647
648 fn do_deregister(id: ParaId) -> DispatchResult {
650 match paras::Pallet::<T>::lifecycle(id) {
651 Some(ParaLifecycle::Parathread) | None => {},
653 _ => return Err(Error::<T>::NotParathread.into()),
654 }
655 polkadot_runtime_parachains::schedule_para_cleanup::<T>(id)
656 .map_err(|_| Error::<T>::CannotDeregister)?;
657
658 if let Some(info) = Paras::<T>::take(&id) {
659 <T as Config>::Currency::unreserve(&info.manager, info.deposit);
660 }
661
662 PendingSwap::<T>::remove(id);
663 Self::deposit_event(Event::<T>::Deregistered { para_id: id });
664 Ok(())
665 }
666
667 fn validate_onboarding_data(
671 genesis_head: HeadData,
672 validation_code: ValidationCode,
673 para_kind: ParaKind,
674 ) -> Result<(ParaGenesisArgs, BalanceOf<T>), sp_runtime::DispatchError> {
675 let config = configuration::ActiveConfig::<T>::get();
676 ensure!(validation_code.0.len() >= MIN_CODE_SIZE as usize, Error::<T>::InvalidCode);
677 ensure!(validation_code.0.len() <= config.max_code_size as usize, Error::<T>::CodeTooLarge);
678 ensure!(
679 genesis_head.0.len() <= config.max_head_data_size as usize,
680 Error::<T>::HeadDataTooLarge
681 );
682
683 let per_byte_fee = T::DataDepositPerByte::get();
684 let deposit = T::ParaDeposit::get()
685 .saturating_add(per_byte_fee.saturating_mul((genesis_head.0.len() as u32).into()))
686 .saturating_add(per_byte_fee.saturating_mul(config.max_code_size.into()));
687
688 Ok((ParaGenesisArgs { genesis_head, validation_code, para_kind }, deposit))
689 }
690
691 fn do_thread_and_chain_swap(to_downgrade: ParaId, to_upgrade: ParaId) {
694 let res1 = polkadot_runtime_parachains::schedule_parachain_downgrade::<T>(to_downgrade);
695 debug_assert!(res1.is_ok());
696 let res2 = polkadot_runtime_parachains::schedule_parathread_upgrade::<T>(to_upgrade);
697 debug_assert!(res2.is_ok());
698 T::OnSwap::on_swap(to_upgrade, to_downgrade);
699 }
700}
701
702impl<T: Config> OnNewHead for Pallet<T> {
703 fn on_new_head(id: ParaId, _head: &HeadData) -> Weight {
704 let mut writes = 0;
706 if let Some(mut info) = Paras::<T>::get(id) {
707 if info.locked.is_none() {
708 info.locked = Some(true);
709 Paras::<T>::insert(id, info);
710 writes += 1;
711 }
712 }
713 T::DbWeight::get().reads_writes(1, writes)
714 }
715}
716
717#[cfg(test)]
718mod mock;
719
720#[cfg(test)]
721mod tests;
722
723#[cfg(feature = "runtime-benchmarks")]
724mod benchmarking;