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