1pub mod migration;
53
54use crate::{
55 slot_range::SlotRange,
56 traits::{Auctioneer, Registrar},
57};
58use alloc::vec::Vec;
59use codec::{Decode, Encode};
60use pezframe_support::{
61 ensure,
62 pezpallet_prelude::{DispatchResult, Weight},
63 storage::{child, ChildTriePrefixIterator},
64 traits::{
65 Currency, Defensive,
66 ExistenceRequirement::{self, AllowDeath, KeepAlive},
67 Get, ReservableCurrency,
68 },
69 Identity, PalletId,
70};
71use pezframe_system::pezpallet_prelude::BlockNumberFor;
72use pezkuwi_primitives::Id as ParaId;
73pub use pezpallet::*;
74use pezsp_runtime::{
75 traits::{
76 AccountIdConversion, CheckedAdd, Hash, IdentifyAccount, One, Saturating, Verify, Zero,
77 },
78 MultiSignature, MultiSigner, RuntimeDebug,
79};
80use scale_info::TypeInfo;
81
82type CurrencyOf<T> = <<T as Config>::Auctioneer as Auctioneer<BlockNumberFor<T>>>::Currency;
83type LeasePeriodOf<T> = <<T as Config>::Auctioneer as Auctioneer<BlockNumberFor<T>>>::LeasePeriod;
84type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
85
86type FundIndex = u32;
87
88pub trait WeightInfo {
89 fn create() -> Weight;
90 fn contribute() -> Weight;
91 fn withdraw() -> Weight;
92 fn refund(k: u32) -> Weight;
93 fn dissolve() -> Weight;
94 fn edit() -> Weight;
95 fn add_memo() -> Weight;
96 fn on_initialize(n: u32) -> Weight;
97 fn poke() -> Weight;
98}
99
100pub struct TestWeightInfo;
101impl WeightInfo for TestWeightInfo {
102 fn create() -> Weight {
103 Weight::zero()
104 }
105 fn contribute() -> Weight {
106 Weight::zero()
107 }
108 fn withdraw() -> Weight {
109 Weight::zero()
110 }
111 fn refund(_k: u32) -> Weight {
112 Weight::zero()
113 }
114 fn dissolve() -> Weight {
115 Weight::zero()
116 }
117 fn edit() -> Weight {
118 Weight::zero()
119 }
120 fn add_memo() -> Weight {
121 Weight::zero()
122 }
123 fn on_initialize(_n: u32) -> Weight {
124 Weight::zero()
125 }
126 fn poke() -> Weight {
127 Weight::zero()
128 }
129}
130
131#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
132pub enum LastContribution<BlockNumber> {
133 Never,
134 PreEnding(u32),
135 Ending(BlockNumber),
136}
137
138#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
141#[codec(dumb_trait_bound)]
142pub struct FundInfo<AccountId, Balance, BlockNumber, LeasePeriod> {
143 pub depositor: AccountId,
145 pub verifier: Option<MultiSigner>,
147 pub deposit: Balance,
149 pub raised: Balance,
151 pub end: BlockNumber,
154 pub cap: Balance,
156 pub last_contribution: LastContribution<BlockNumber>,
163 pub first_period: LeasePeriod,
166 pub last_period: LeasePeriod,
169 pub fund_index: FundIndex,
171}
172
173#[pezframe_support::pezpallet]
174pub mod pezpallet {
175 use super::*;
176 use pezframe_support::pezpallet_prelude::*;
177 use pezframe_system::{ensure_root, ensure_signed, pezpallet_prelude::*};
178
179 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
181
182 #[pezpallet::pezpallet]
183 #[pezpallet::without_storage_info]
184 #[pezpallet::storage_version(STORAGE_VERSION)]
185 pub struct Pezpallet<T>(_);
186
187 #[pezpallet::config]
188 pub trait Config: pezframe_system::Config {
189 #[allow(deprecated)]
190 type RuntimeEvent: From<Event<Self>>
191 + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
192
193 #[pezpallet::constant]
196 type PalletId: Get<PalletId>;
197
198 type SubmissionDeposit: Get<BalanceOf<Self>>;
200
201 #[pezpallet::constant]
204 type MinContribution: Get<BalanceOf<Self>>;
205
206 #[pezpallet::constant]
208 type RemoveKeysLimit: Get<u32>;
209
210 type Registrar: Registrar<AccountId = Self::AccountId>;
213
214 type Auctioneer: Auctioneer<
216 BlockNumberFor<Self>,
217 AccountId = Self::AccountId,
218 LeasePeriod = BlockNumberFor<Self>,
219 >;
220
221 type MaxMemoLength: Get<u8>;
223
224 type WeightInfo: WeightInfo;
226 }
227
228 #[pezpallet::storage]
230 pub type Funds<T: Config> = StorageMap<
231 _,
232 Twox64Concat,
233 ParaId,
234 FundInfo<T::AccountId, BalanceOf<T>, BlockNumberFor<T>, LeasePeriodOf<T>>,
235 >;
236
237 #[pezpallet::storage]
240 pub type NewRaise<T> = StorageValue<_, Vec<ParaId>, ValueQuery>;
241
242 #[pezpallet::storage]
244 pub type EndingsCount<T> = StorageValue<_, u32, ValueQuery>;
245
246 #[pezpallet::storage]
248 pub type NextFundIndex<T> = StorageValue<_, u32, ValueQuery>;
249
250 #[pezpallet::event]
251 #[pezpallet::generate_deposit(pub(super) fn deposit_event)]
252 pub enum Event<T: Config> {
253 Created { para_id: ParaId },
255 Contributed { who: T::AccountId, fund_index: ParaId, amount: BalanceOf<T> },
257 Withdrew { who: T::AccountId, fund_index: ParaId, amount: BalanceOf<T> },
259 PartiallyRefunded { para_id: ParaId },
262 AllRefunded { para_id: ParaId },
264 Dissolved { para_id: ParaId },
266 HandleBidResult { para_id: ParaId, result: DispatchResult },
268 Edited { para_id: ParaId },
270 MemoUpdated { who: T::AccountId, para_id: ParaId, memo: Vec<u8> },
272 AddedToNewRaise { para_id: ParaId },
274 }
275
276 #[pezpallet::error]
277 pub enum Error<T> {
278 FirstPeriodInPast,
280 FirstPeriodTooFarInFuture,
282 LastPeriodBeforeFirstPeriod,
284 LastPeriodTooFarInFuture,
286 CannotEndInPast,
288 EndTooFarInFuture,
290 Overflow,
292 ContributionTooSmall,
294 InvalidParaId,
296 CapExceeded,
298 ContributionPeriodOver,
300 InvalidOrigin,
302 NotTeyrchain,
304 LeaseActive,
306 BidOrLeaseActive,
308 FundNotEnded,
310 NoContributions,
312 NotReadyToDissolve,
315 InvalidSignature,
317 MemoTooLarge,
319 AlreadyInNewRaise,
321 VrfDelayInProgress,
323 NoLeasePeriod,
325 }
326
327 #[pezpallet::hooks]
328 impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
329 fn on_initialize(num: BlockNumberFor<T>) -> pezframe_support::weights::Weight {
330 if let Some((sample, sub_sample)) = T::Auctioneer::auction_status(num).is_ending() {
331 if sample.is_zero() && sub_sample.is_zero() {
333 EndingsCount::<T>::mutate(|c| *c += 1);
335 }
336 let new_raise = NewRaise::<T>::take();
337 let new_raise_len = new_raise.len() as u32;
338 for (fund, para_id) in
339 new_raise.into_iter().filter_map(|i| Funds::<T>::get(i).map(|f| (f, i)))
340 {
341 let result = T::Auctioneer::place_bid(
345 Self::fund_account_id(fund.fund_index),
346 para_id,
347 fund.first_period,
348 fund.last_period,
349 fund.raised,
350 );
351
352 Self::deposit_event(Event::<T>::HandleBidResult { para_id, result });
353 }
354 T::WeightInfo::on_initialize(new_raise_len)
355 } else {
356 T::DbWeight::get().reads(1)
357 }
358 }
359 }
360
361 #[pezpallet::call]
362 impl<T: Config> Pezpallet<T> {
363 #[pezpallet::call_index(0)]
369 #[pezpallet::weight(T::WeightInfo::create())]
370 pub fn create(
371 origin: OriginFor<T>,
372 #[pezpallet::compact] index: ParaId,
373 #[pezpallet::compact] cap: BalanceOf<T>,
374 #[pezpallet::compact] first_period: LeasePeriodOf<T>,
375 #[pezpallet::compact] last_period: LeasePeriodOf<T>,
376 #[pezpallet::compact] end: BlockNumberFor<T>,
377 verifier: Option<MultiSigner>,
378 ) -> DispatchResult {
379 let depositor = ensure_signed(origin)?;
380 let now = pezframe_system::Pezpallet::<T>::block_number();
381
382 ensure!(first_period <= last_period, Error::<T>::LastPeriodBeforeFirstPeriod);
383 let last_period_limit = first_period
384 .checked_add(&((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into())
385 .ok_or(Error::<T>::FirstPeriodTooFarInFuture)?;
386 ensure!(last_period <= last_period_limit, Error::<T>::LastPeriodTooFarInFuture);
387 ensure!(end > now, Error::<T>::CannotEndInPast);
388
389 let (lease_period_at_end, is_first_block) =
393 T::Auctioneer::lease_period_index(end).ok_or(Error::<T>::NoLeasePeriod)?;
394 let adjusted_lease_period_at_end = if is_first_block {
395 lease_period_at_end.saturating_sub(One::one())
396 } else {
397 lease_period_at_end
398 };
399 ensure!(adjusted_lease_period_at_end <= first_period, Error::<T>::EndTooFarInFuture);
400
401 if let Some((current_lease_period, _)) = T::Auctioneer::lease_period_index(now) {
403 ensure!(first_period >= current_lease_period, Error::<T>::FirstPeriodInPast);
404 }
405
406 ensure!(!Funds::<T>::contains_key(index), Error::<T>::FundNotEnded);
408
409 let manager = T::Registrar::manager_of(index).ok_or(Error::<T>::InvalidParaId)?;
410 ensure!(depositor == manager, Error::<T>::InvalidOrigin);
411 ensure!(T::Registrar::is_registered(index), Error::<T>::InvalidParaId);
412
413 let fund_index = NextFundIndex::<T>::get();
414 let new_fund_index = fund_index.checked_add(1).ok_or(Error::<T>::Overflow)?;
415
416 let deposit = T::SubmissionDeposit::get();
417
418 pezframe_system::Pezpallet::<T>::inc_providers(&Self::fund_account_id(fund_index));
419 CurrencyOf::<T>::reserve(&depositor, deposit)?;
420
421 Funds::<T>::insert(
422 index,
423 FundInfo {
424 depositor,
425 verifier,
426 deposit,
427 raised: Zero::zero(),
428 end,
429 cap,
430 last_contribution: LastContribution::Never,
431 first_period,
432 last_period,
433 fund_index,
434 },
435 );
436
437 NextFundIndex::<T>::put(new_fund_index);
438
439 Self::deposit_event(Event::<T>::Created { para_id: index });
440 Ok(())
441 }
442
443 #[pezpallet::call_index(1)]
446 #[pezpallet::weight(T::WeightInfo::contribute())]
447 pub fn contribute(
448 origin: OriginFor<T>,
449 #[pezpallet::compact] index: ParaId,
450 #[pezpallet::compact] value: BalanceOf<T>,
451 signature: Option<MultiSignature>,
452 ) -> DispatchResult {
453 let who = ensure_signed(origin)?;
454 Self::do_contribute(who, index, value, signature, KeepAlive)
455 }
456
457 #[pezpallet::call_index(2)]
475 #[pezpallet::weight(T::WeightInfo::withdraw())]
476 pub fn withdraw(
477 origin: OriginFor<T>,
478 who: T::AccountId,
479 #[pezpallet::compact] index: ParaId,
480 ) -> DispatchResult {
481 ensure_signed(origin)?;
482
483 let mut fund = Funds::<T>::get(index).ok_or(Error::<T>::InvalidParaId)?;
484 let now = pezframe_system::Pezpallet::<T>::block_number();
485 let fund_account = Self::fund_account_id(fund.fund_index);
486 Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
487
488 let (balance, _) = Self::contribution_get(fund.fund_index, &who);
489 ensure!(balance > Zero::zero(), Error::<T>::NoContributions);
490
491 CurrencyOf::<T>::transfer(&fund_account, &who, balance, AllowDeath)?;
492 CurrencyOf::<T>::reactivate(balance);
493
494 Self::contribution_kill(fund.fund_index, &who);
495 fund.raised = fund.raised.saturating_sub(balance);
496
497 Funds::<T>::insert(index, &fund);
498
499 Self::deposit_event(Event::<T>::Withdrew { who, fund_index: index, amount: balance });
500 Ok(())
501 }
502
503 #[pezpallet::call_index(3)]
509 #[pezpallet::weight(T::WeightInfo::refund(T::RemoveKeysLimit::get()))]
510 pub fn refund(
511 origin: OriginFor<T>,
512 #[pezpallet::compact] index: ParaId,
513 ) -> DispatchResultWithPostInfo {
514 ensure_signed(origin)?;
515
516 let mut fund = Funds::<T>::get(index).ok_or(Error::<T>::InvalidParaId)?;
517 let now = pezframe_system::Pezpallet::<T>::block_number();
518 let fund_account = Self::fund_account_id(fund.fund_index);
519 Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
520
521 let mut refund_count = 0u32;
522 let contributions = Self::contribution_iterator(fund.fund_index);
524 let mut all_refunded = true;
526 for (who, (balance, _)) in contributions {
527 if refund_count >= T::RemoveKeysLimit::get() {
528 all_refunded = false;
530 break;
531 }
532 CurrencyOf::<T>::transfer(&fund_account, &who, balance, AllowDeath)?;
533 CurrencyOf::<T>::reactivate(balance);
534 Self::contribution_kill(fund.fund_index, &who);
535 fund.raised = fund.raised.saturating_sub(balance);
536 refund_count += 1;
537 }
538
539 Funds::<T>::insert(index, &fund);
541
542 if all_refunded {
543 Self::deposit_event(Event::<T>::AllRefunded { para_id: index });
544 Ok(Some(T::WeightInfo::refund(refund_count)).into())
546 } else {
547 Self::deposit_event(Event::<T>::PartiallyRefunded { para_id: index });
548 Ok(().into())
550 }
551 }
552
553 #[pezpallet::call_index(4)]
555 #[pezpallet::weight(T::WeightInfo::dissolve())]
556 pub fn dissolve(
557 origin: OriginFor<T>,
558 #[pezpallet::compact] index: ParaId,
559 ) -> DispatchResult {
560 let who = ensure_signed(origin)?;
561
562 let fund = Funds::<T>::get(index).ok_or(Error::<T>::InvalidParaId)?;
563 let pot = Self::fund_account_id(fund.fund_index);
564 let now = pezframe_system::Pezpallet::<T>::block_number();
565
566 let permitted = who == fund.depositor || now >= fund.end;
569 let can_dissolve = permitted && fund.raised.is_zero();
570 ensure!(can_dissolve, Error::<T>::NotReadyToDissolve);
571
572 debug_assert!(Self::contribution_iterator(fund.fund_index).count().is_zero());
576
577 let _imba = CurrencyOf::<T>::make_free_balance_be(&pot, Zero::zero());
579 let _ = pezframe_system::Pezpallet::<T>::dec_providers(&pot).defensive();
580
581 CurrencyOf::<T>::unreserve(&fund.depositor, fund.deposit);
582 Funds::<T>::remove(index);
583 Self::deposit_event(Event::<T>::Dissolved { para_id: index });
584 Ok(())
585 }
586
587 #[pezpallet::call_index(5)]
591 #[pezpallet::weight(T::WeightInfo::edit())]
592 pub fn edit(
593 origin: OriginFor<T>,
594 #[pezpallet::compact] index: ParaId,
595 #[pezpallet::compact] cap: BalanceOf<T>,
596 #[pezpallet::compact] first_period: LeasePeriodOf<T>,
597 #[pezpallet::compact] last_period: LeasePeriodOf<T>,
598 #[pezpallet::compact] end: BlockNumberFor<T>,
599 verifier: Option<MultiSigner>,
600 ) -> DispatchResult {
601 ensure_root(origin)?;
602
603 let fund = Funds::<T>::get(index).ok_or(Error::<T>::InvalidParaId)?;
604
605 Funds::<T>::insert(
606 index,
607 FundInfo {
608 depositor: fund.depositor,
609 verifier,
610 deposit: fund.deposit,
611 raised: fund.raised,
612 end,
613 cap,
614 last_contribution: fund.last_contribution,
615 first_period,
616 last_period,
617 fund_index: fund.fund_index,
618 },
619 );
620
621 Self::deposit_event(Event::<T>::Edited { para_id: index });
622 Ok(())
623 }
624
625 #[pezpallet::call_index(6)]
629 #[pezpallet::weight(T::WeightInfo::add_memo())]
630 pub fn add_memo(origin: OriginFor<T>, index: ParaId, memo: Vec<u8>) -> DispatchResult {
631 let who = ensure_signed(origin)?;
632
633 ensure!(memo.len() <= T::MaxMemoLength::get().into(), Error::<T>::MemoTooLarge);
634 let fund = Funds::<T>::get(index).ok_or(Error::<T>::InvalidParaId)?;
635
636 let (balance, _) = Self::contribution_get(fund.fund_index, &who);
637 ensure!(balance > Zero::zero(), Error::<T>::NoContributions);
638
639 Self::contribution_put(fund.fund_index, &who, &balance, &memo);
640 Self::deposit_event(Event::<T>::MemoUpdated { who, para_id: index, memo });
641 Ok(())
642 }
643
644 #[pezpallet::call_index(7)]
648 #[pezpallet::weight(T::WeightInfo::poke())]
649 pub fn poke(origin: OriginFor<T>, index: ParaId) -> DispatchResult {
650 ensure_signed(origin)?;
651 let fund = Funds::<T>::get(index).ok_or(Error::<T>::InvalidParaId)?;
652 ensure!(!fund.raised.is_zero(), Error::<T>::NoContributions);
653 ensure!(!NewRaise::<T>::get().contains(&index), Error::<T>::AlreadyInNewRaise);
654 NewRaise::<T>::append(index);
655 Self::deposit_event(Event::<T>::AddedToNewRaise { para_id: index });
656 Ok(())
657 }
658
659 #[pezpallet::call_index(8)]
663 #[pezpallet::weight(T::WeightInfo::contribute())]
664 pub fn contribute_all(
665 origin: OriginFor<T>,
666 #[pezpallet::compact] index: ParaId,
667 signature: Option<MultiSignature>,
668 ) -> DispatchResult {
669 let who = ensure_signed(origin)?;
670 let value = CurrencyOf::<T>::free_balance(&who);
671 Self::do_contribute(who, index, value, signature, AllowDeath)
672 }
673 }
674}
675
676impl<T: Config> Pezpallet<T> {
677 pub fn fund_account_id(index: FundIndex) -> T::AccountId {
682 T::PalletId::get().into_sub_account_truncating(index)
683 }
684
685 pub fn id_from_index(index: FundIndex) -> child::ChildInfo {
686 let mut buf = Vec::new();
687 buf.extend_from_slice(b"crowdloan");
688 buf.extend_from_slice(&index.encode()[..]);
689 child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
690 }
691
692 pub fn contribution_put(
693 index: FundIndex,
694 who: &T::AccountId,
695 balance: &BalanceOf<T>,
696 memo: &[u8],
697 ) {
698 who.using_encoded(|b| child::put(&Self::id_from_index(index), b, &(balance, memo)));
699 }
700
701 pub fn contribution_get(index: FundIndex, who: &T::AccountId) -> (BalanceOf<T>, Vec<u8>) {
702 who.using_encoded(|b| {
703 child::get_or_default::<(BalanceOf<T>, Vec<u8>)>(&Self::id_from_index(index), b)
704 })
705 }
706
707 pub fn contribution_kill(index: FundIndex, who: &T::AccountId) {
708 who.using_encoded(|b| child::kill(&Self::id_from_index(index), b));
709 }
710
711 pub fn crowdloan_kill(index: FundIndex) -> child::KillStorageResult {
712 #[allow(deprecated)]
713 child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()))
714 }
715
716 pub fn contribution_iterator(
717 index: FundIndex,
718 ) -> ChildTriePrefixIterator<(T::AccountId, (BalanceOf<T>, Vec<u8>))> {
719 ChildTriePrefixIterator::<_>::with_prefix_over_key::<Identity>(
720 &Self::id_from_index(index),
721 &[],
722 )
723 }
724
725 fn ensure_crowdloan_ended(
730 now: BlockNumberFor<T>,
731 fund_account: &T::AccountId,
732 fund: &FundInfo<T::AccountId, BalanceOf<T>, BlockNumberFor<T>, LeasePeriodOf<T>>,
733 ) -> pezsp_runtime::DispatchResult {
734 let (current_lease_period, _) =
738 T::Auctioneer::lease_period_index(now).ok_or(Error::<T>::NoLeasePeriod)?;
739 ensure!(
740 now >= fund.end || current_lease_period > fund.first_period,
741 Error::<T>::FundNotEnded
742 );
743 ensure!(
746 CurrencyOf::<T>::free_balance(&fund_account) >= fund.raised,
747 Error::<T>::BidOrLeaseActive
748 );
749
750 Ok(())
751 }
752
753 fn do_contribute(
754 who: T::AccountId,
755 index: ParaId,
756 value: BalanceOf<T>,
757 signature: Option<MultiSignature>,
758 existence: ExistenceRequirement,
759 ) -> DispatchResult {
760 ensure!(value >= T::MinContribution::get(), Error::<T>::ContributionTooSmall);
761 let mut fund = Funds::<T>::get(index).ok_or(Error::<T>::InvalidParaId)?;
762 fund.raised = fund.raised.checked_add(&value).ok_or(Error::<T>::Overflow)?;
763 ensure!(fund.raised <= fund.cap, Error::<T>::CapExceeded);
764
765 let now = pezframe_system::Pezpallet::<T>::block_number();
767 ensure!(now < fund.end, Error::<T>::ContributionPeriodOver);
768
769 let now = pezframe_system::Pezpallet::<T>::block_number();
771 let (current_lease_period, _) =
772 T::Auctioneer::lease_period_index(now).ok_or(Error::<T>::NoLeasePeriod)?;
773 ensure!(current_lease_period <= fund.first_period, Error::<T>::ContributionPeriodOver);
774
775 let fund_account = Self::fund_account_id(fund.fund_index);
777 ensure!(
778 !T::Auctioneer::has_won_an_auction(index, &fund_account),
779 Error::<T>::BidOrLeaseActive
780 );
781
782 ensure!(!T::Auctioneer::auction_status(now).is_vrf(), Error::<T>::VrfDelayInProgress);
785
786 let (old_balance, memo) = Self::contribution_get(fund.fund_index, &who);
787
788 if let Some(ref verifier) = fund.verifier {
789 let signature = signature.ok_or(Error::<T>::InvalidSignature)?;
790 let payload = (index, &who, old_balance, value);
791 let valid = payload.using_encoded(|encoded| {
792 signature.verify(encoded, &verifier.clone().into_account())
793 });
794 ensure!(valid, Error::<T>::InvalidSignature);
795 }
796
797 CurrencyOf::<T>::transfer(&who, &fund_account, value, existence)?;
798 CurrencyOf::<T>::deactivate(value);
799
800 let balance = old_balance.saturating_add(value);
801 Self::contribution_put(fund.fund_index, &who, &balance, &memo);
802
803 if T::Auctioneer::auction_status(now).is_ending().is_some() {
804 match fund.last_contribution {
805 LastContribution::Ending(n) if n == now => {
807 },
809 _ => {
810 NewRaise::<T>::append(index);
811 fund.last_contribution = LastContribution::Ending(now);
812 },
813 }
814 } else {
815 let endings_count = EndingsCount::<T>::get();
816 match fund.last_contribution {
817 LastContribution::PreEnding(a) if a == endings_count => {
818 },
822 _ => {
823 NewRaise::<T>::append(index);
826 fund.last_contribution = LastContribution::PreEnding(endings_count);
827 },
828 }
829 }
830
831 Funds::<T>::insert(index, &fund);
832
833 Self::deposit_event(Event::<T>::Contributed { who, fund_index: index, amount: value });
834 Ok(())
835 }
836}
837
838impl<T: Config> crate::traits::OnSwap for Pezpallet<T> {
839 fn on_swap(one: ParaId, other: ParaId) {
840 Funds::<T>::mutate(one, |x| Funds::<T>::mutate(other, |y| core::mem::swap(x, y)))
841 }
842}
843
844#[cfg(any(feature = "runtime-benchmarks", test))]
845mod crypto {
846 use alloc::vec::Vec;
847 use pezsp_core::ed25519;
848 use pezsp_io::crypto::{ed25519_generate, ed25519_sign};
849 use pezsp_runtime::{MultiSignature, MultiSigner};
850
851 pub fn create_ed25519_pubkey(seed: Vec<u8>) -> MultiSigner {
852 ed25519_generate(0.into(), Some(seed)).into()
853 }
854
855 pub fn create_ed25519_signature(payload: &[u8], pubkey: MultiSigner) -> MultiSignature {
856 let edpubkey = ed25519::Public::try_from(pubkey).unwrap();
857 let edsig = ed25519_sign(0.into(), &edpubkey, payload).unwrap();
858 edsig.into()
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 use pezframe_support::{assert_noop, assert_ok, derive_impl, parameter_types};
867 use pezkuwi_primitives::Id as ParaId;
868 use pezsp_core::H256;
869 use std::{cell::RefCell, collections::BTreeMap, sync::Arc};
870 use crate::{
873 crowdloan,
874 mock::TestRegistrar,
875 traits::{AuctionStatus, OnSwap},
876 };
877 use pezkuwi_primitives_test_helpers::{dummy_head_data, dummy_validation_code};
878 use pezsp_keystore::{testing::MemoryKeystore, KeystoreExt};
879 use pezsp_runtime::{
880 traits::{BlakeTwo256, IdentityLookup, TrailingZeroInput},
881 BuildStorage, DispatchResult,
882 };
883
884 type Block = pezframe_system::mocking::MockBlock<Test>;
885
886 pezframe_support::construct_runtime!(
887 pub enum Test
888 {
889 System: pezframe_system,
890 Balances: pezpallet_balances,
891 Crowdloan: crowdloan,
892 }
893 );
894
895 type BlockNumber = u64;
896
897 #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
898 impl pezframe_system::Config for Test {
899 type BaseCallFilter = pezframe_support::traits::Everything;
900 type BlockWeights = ();
901 type BlockLength = ();
902 type DbWeight = ();
903 type RuntimeOrigin = RuntimeOrigin;
904 type RuntimeCall = RuntimeCall;
905 type Nonce = u64;
906 type Hash = H256;
907 type Hashing = BlakeTwo256;
908 type AccountId = u64;
909 type Lookup = IdentityLookup<Self::AccountId>;
910 type Block = Block;
911 type RuntimeEvent = RuntimeEvent;
912 type Version = ();
913 type PalletInfo = PalletInfo;
914 type AccountData = pezpallet_balances::AccountData<u64>;
915 type OnNewAccount = ();
916 type OnKilledAccount = ();
917 type SystemWeightInfo = ();
918 type SS58Prefix = ();
919 type OnSetCode = ();
920 type MaxConsumers = pezframe_support::traits::ConstU32<16>;
921 }
922
923 #[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
924 impl pezpallet_balances::Config for Test {
925 type AccountStore = System;
926 }
927
928 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
929 struct BidPlaced {
930 height: u64,
931 bidder: u64,
932 para: ParaId,
933 first_period: u64,
934 last_period: u64,
935 amount: u64,
936 }
937 thread_local! {
938 static AUCTION: RefCell<Option<(u64, u64)>> = RefCell::new(None);
939 static VRF_DELAY: RefCell<u64> = RefCell::new(0);
940 static ENDING_PERIOD: RefCell<u64> = RefCell::new(5);
941 static BIDS_PLACED: RefCell<Vec<BidPlaced>> = RefCell::new(Vec::new());
942 static HAS_WON: RefCell<BTreeMap<(ParaId, u64), bool>> = RefCell::new(BTreeMap::new());
943 }
944
945 #[allow(unused)]
946 fn set_ending_period(ending_period: u64) {
947 ENDING_PERIOD.with(|p| *p.borrow_mut() = ending_period);
948 }
949 fn auction() -> Option<(u64, u64)> {
950 AUCTION.with(|p| *p.borrow())
951 }
952 fn ending_period() -> u64 {
953 ENDING_PERIOD.with(|p| *p.borrow())
954 }
955 fn bids() -> Vec<BidPlaced> {
956 BIDS_PLACED.with(|p| p.borrow().clone())
957 }
958 fn vrf_delay() -> u64 {
959 VRF_DELAY.with(|p| *p.borrow())
960 }
961 fn set_vrf_delay(delay: u64) {
962 VRF_DELAY.with(|p| *p.borrow_mut() = delay);
963 }
964 fn set_winner(para: ParaId, who: u64, winner: bool) {
967 let fund = Funds::<Test>::get(para).unwrap();
968 let account_id = Crowdloan::fund_account_id(fund.fund_index);
969 if winner {
970 let ed: u64 = <Test as pezpallet_balances::Config>::ExistentialDeposit::get();
971 let free_balance = Balances::free_balance(&account_id);
972 Balances::reserve(&account_id, free_balance - ed)
973 .expect("should be able to reserve free balance minus ED");
974 } else {
975 let reserved_balance = Balances::reserved_balance(&account_id);
976 Balances::unreserve(&account_id, reserved_balance);
977 }
978 HAS_WON.with(|p| p.borrow_mut().insert((para, who), winner));
979 }
980
981 pub struct TestAuctioneer;
982 impl Auctioneer<u64> for TestAuctioneer {
983 type AccountId = u64;
984 type LeasePeriod = u64;
985 type Currency = Balances;
986
987 fn new_auction(duration: u64, lease_period_index: u64) -> DispatchResult {
988 let now = System::block_number();
989 let (current_lease_period, _) =
990 Self::lease_period_index(now).ok_or("no lease period yet")?;
991 assert!(lease_period_index >= current_lease_period);
992
993 let ending = System::block_number().saturating_add(duration);
994 AUCTION.with(|p| *p.borrow_mut() = Some((lease_period_index, ending)));
995 Ok(())
996 }
997
998 fn auction_status(now: u64) -> AuctionStatus<u64> {
999 let early_end = match auction() {
1000 Some((_, early_end)) => early_end,
1001 None => return AuctionStatus::NotStarted,
1002 };
1003 let after_early_end = match now.checked_sub(early_end) {
1004 Some(after_early_end) => after_early_end,
1005 None => return AuctionStatus::StartingPeriod,
1006 };
1007
1008 let ending_period = ending_period();
1009 if after_early_end < ending_period {
1010 return AuctionStatus::EndingPeriod(after_early_end, 0);
1011 } else {
1012 let after_end = after_early_end - ending_period;
1013 if after_end < vrf_delay() {
1015 return AuctionStatus::VrfDelay(after_end);
1016 } else {
1017 return AuctionStatus::NotStarted;
1019 }
1020 }
1021 }
1022
1023 fn place_bid(
1024 bidder: u64,
1025 para: ParaId,
1026 first_period: u64,
1027 last_period: u64,
1028 amount: u64,
1029 ) -> DispatchResult {
1030 let height = System::block_number();
1031 BIDS_PLACED.with(|p| {
1032 p.borrow_mut().push(BidPlaced {
1033 height,
1034 bidder,
1035 para,
1036 first_period,
1037 last_period,
1038 amount,
1039 })
1040 });
1041 Ok(())
1042 }
1043
1044 fn lease_period_index(b: BlockNumber) -> Option<(u64, bool)> {
1045 let (lease_period_length, offset) = Self::lease_period_length();
1046 let b = b.checked_sub(offset)?;
1047
1048 let lease_period = b / lease_period_length;
1049 let first_block = (b % lease_period_length).is_zero();
1050 Some((lease_period, first_block))
1051 }
1052
1053 fn lease_period_length() -> (u64, u64) {
1054 (20, 0)
1055 }
1056
1057 fn has_won_an_auction(para: ParaId, bidder: &u64) -> bool {
1058 HAS_WON.with(|p| *p.borrow().get(&(para, *bidder)).unwrap_or(&false))
1059 }
1060 }
1061
1062 parameter_types! {
1063 pub const SubmissionDeposit: u64 = 1;
1064 pub const MinContribution: u64 = 10;
1065 pub const CrowdloanPalletId: PalletId = PalletId(*b"py/cfund");
1066 pub const RemoveKeysLimit: u32 = 10;
1067 pub const MaxMemoLength: u8 = 32;
1068 }
1069
1070 impl Config for Test {
1071 type RuntimeEvent = RuntimeEvent;
1072 type SubmissionDeposit = SubmissionDeposit;
1073 type MinContribution = MinContribution;
1074 type PalletId = CrowdloanPalletId;
1075 type RemoveKeysLimit = RemoveKeysLimit;
1076 type Registrar = TestRegistrar<Test>;
1077 type Auctioneer = TestAuctioneer;
1078 type MaxMemoLength = MaxMemoLength;
1079 type WeightInfo = crate::crowdloan::TestWeightInfo;
1080 }
1081
1082 use pezpallet_balances::Error as BalancesError;
1083
1084 pub fn new_test_ext() -> pezsp_io::TestExternalities {
1087 let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
1088 pezpallet_balances::GenesisConfig::<Test> {
1089 balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)],
1090 ..Default::default()
1091 }
1092 .assimilate_storage(&mut t)
1093 .unwrap();
1094 let keystore = MemoryKeystore::new();
1095 let mut t: pezsp_io::TestExternalities = t.into();
1096 t.register_extension(KeystoreExt(Arc::new(keystore)));
1097 t
1098 }
1099
1100 fn new_para() -> ParaId {
1101 for i in 0.. {
1102 let para: ParaId = i.into();
1103 if TestRegistrar::<Test>::is_registered(para) {
1104 continue;
1105 }
1106 assert_ok!(TestRegistrar::<Test>::register(
1107 1,
1108 para,
1109 dummy_head_data(),
1110 dummy_validation_code()
1111 ));
1112 return para;
1113 }
1114 unreachable!()
1115 }
1116
1117 fn last_event() -> RuntimeEvent {
1118 System::events().pop().expect("RuntimeEvent expected").event
1119 }
1120
1121 #[test]
1122 fn basic_setup_works() {
1123 new_test_ext().execute_with(|| {
1124 assert_eq!(System::block_number(), 0);
1125 assert_eq!(crowdloan::Funds::<Test>::get(ParaId::from(0)), None);
1126 let empty: Vec<ParaId> = Vec::new();
1127 assert_eq!(crowdloan::NewRaise::<Test>::get(), empty);
1128 assert_eq!(Crowdloan::contribution_get(0u32, &1).0, 0);
1129 assert_eq!(crowdloan::EndingsCount::<Test>::get(), 0);
1130
1131 assert_ok!(TestAuctioneer::new_auction(5, 0));
1132
1133 assert_eq!(bids(), vec![]);
1134 assert_ok!(TestAuctioneer::place_bid(1, 2.into(), 0, 3, 6));
1135 let b = BidPlaced {
1136 height: 0,
1137 bidder: 1,
1138 para: 2.into(),
1139 first_period: 0,
1140 last_period: 3,
1141 amount: 6,
1142 };
1143 assert_eq!(bids(), vec![b]);
1144 assert_eq!(TestAuctioneer::auction_status(4), AuctionStatus::<u64>::StartingPeriod);
1145 assert_eq!(TestAuctioneer::auction_status(5), AuctionStatus::<u64>::EndingPeriod(0, 0));
1146 assert_eq!(TestAuctioneer::auction_status(9), AuctionStatus::<u64>::EndingPeriod(4, 0));
1147 assert_eq!(TestAuctioneer::auction_status(11), AuctionStatus::<u64>::NotStarted);
1148 });
1149 }
1150
1151 #[test]
1152 fn create_works() {
1153 new_test_ext().execute_with(|| {
1154 let para = new_para();
1155 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 9, None));
1157 let fund_info = FundInfo {
1159 depositor: 1,
1160 verifier: None,
1161 deposit: 1,
1162 raised: 0,
1163 end: 9,
1165 cap: 1000,
1166 last_contribution: LastContribution::Never,
1167 first_period: 1,
1168 last_period: 4,
1169 fund_index: 0,
1170 };
1171 assert_eq!(crowdloan::Funds::<Test>::get(para), Some(fund_info));
1172 assert_eq!(Balances::free_balance(1), 999);
1174 assert_eq!(Balances::reserved_balance(1), 1);
1176 let empty: Vec<ParaId> = Vec::new();
1178 assert_eq!(crowdloan::NewRaise::<Test>::get(), empty);
1179 });
1180 }
1181
1182 #[test]
1183 fn create_with_verifier_works() {
1184 new_test_ext().execute_with(|| {
1185 let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
1186 let para = new_para();
1187 assert_ok!(Crowdloan::create(
1189 RuntimeOrigin::signed(1),
1190 para,
1191 1000,
1192 1,
1193 4,
1194 9,
1195 Some(pubkey.clone())
1196 ));
1197 let fund_info = FundInfo {
1199 depositor: 1,
1200 verifier: Some(pubkey),
1201 deposit: 1,
1202 raised: 0,
1203 end: 9,
1205 cap: 1000,
1206 last_contribution: LastContribution::Never,
1207 first_period: 1,
1208 last_period: 4,
1209 fund_index: 0,
1210 };
1211 assert_eq!(crowdloan::Funds::<Test>::get(ParaId::from(0)), Some(fund_info));
1212 assert_eq!(Balances::free_balance(1), 999);
1214 assert_eq!(Balances::reserved_balance(1), 1);
1216 let empty: Vec<ParaId> = Vec::new();
1218 assert_eq!(crowdloan::NewRaise::<Test>::get(), empty);
1219 });
1220 }
1221
1222 #[test]
1223 fn create_handles_basic_errors() {
1224 new_test_ext().execute_with(|| {
1225 let para = new_para();
1227
1228 let e = Error::<Test>::InvalidParaId;
1229 assert_noop!(
1230 Crowdloan::create(RuntimeOrigin::signed(1), 1.into(), 1000, 1, 4, 9, None),
1231 e
1232 );
1233 let e = Error::<Test>::LastPeriodBeforeFirstPeriod;
1235 assert_noop!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 4, 1, 9, None), e);
1236 let e = Error::<Test>::LastPeriodTooFarInFuture;
1237 assert_noop!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 9, 9, None), e);
1238
1239 assert_ok!(TestRegistrar::<Test>::register(
1241 1337,
1242 ParaId::from(1234),
1243 dummy_head_data(),
1244 dummy_validation_code()
1245 ));
1246 let e = BalancesError::<Test, _>::InsufficientBalance;
1247 assert_noop!(
1248 Crowdloan::create(
1249 RuntimeOrigin::signed(1337),
1250 ParaId::from(1234),
1251 1000,
1252 1,
1253 3,
1254 9,
1255 None
1256 ),
1257 e
1258 );
1259
1260 assert_noop!(
1264 Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 41, None),
1265 Error::<Test>::EndTooFarInFuture
1266 );
1267 });
1268 }
1269
1270 #[test]
1271 fn contribute_works() {
1272 new_test_ext().execute_with(|| {
1273 let para = new_para();
1274 let index = NextFundIndex::<Test>::get();
1275
1276 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 9, None));
1278
1279 assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
1281
1282 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None));
1284 assert_eq!(Balances::free_balance(1), 950);
1286 assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 49);
1288 assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(index)), 49);
1290 assert_eq!(crowdloan::NewRaise::<Test>::get(), vec![para]);
1292
1293 let fund = crowdloan::Funds::<Test>::get(para).unwrap();
1294
1295 assert_eq!(fund.last_contribution, LastContribution::PreEnding(0));
1297 assert_eq!(fund.raised, 49);
1298 });
1299 }
1300
1301 #[test]
1302 fn contribute_with_verifier_works() {
1303 new_test_ext().execute_with(|| {
1304 let para = new_para();
1305 let index = NextFundIndex::<Test>::get();
1306 let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
1307 assert_ok!(Crowdloan::create(
1309 RuntimeOrigin::signed(1),
1310 para,
1311 1000,
1312 1,
1313 4,
1314 9,
1315 Some(pubkey.clone())
1316 ));
1317
1318 assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
1320
1321 assert_noop!(
1323 Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None),
1324 Error::<Test>::InvalidSignature
1325 );
1326
1327 let payload = (0u32, 1u64, 0u64, 49u64);
1328 let valid_signature =
1329 crypto::create_ed25519_signature(&payload.encode(), pubkey.clone());
1330 let invalid_signature =
1331 MultiSignature::decode(&mut TrailingZeroInput::zeroes()).unwrap();
1332
1333 assert_noop!(
1335 Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, Some(invalid_signature)),
1336 Error::<Test>::InvalidSignature
1337 );
1338
1339 assert_noop!(
1341 Crowdloan::contribute(
1342 RuntimeOrigin::signed(1),
1343 para,
1344 50,
1345 Some(valid_signature.clone())
1346 ),
1347 Error::<Test>::InvalidSignature
1348 );
1349 assert_noop!(
1350 Crowdloan::contribute(
1351 RuntimeOrigin::signed(2),
1352 para,
1353 49,
1354 Some(valid_signature.clone())
1355 ),
1356 Error::<Test>::InvalidSignature
1357 );
1358
1359 assert_ok!(Crowdloan::contribute(
1361 RuntimeOrigin::signed(1),
1362 para,
1363 49,
1364 Some(valid_signature.clone())
1365 ));
1366
1367 assert_noop!(
1369 Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, Some(valid_signature)),
1370 Error::<Test>::InvalidSignature
1371 );
1372
1373 let payload_2 = (0u32, 1u64, 49u64, 10u64);
1374 let valid_signature_2 = crypto::create_ed25519_signature(&payload_2.encode(), pubkey);
1375
1376 assert_ok!(Crowdloan::contribute(
1378 RuntimeOrigin::signed(1),
1379 para,
1380 10,
1381 Some(valid_signature_2)
1382 ));
1383
1384 assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(index)), 59);
1386
1387 let fund = crowdloan::Funds::<Test>::get(para).unwrap();
1389 assert_eq!(fund.raised, 59);
1390 });
1391 }
1392
1393 #[test]
1394 fn contribute_handles_basic_errors() {
1395 new_test_ext().execute_with(|| {
1396 let para = new_para();
1397
1398 assert_noop!(
1400 Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None),
1401 Error::<Test>::InvalidParaId
1402 );
1403 assert_noop!(
1405 Crowdloan::contribute(RuntimeOrigin::signed(1), para, 9, None),
1406 Error::<Test>::ContributionTooSmall
1407 );
1408
1409 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 9, None));
1411 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para, 101, None));
1412
1413 assert_noop!(
1415 Crowdloan::contribute(RuntimeOrigin::signed(2), para, 900, None),
1416 Error::<Test>::CapExceeded
1417 );
1418
1419 System::run_to_block::<AllPalletsWithSystem>(10);
1421
1422 assert_noop!(
1424 Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None),
1425 Error::<Test>::ContributionPeriodOver
1426 );
1427
1428 let para_2 = new_para();
1430 let index = NextFundIndex::<Test>::get();
1431 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_2, 1000, 1, 4, 40, None));
1432 let crowdloan_account = Crowdloan::fund_account_id(index);
1435 set_winner(para_2, crowdloan_account, true);
1436 assert_noop!(
1437 Crowdloan::contribute(RuntimeOrigin::signed(1), para_2, 49, None),
1438 Error::<Test>::BidOrLeaseActive
1439 );
1440
1441 let para_3 = new_para();
1444 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_3, 1000, 1, 4, 40, None));
1445 System::run_to_block::<AllPalletsWithSystem>(40);
1446 let now = System::block_number();
1447 assert_eq!(TestAuctioneer::lease_period_index(now).unwrap().0, 2);
1448 assert_noop!(
1449 Crowdloan::contribute(RuntimeOrigin::signed(1), para_3, 49, None),
1450 Error::<Test>::ContributionPeriodOver
1451 );
1452 });
1453 }
1454
1455 #[test]
1456 fn cannot_contribute_during_vrf() {
1457 new_test_ext().execute_with(|| {
1458 set_vrf_delay(5);
1459
1460 let para = new_para();
1461 let first_period = 1;
1462 let last_period = 4;
1463
1464 assert_ok!(TestAuctioneer::new_auction(5, 0));
1465
1466 assert_ok!(Crowdloan::create(
1468 RuntimeOrigin::signed(1),
1469 para,
1470 1000,
1471 first_period,
1472 last_period,
1473 20,
1474 None
1475 ));
1476
1477 System::run_to_block::<AllPalletsWithSystem>(8);
1478 assert!(TestAuctioneer::auction_status(System::block_number()).is_ending().is_some());
1480 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None));
1481
1482 System::run_to_block::<AllPalletsWithSystem>(10);
1483 assert!(TestAuctioneer::auction_status(System::block_number()).is_vrf());
1485 assert_noop!(
1486 Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None),
1487 Error::<Test>::VrfDelayInProgress
1488 );
1489
1490 System::run_to_block::<AllPalletsWithSystem>(15);
1491 assert!(!TestAuctioneer::auction_status(System::block_number()).is_in_progress());
1493 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None));
1494 })
1495 }
1496
1497 #[test]
1498 fn bidding_works() {
1499 new_test_ext().execute_with(|| {
1500 let para = new_para();
1501 let index = NextFundIndex::<Test>::get();
1502 let first_period = 1;
1503 let last_period = 4;
1504
1505 assert_ok!(TestAuctioneer::new_auction(5, 0));
1506
1507 assert_ok!(Crowdloan::create(
1509 RuntimeOrigin::signed(1),
1510 para,
1511 1000,
1512 first_period,
1513 last_period,
1514 9,
1515 None
1516 ));
1517 let bidder = Crowdloan::fund_account_id(index);
1518
1519 System::run_to_block::<AllPalletsWithSystem>(1);
1521 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1522 System::run_to_block::<AllPalletsWithSystem>(3);
1523 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 150, None));
1524 System::run_to_block::<AllPalletsWithSystem>(5);
1525 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(4), para, 200, None));
1526 System::run_to_block::<AllPalletsWithSystem>(8);
1527 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None));
1528 System::run_to_block::<AllPalletsWithSystem>(10);
1529
1530 assert_eq!(
1531 bids(),
1532 vec![
1533 BidPlaced { height: 5, amount: 250, bidder, para, first_period, last_period },
1534 BidPlaced { height: 6, amount: 450, bidder, para, first_period, last_period },
1535 BidPlaced { height: 9, amount: 700, bidder, para, first_period, last_period },
1536 ]
1537 );
1538
1539 assert_eq!(crowdloan::EndingsCount::<Test>::get(), 1);
1541 });
1542 }
1543
1544 #[test]
1545 fn withdraw_from_failed_works() {
1546 new_test_ext().execute_with(|| {
1547 let para = new_para();
1548 let index = NextFundIndex::<Test>::get();
1549
1550 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1552 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1553 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None));
1554
1555 System::run_to_block::<AllPalletsWithSystem>(10);
1556 let account_id = Crowdloan::fund_account_id(index);
1557 assert_eq!(Balances::reserved_balance(&account_id), 0);
1559 assert_eq!(Balances::free_balance(&account_id), 150);
1561 assert_eq!(Balances::free_balance(2), 1900);
1562 assert_eq!(Balances::free_balance(3), 2950);
1563
1564 assert_ok!(Crowdloan::withdraw(RuntimeOrigin::signed(2), 2, para));
1565 assert_eq!(Balances::free_balance(&account_id), 50);
1566 assert_eq!(Balances::free_balance(2), 2000);
1567
1568 assert_ok!(Crowdloan::withdraw(RuntimeOrigin::signed(2), 3, para));
1569 assert_eq!(Balances::free_balance(&account_id), 0);
1570 assert_eq!(Balances::free_balance(3), 3000);
1571 });
1572 }
1573
1574 #[test]
1575 fn withdraw_cannot_be_griefed() {
1576 new_test_ext().execute_with(|| {
1577 let para = new_para();
1578 let index = NextFundIndex::<Test>::get();
1579 let issuance = Balances::total_issuance();
1580
1581 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1583 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1584
1585 System::run_to_block::<AllPalletsWithSystem>(10);
1586 let account_id = Crowdloan::fund_account_id(index);
1587
1588 assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), account_id, 10));
1590
1591 assert_eq!(Balances::free_balance(&account_id), 110);
1593 assert_eq!(Balances::free_balance(2), 1900);
1594
1595 assert_ok!(Crowdloan::withdraw(RuntimeOrigin::signed(2), 2, para));
1596 assert_eq!(Balances::free_balance(2), 2000);
1597
1598 assert_eq!(Balances::free_balance(&account_id), 10);
1600 assert_ok!(Crowdloan::dissolve(RuntimeOrigin::signed(1), para));
1602 assert_eq!(Balances::free_balance(&account_id), 0);
1603 assert_eq!(Balances::total_issuance(), issuance - 10);
1604 });
1605 }
1606
1607 #[test]
1608 fn refund_works() {
1609 new_test_ext().execute_with(|| {
1610 let para = new_para();
1611 let index = NextFundIndex::<Test>::get();
1612 let account_id = Crowdloan::fund_account_id(index);
1613
1614 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1616 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para, 100, None));
1618 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 200, None));
1619 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 300, None));
1620
1621 assert_eq!(Balances::free_balance(account_id), 600);
1622
1623 assert_noop!(
1625 Crowdloan::refund(RuntimeOrigin::signed(1337), para),
1626 Error::<Test>::FundNotEnded,
1627 );
1628
1629 System::run_to_block::<AllPalletsWithSystem>(10);
1631 assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para));
1632
1633 assert_eq!(Balances::free_balance(account_id), 0);
1635 assert_eq!(Balances::free_balance(1), 1000 - 1);
1637 assert_eq!(Balances::free_balance(2), 2000);
1638 assert_eq!(Balances::free_balance(3), 3000);
1639 });
1640 }
1641
1642 #[test]
1643 fn multiple_refund_works() {
1644 new_test_ext().execute_with(|| {
1645 let para = new_para();
1646 let index = NextFundIndex::<Test>::get();
1647 let account_id = Crowdloan::fund_account_id(index);
1648
1649 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 100000, 1, 1, 9, None));
1651 for i in 1..=RemoveKeysLimit::get() * 2 {
1653 Balances::make_free_balance_be(&i.into(), (1000 * i).into());
1654 assert_ok!(Crowdloan::contribute(
1655 RuntimeOrigin::signed(i.into()),
1656 para,
1657 (i * 100).into(),
1658 None
1659 ));
1660 }
1661
1662 assert_eq!(Balances::free_balance(account_id), 21000);
1663
1664 System::run_to_block::<AllPalletsWithSystem>(10);
1666 assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para));
1667 assert_eq!(
1668 last_event(),
1669 super::Event::<Test>::PartiallyRefunded { para_id: para }.into()
1670 );
1671
1672 assert!(!Balances::free_balance(account_id).is_zero());
1674
1675 assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para));
1677 assert_eq!(last_event(), super::Event::<Test>::AllRefunded { para_id: para }.into());
1678
1679 assert_eq!(Balances::free_balance(account_id), 0);
1681 for i in 1..=RemoveKeysLimit::get() * 2 {
1683 assert_eq!(Balances::free_balance(&i.into()), i as u64 * 1000);
1684 }
1685 });
1686 }
1687
1688 #[test]
1689 fn refund_and_dissolve_works() {
1690 new_test_ext().execute_with(|| {
1691 let para = new_para();
1692 let issuance = Balances::total_issuance();
1693
1694 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1696 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1697 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None));
1698
1699 System::run_to_block::<AllPalletsWithSystem>(10);
1700 assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1702
1703 assert_ok!(Crowdloan::dissolve(RuntimeOrigin::signed(1), para));
1705 assert_eq!(Balances::free_balance(1), 1000);
1706 assert_eq!(Balances::free_balance(2), 2000);
1707 assert_eq!(Balances::free_balance(3), 3000);
1708 assert_eq!(Balances::total_issuance(), issuance);
1709 });
1710 }
1711
1712 #[test]
1714 fn dissolve_provider_refs_total_issuance_works() {
1715 new_test_ext().execute_with(|| {
1716 let para = new_para();
1717 let issuance = Balances::total_issuance();
1718
1719 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1721 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1722 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None));
1723
1724 System::run_to_block::<AllPalletsWithSystem>(10);
1725
1726 {
1728 let fund = crowdloan::Funds::<Test>::get(para).unwrap();
1729 let pot = Crowdloan::fund_account_id(fund.fund_index);
1730 System::dec_providers(&pot).unwrap();
1731 assert_eq!(System::providers(&pot), 1);
1732 }
1733
1734 assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1736
1737 assert_ok!(Crowdloan::dissolve(RuntimeOrigin::signed(1), para));
1739
1740 assert_eq!(Balances::free_balance(1), 1000);
1741 assert_eq!(Balances::free_balance(2), 2000);
1742 assert_eq!(Balances::free_balance(3), 3000);
1743 assert_eq!(Balances::total_issuance(), issuance);
1744 });
1745 }
1746
1747 #[test]
1748 fn dissolve_works() {
1749 new_test_ext().execute_with(|| {
1750 let para = new_para();
1751 let issuance = Balances::total_issuance();
1752
1753 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1755 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1756 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None));
1757
1758 assert_noop!(
1760 Crowdloan::dissolve(RuntimeOrigin::signed(1), para),
1761 Error::<Test>::NotReadyToDissolve
1762 );
1763
1764 System::run_to_block::<AllPalletsWithSystem>(10);
1765 set_winner(para, 1, true);
1766 assert_noop!(
1768 Crowdloan::dissolve(RuntimeOrigin::signed(1), para),
1769 Error::<Test>::NotReadyToDissolve
1770 );
1771 set_winner(para, 1, false);
1772
1773 assert_noop!(
1775 Crowdloan::dissolve(RuntimeOrigin::signed(1), para),
1776 Error::<Test>::NotReadyToDissolve
1777 );
1778
1779 assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1781
1782 assert_ok!(Crowdloan::dissolve(RuntimeOrigin::signed(1), para));
1784 assert_eq!(Balances::free_balance(1), 1000);
1785 assert_eq!(Balances::free_balance(2), 2000);
1786 assert_eq!(Balances::free_balance(3), 3000);
1787 assert_eq!(Balances::total_issuance(), issuance);
1788 });
1789 }
1790
1791 #[test]
1792 fn withdraw_from_finished_works() {
1793 new_test_ext().execute_with(|| {
1794 let ed: u64 = <Test as pezpallet_balances::Config>::ExistentialDeposit::get();
1795 assert_eq!(ed, 1);
1796 let para = new_para();
1797 let index = NextFundIndex::<Test>::get();
1798 let account_id = Crowdloan::fund_account_id(index);
1799
1800 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1802
1803 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1805 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None));
1806 assert_ok!(Balances::reserve(&account_id, 149));
1808
1809 System::run_to_block::<AllPalletsWithSystem>(19);
1810 assert_noop!(
1811 Crowdloan::withdraw(RuntimeOrigin::signed(2), 2, para),
1812 Error::<Test>::BidOrLeaseActive
1813 );
1814
1815 System::run_to_block::<AllPalletsWithSystem>(20);
1816 Balances::unreserve(&account_id, 150);
1819
1820 assert_eq!(Balances::reserved_balance(&account_id), 0);
1822 assert_eq!(Balances::free_balance(&account_id), 150);
1824 assert_eq!(Balances::free_balance(2), 1900);
1825 assert_eq!(Balances::free_balance(3), 2950);
1826
1827 assert_ok!(Crowdloan::withdraw(RuntimeOrigin::signed(2), 2, para));
1828 assert_eq!(Balances::free_balance(&account_id), 50);
1829 assert_eq!(Balances::free_balance(2), 2000);
1830
1831 assert_ok!(Crowdloan::withdraw(RuntimeOrigin::signed(2), 3, para));
1832 assert_eq!(Balances::free_balance(&account_id), 0);
1833 assert_eq!(Balances::free_balance(3), 3000);
1834 });
1835 }
1836
1837 #[test]
1838 fn on_swap_works() {
1839 new_test_ext().execute_with(|| {
1840 let para_1 = new_para();
1841 let para_2 = new_para();
1842
1843 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_1, 1000, 1, 1, 9, None));
1845 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_2, 1000, 1, 1, 9, None));
1846 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None));
1848 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para_2, 50, None));
1849 assert_eq!(Funds::<Test>::get(para_1).unwrap().raised, 100);
1851 assert_eq!(Funds::<Test>::get(para_2).unwrap().raised, 50);
1852 Crowdloan::on_swap(para_1, para_2);
1854 assert_eq!(Funds::<Test>::get(para_2).unwrap().raised, 100);
1856 assert_eq!(Funds::<Test>::get(para_1).unwrap().raised, 50);
1857 });
1858 }
1859
1860 #[test]
1861 fn cannot_create_fund_when_already_active() {
1862 new_test_ext().execute_with(|| {
1863 let para_1 = new_para();
1864
1865 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_1, 1000, 1, 1, 9, None));
1866 assert_noop!(
1868 Crowdloan::create(RuntimeOrigin::signed(1), para_1, 1000, 1, 1, 9, None),
1869 Error::<Test>::FundNotEnded,
1870 );
1871 });
1872 }
1873
1874 #[test]
1875 fn edit_works() {
1876 new_test_ext().execute_with(|| {
1877 let para_1 = new_para();
1878
1879 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_1, 1000, 1, 1, 9, None));
1880 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None));
1881 let old_crowdloan = crowdloan::Funds::<Test>::get(para_1).unwrap();
1882
1883 assert_ok!(Crowdloan::edit(RuntimeOrigin::root(), para_1, 1234, 2, 3, 4, None));
1884 let new_crowdloan = crowdloan::Funds::<Test>::get(para_1).unwrap();
1885
1886 assert_eq!(old_crowdloan.depositor, new_crowdloan.depositor);
1888 assert_eq!(old_crowdloan.deposit, new_crowdloan.deposit);
1889 assert_eq!(old_crowdloan.raised, new_crowdloan.raised);
1890
1891 assert!(old_crowdloan.cap != new_crowdloan.cap);
1893 assert!(old_crowdloan.first_period != new_crowdloan.first_period);
1894 assert!(old_crowdloan.last_period != new_crowdloan.last_period);
1895 });
1896 }
1897
1898 #[test]
1899 fn add_memo_works() {
1900 new_test_ext().execute_with(|| {
1901 let para_1 = new_para();
1902
1903 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_1, 1000, 1, 1, 9, None));
1904 assert_noop!(
1906 Crowdloan::add_memo(RuntimeOrigin::signed(1), para_1, b"hello, world".to_vec()),
1907 Error::<Test>::NoContributions,
1908 );
1909 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para_1, 100, None));
1911 assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, vec![]));
1912 assert_noop!(
1914 Crowdloan::add_memo(RuntimeOrigin::signed(1), para_1, vec![123; 123]),
1915 Error::<Test>::MemoTooLarge,
1916 );
1917 assert_ok!(Crowdloan::add_memo(
1919 RuntimeOrigin::signed(1),
1920 para_1,
1921 b"hello, world".to_vec()
1922 ));
1923 assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, b"hello, world".to_vec()));
1924 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para_1, 100, None));
1926 assert_eq!(Crowdloan::contribution_get(0u32, &1), (200, b"hello, world".to_vec()));
1927 });
1928 }
1929
1930 #[test]
1931 fn poke_works() {
1932 new_test_ext().execute_with(|| {
1933 let para_1 = new_para();
1934
1935 assert_ok!(TestAuctioneer::new_auction(5, 0));
1936 assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_1, 1000, 1, 1, 9, None));
1937 assert_noop!(
1939 Crowdloan::poke(RuntimeOrigin::signed(1), para_1),
1940 Error::<Test>::NoContributions
1941 );
1942 assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None));
1943 System::run_to_block::<AllPalletsWithSystem>(6);
1944 assert_ok!(Crowdloan::poke(RuntimeOrigin::signed(1), para_1));
1945 assert_eq!(crowdloan::NewRaise::<Test>::get(), vec![para_1]);
1946 assert_noop!(
1947 Crowdloan::poke(RuntimeOrigin::signed(1), para_1),
1948 Error::<Test>::AlreadyInNewRaise
1949 );
1950 });
1951 }
1952}
1953
1954#[cfg(feature = "runtime-benchmarks")]
1955mod benchmarking {
1956 use super::{Pezpallet as Crowdloan, *};
1957 use pezframe_support::{assert_ok, traits::OnInitialize};
1958 use pezframe_system::RawOrigin;
1959 use pezkuwi_runtime_teyrchains::paras;
1960 use pezsp_core::crypto::UncheckedFrom;
1961 use pezsp_runtime::traits::{Bounded, CheckedSub};
1962
1963 use pezframe_benchmarking::v2::*;
1964
1965 fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
1966 let events = pezframe_system::Pezpallet::<T>::events();
1967 let system_event: <T as pezframe_system::Config>::RuntimeEvent = generic_event.into();
1968 let pezframe_system::EventRecord { event, .. } = &events[events.len() - 1];
1970 assert_eq!(event, &system_event);
1971 }
1972
1973 fn create_fund<T: Config + paras::Config>(id: u32, end: BlockNumberFor<T>) -> ParaId {
1974 let cap = BalanceOf::<T>::max_value();
1975 let (_, offset) = T::Auctioneer::lease_period_length();
1976 pezframe_system::Pezpallet::<T>::set_block_number(offset);
1978 let now = pezframe_system::Pezpallet::<T>::block_number();
1979 let (lease_period_index, _) = T::Auctioneer::lease_period_index(now).unwrap_or_default();
1980 let first_period = lease_period_index;
1981 let last_period =
1982 lease_period_index + ((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into();
1983 let para_id = id.into();
1984
1985 let caller = account("fund_creator", id, 0);
1986 CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
1987
1988 let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
1990
1991 let head_data = T::Registrar::worst_head_data();
1992 let validation_code = T::Registrar::worst_validation_code();
1993 assert_ok!(T::Registrar::register(
1994 caller.clone(),
1995 para_id,
1996 head_data,
1997 validation_code.clone()
1998 ));
1999 assert_ok!(paras::Pezpallet::<T>::add_trusted_validation_code(
2000 pezframe_system::Origin::<T>::Root.into(),
2001 validation_code,
2002 ));
2003 T::Registrar::execute_pending_transitions();
2004
2005 assert_ok!(Crowdloan::<T>::create(
2006 RawOrigin::Signed(caller).into(),
2007 para_id,
2008 cap,
2009 first_period,
2010 last_period,
2011 end,
2012 Some(pubkey)
2013 ));
2014
2015 para_id
2016 }
2017
2018 fn contribute_fund<T: Config>(who: &T::AccountId, index: ParaId) {
2019 CurrencyOf::<T>::make_free_balance_be(&who, BalanceOf::<T>::max_value());
2020 let value = T::MinContribution::get();
2021
2022 let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
2023 let payload = (index, &who, BalanceOf::<T>::default(), value);
2024 let sig = crypto::create_ed25519_signature(&payload.encode(), pubkey);
2025
2026 assert_ok!(Crowdloan::<T>::contribute(
2027 RawOrigin::Signed(who.clone()).into(),
2028 index,
2029 value,
2030 Some(sig)
2031 ));
2032 }
2033
2034 #[benchmarks(
2035 where T: paras::Config,
2036 )]
2037 mod benchmarks {
2038 use super::*;
2039 use alloc::vec;
2040
2041 #[benchmark]
2042 fn create() -> Result<(), BenchmarkError> {
2043 let para_id = ParaId::from(1_u32);
2044 let cap = BalanceOf::<T>::max_value();
2045 let first_period = 0u32.into();
2046 let last_period = 3u32.into();
2047 let (lpl, offset) = T::Auctioneer::lease_period_length();
2048 let end = lpl + offset;
2049
2050 let caller: T::AccountId = whitelisted_caller();
2051 let head_data = T::Registrar::worst_head_data();
2052 let validation_code = T::Registrar::worst_validation_code();
2053
2054 let verifier = MultiSigner::unchecked_from(account::<[u8; 32]>("verifier", 0, 0));
2055
2056 CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
2057 T::Registrar::register(caller.clone(), para_id, head_data, validation_code.clone())?;
2058 assert_ok!(paras::Pezpallet::<T>::add_trusted_validation_code(
2059 pezframe_system::Origin::<T>::Root.into(),
2060 validation_code,
2061 ));
2062
2063 T::Registrar::execute_pending_transitions();
2064
2065 #[extrinsic_call]
2066 _(
2067 RawOrigin::Signed(caller),
2068 para_id,
2069 cap,
2070 first_period,
2071 last_period,
2072 end,
2073 Some(verifier),
2074 );
2075
2076 assert_last_event::<T>(Event::<T>::Created { para_id }.into());
2077 Ok(())
2078 }
2079
2080 #[benchmark]
2082 fn contribute() -> Result<(), BenchmarkError> {
2083 let (lpl, offset) = T::Auctioneer::lease_period_length();
2084 let end = lpl + offset;
2085 let fund_index = create_fund::<T>(1, end);
2086 let caller: T::AccountId = whitelisted_caller();
2087 let contribution = T::MinContribution::get();
2088 CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
2089 assert!(NewRaise::<T>::get().is_empty());
2090
2091 let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
2092 let payload = (fund_index, &caller, BalanceOf::<T>::default(), contribution);
2093 let sig = crypto::create_ed25519_signature(&payload.encode(), pubkey);
2094
2095 #[extrinsic_call]
2096 _(RawOrigin::Signed(caller.clone()), fund_index, contribution, Some(sig));
2097
2098 assert!(!NewRaise::<T>::get().is_empty());
2100 assert_last_event::<T>(
2101 Event::<T>::Contributed { who: caller, fund_index, amount: contribution }.into(),
2102 );
2103
2104 Ok(())
2105 }
2106
2107 #[benchmark]
2108 fn withdraw() -> Result<(), BenchmarkError> {
2109 let (lpl, offset) = T::Auctioneer::lease_period_length();
2110 let end = lpl + offset;
2111 let fund_index = create_fund::<T>(1337, end);
2112 let caller: T::AccountId = whitelisted_caller();
2113 let contributor = account("contributor", 0, 0);
2114 contribute_fund::<T>(&contributor, fund_index);
2115 pezframe_system::Pezpallet::<T>::set_block_number(BlockNumberFor::<T>::max_value());
2116 #[extrinsic_call]
2117 _(RawOrigin::Signed(caller), contributor.clone(), fund_index);
2118
2119 assert_last_event::<T>(
2120 Event::<T>::Withdrew {
2121 who: contributor,
2122 fund_index,
2123 amount: T::MinContribution::get(),
2124 }
2125 .into(),
2126 );
2127
2128 Ok(())
2129 }
2130
2131 #[benchmark(skip_meta)]
2133 fn refund(k: Linear<0, { T::RemoveKeysLimit::get() }>) -> Result<(), BenchmarkError> {
2134 let (lpl, offset) = T::Auctioneer::lease_period_length();
2135 let end = lpl + offset;
2136 let fund_index = create_fund::<T>(1337, end);
2137
2138 for i in 0..k {
2140 contribute_fund::<T>(&account("contributor", i, 0), fund_index);
2141 }
2142
2143 let caller: T::AccountId = whitelisted_caller();
2144 pezframe_system::Pezpallet::<T>::set_block_number(BlockNumberFor::<T>::max_value());
2145 #[extrinsic_call]
2146 _(RawOrigin::Signed(caller), fund_index);
2147
2148 assert_last_event::<T>(Event::<T>::AllRefunded { para_id: fund_index }.into());
2149 Ok(())
2150 }
2151
2152 #[benchmark]
2153 fn dissolve() -> Result<(), BenchmarkError> {
2154 let (lpl, offset) = T::Auctioneer::lease_period_length();
2155 let end = lpl + offset;
2156 let fund_index = create_fund::<T>(1337, end);
2157 let caller: T::AccountId = whitelisted_caller();
2158 pezframe_system::Pezpallet::<T>::set_block_number(BlockNumberFor::<T>::max_value());
2159 #[extrinsic_call]
2160 _(RawOrigin::Signed(caller.clone()), fund_index);
2161
2162 assert_last_event::<T>(Event::<T>::Dissolved { para_id: fund_index }.into());
2163 Ok(())
2164 }
2165
2166 #[benchmark]
2167 fn edit() -> Result<(), BenchmarkError> {
2168 let para_id = ParaId::from(1_u32);
2169 let cap = BalanceOf::<T>::max_value();
2170 let first_period = 0u32.into();
2171 let last_period = 3u32.into();
2172 let (lpl, offset) = T::Auctioneer::lease_period_length();
2173 let end = lpl + offset;
2174
2175 let caller: T::AccountId = whitelisted_caller();
2176 let head_data = T::Registrar::worst_head_data();
2177 let validation_code = T::Registrar::worst_validation_code();
2178
2179 let verifier = MultiSigner::unchecked_from(account::<[u8; 32]>("verifier", 0, 0));
2180
2181 CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
2182 T::Registrar::register(caller.clone(), para_id, head_data, validation_code.clone())?;
2183 assert_ok!(paras::Pezpallet::<T>::add_trusted_validation_code(
2184 pezframe_system::Origin::<T>::Root.into(),
2185 validation_code,
2186 ));
2187
2188 T::Registrar::execute_pending_transitions();
2189
2190 Crowdloan::<T>::create(
2191 RawOrigin::Signed(caller).into(),
2192 para_id,
2193 cap,
2194 first_period,
2195 last_period,
2196 end,
2197 Some(verifier.clone()),
2198 )?;
2199
2200 #[extrinsic_call]
2202 _(RawOrigin::Root, para_id, cap, first_period, last_period, end, Some(verifier));
2203
2204 assert_last_event::<T>(Event::<T>::Edited { para_id }.into());
2205
2206 Ok(())
2207 }
2208
2209 #[benchmark]
2210 fn add_memo() -> Result<(), BenchmarkError> {
2211 let (lpl, offset) = T::Auctioneer::lease_period_length();
2212 let end = lpl + offset;
2213 let fund_index = create_fund::<T>(1, end);
2214 let caller: T::AccountId = whitelisted_caller();
2215 contribute_fund::<T>(&caller, fund_index);
2216 let worst_memo = vec![42; T::MaxMemoLength::get().into()];
2217 #[extrinsic_call]
2218 _(RawOrigin::Signed(caller.clone()), fund_index, worst_memo.clone());
2219 let fund = Funds::<T>::get(fund_index).expect("fund was created...");
2220 assert_eq!(
2221 Crowdloan::<T>::contribution_get(fund.fund_index, &caller),
2222 (T::MinContribution::get(), worst_memo),
2223 );
2224 Ok(())
2225 }
2226
2227 #[benchmark]
2228 fn poke() -> Result<(), BenchmarkError> {
2229 let (lpl, offset) = T::Auctioneer::lease_period_length();
2230 let end = lpl + offset;
2231 let fund_index = create_fund::<T>(1, end);
2232 let caller: T::AccountId = whitelisted_caller();
2233 contribute_fund::<T>(&caller, fund_index);
2234 NewRaise::<T>::kill();
2235 assert!(NewRaise::<T>::get().is_empty());
2236 #[extrinsic_call]
2237 _(RawOrigin::Signed(caller), fund_index);
2238 assert!(!NewRaise::<T>::get().is_empty());
2239 assert_last_event::<T>(Event::<T>::AddedToNewRaise { para_id: fund_index }.into());
2240 Ok(())
2241 }
2242
2243 #[benchmark]
2248 fn on_initialize(n: Linear<2, 100>) -> Result<(), BenchmarkError> {
2249 let (lpl, offset) = T::Auctioneer::lease_period_length();
2250 let end_block = lpl + offset - 1u32.into();
2251
2252 let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
2253
2254 for i in 0..n {
2255 let fund_index = create_fund::<T>(i, end_block);
2256 let contributor: T::AccountId = account("contributor", i, 0);
2257 let contribution = T::MinContribution::get() * (i + 1).into();
2258 let payload = (fund_index, &contributor, BalanceOf::<T>::default(), contribution);
2259 let sig = crypto::create_ed25519_signature(&payload.encode(), pubkey.clone());
2260
2261 CurrencyOf::<T>::make_free_balance_be(&contributor, BalanceOf::<T>::max_value());
2262 Crowdloan::<T>::contribute(
2263 RawOrigin::Signed(contributor).into(),
2264 fund_index,
2265 contribution,
2266 Some(sig),
2267 )?;
2268 }
2269
2270 let now = pezframe_system::Pezpallet::<T>::block_number();
2271 let (lease_period_index, _) =
2272 T::Auctioneer::lease_period_index(now).unwrap_or_default();
2273 let duration = end_block
2274 .checked_sub(&pezframe_system::Pezpallet::<T>::block_number())
2275 .ok_or("duration of auction less than zero")?;
2276 T::Auctioneer::new_auction(duration, lease_period_index)?;
2277
2278 assert_eq!(
2279 T::Auctioneer::auction_status(end_block).is_ending(),
2280 Some((0u32.into(), 0u32.into()))
2281 );
2282 assert_eq!(NewRaise::<T>::get().len(), n as usize);
2283 let old_endings_count = EndingsCount::<T>::get();
2284 #[block]
2285 {
2286 let _ = Crowdloan::<T>::on_initialize(end_block);
2287 }
2288
2289 assert_eq!(EndingsCount::<T>::get(), old_endings_count + 1);
2290 assert_last_event::<T>(
2291 Event::<T>::HandleBidResult { para_id: (n - 1).into(), result: Ok(()) }.into(),
2292 );
2293 Ok(())
2294 }
2295
2296 impl_benchmark_test_suite!(
2297 Crowdloan,
2298 crate::integration_tests::new_test_ext_with_offset(10),
2299 crate::integration_tests::Test,
2300 );
2301 }
2302}