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