pezkuwi_runtime_common/crowdloan/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
2// This file is part of Pezkuwi.
3
4// Pezkuwi is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Pezkuwi is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Pezkuwi.  If not, see <http://www.gnu.org/licenses/>.
16
17//! # Teyrchain `Crowdloaning` pezpallet
18//!
19//! The point of this pezpallet is to allow teyrchain projects to offer the ability to help fund a
20//! deposit for the teyrchain. When the crowdloan has ended, the funds are returned.
21//!
22//! Each fund has a child-trie which stores all contributors account IDs together with the amount
23//! they contributed; the root of this can then be used by the teyrchain to allow contributors to
24//! prove that they made some particular contribution to the project (e.g. to be rewarded through
25//! some token or badge). The trie is retained for later (efficient) redistribution back to the
26//! contributors.
27//!
28//! Contributions must be of at least `MinContribution` (to account for the resources taken in
29//! tracking contributions), and may never tally greater than the fund's `cap`, set and fixed at the
30//! time of creation. The `create` call may be used to create a new fund. In order to do this, then
31//! a deposit must be paid of the amount `SubmissionDeposit`. Substantial resources are taken on
32//! the main trie in tracking a fund and this accounts for that.
33//!
34//! Funds may be set up during an auction period; their closing time is fixed at creation (as a
35//! block number) and if the fund is not successful by the closing time, then it can be dissolved.
36//! Funds may span multiple auctions, and even auctions that sell differing periods. However, for a
37//! fund to be active in bidding for an auction, it *must* have had *at least one bid* since the end
38//! of the last auction. Until a fund takes a further bid following the end of an auction, then it
39//! will be inactive.
40//!
41//! Contributors will get a refund of their contributions from completed funds before the crowdloan
42//! can be dissolved.
43//!
44//! Funds may accept contributions at any point before their success or end. When a teyrchain
45//! slot auction enters its ending period, then teyrchains will each place a bid; the bid will be
46//! raised once per block if the teyrchain had additional funds contributed since the last bid.
47//!
48//! Successful funds remain tracked (in the `Funds` storage item and the associated child trie) as
49//! long as the teyrchain remains active. Users can withdraw their funds once the slot is completed
50//! and funds are returned to the crowdloan account.
51
52pub 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/// Information on a funding effort for a pre-existing teyrchain. We assume that the teyrchain ID
139/// is known as it's used for the key of the storage item for which this is the value (`Funds`).
140#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
141#[codec(dumb_trait_bound)]
142pub struct FundInfo<AccountId, Balance, BlockNumber, LeasePeriod> {
143	/// The owning account who placed the deposit.
144	pub depositor: AccountId,
145	/// An optional verifier. If exists, contributions must be signed by verifier.
146	pub verifier: Option<MultiSigner>,
147	/// The amount of deposit placed.
148	pub deposit: Balance,
149	/// The total amount raised.
150	pub raised: Balance,
151	/// Block number after which the funding must have succeeded. If not successful at this number
152	/// then everyone may withdraw their funds.
153	pub end: BlockNumber,
154	/// A hard-cap on the amount that may be contributed.
155	pub cap: Balance,
156	/// The most recent block that this had a contribution. Determines if we make a bid or not.
157	/// If this is `Never`, this fund has never received a contribution.
158	/// If this is `PreEnding(n)`, this fund received a contribution sometime in auction
159	/// number `n` before the ending period.
160	/// If this is `Ending(n)`, this fund received a contribution during the current ending period,
161	/// where `n` is how far into the ending period the contribution was made.
162	pub last_contribution: LastContribution<BlockNumber>,
163	/// First lease period in range to bid on; it's actually a `LeasePeriod`, but that's the same
164	/// type as `BlockNumber`.
165	pub first_period: LeasePeriod,
166	/// Last lease period in range to bid on; it's actually a `LeasePeriod`, but that's the same
167	/// type as `BlockNumber`.
168	pub last_period: LeasePeriod,
169	/// Unique index used to represent this fund.
170	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	/// The in-code storage version.
180	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		/// `PalletId` for the crowdloan pezpallet. An appropriate value could be
194		/// `PalletId(*b"py/cfund")`
195		#[pezpallet::constant]
196		type PalletId: Get<PalletId>;
197
198		/// The amount to be held on deposit by the depositor of a crowdloan.
199		type SubmissionDeposit: Get<BalanceOf<Self>>;
200
201		/// The minimum amount that may be contributed into a crowdloan. Should almost certainly be
202		/// at least `ExistentialDeposit`.
203		#[pezpallet::constant]
204		type MinContribution: Get<BalanceOf<Self>>;
205
206		/// Max number of storage keys to remove per extrinsic call.
207		#[pezpallet::constant]
208		type RemoveKeysLimit: Get<u32>;
209
210		/// The teyrchain registrar type. We just use this to ensure that only the manager of a para
211		/// is able to start a crowdloan for its slot.
212		type Registrar: Registrar<AccountId = Self::AccountId>;
213
214		/// The type representing the auctioning system.
215		type Auctioneer: Auctioneer<
216			BlockNumberFor<Self>,
217			AccountId = Self::AccountId,
218			LeasePeriod = BlockNumberFor<Self>,
219		>;
220
221		/// The maximum length for the memo attached to a crowdloan contribution.
222		type MaxMemoLength: Get<u8>;
223
224		/// Weight Information for the Extrinsics in the Pezpallet
225		type WeightInfo: WeightInfo;
226	}
227
228	/// Info on all of the funds.
229	#[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	/// The funds that have had additional contributions during the last block. This is used
238	/// in order to determine which funds should submit new or updated bids.
239	#[pezpallet::storage]
240	pub type NewRaise<T> = StorageValue<_, Vec<ParaId>, ValueQuery>;
241
242	/// The number of auctions that have entered into their ending period so far.
243	#[pezpallet::storage]
244	pub type EndingsCount<T> = StorageValue<_, u32, ValueQuery>;
245
246	/// Tracker for the next available fund index
247	#[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		/// Create a new crowdloaning campaign.
254		Created { para_id: ParaId },
255		/// Contributed to a crowd sale.
256		Contributed { who: T::AccountId, fund_index: ParaId, amount: BalanceOf<T> },
257		/// Withdrew full balance of a contributor.
258		Withdrew { who: T::AccountId, fund_index: ParaId, amount: BalanceOf<T> },
259		/// The loans in a fund have been partially dissolved, i.e. there are some left
260		/// over child keys that still need to be killed.
261		PartiallyRefunded { para_id: ParaId },
262		/// All loans in a fund have been refunded.
263		AllRefunded { para_id: ParaId },
264		/// Fund is dissolved.
265		Dissolved { para_id: ParaId },
266		/// The result of trying to submit a new bid to the Slots pezpallet.
267		HandleBidResult { para_id: ParaId, result: DispatchResult },
268		/// The configuration to a crowdloan has been edited.
269		Edited { para_id: ParaId },
270		/// A memo has been updated.
271		MemoUpdated { who: T::AccountId, para_id: ParaId, memo: Vec<u8> },
272		/// A teyrchain has been moved to `NewRaise`
273		AddedToNewRaise { para_id: ParaId },
274	}
275
276	#[pezpallet::error]
277	pub enum Error<T> {
278		/// The current lease period is more than the first lease period.
279		FirstPeriodInPast,
280		/// The first lease period needs to at least be less than 3 `max_value`.
281		FirstPeriodTooFarInFuture,
282		/// Last lease period must be greater than first lease period.
283		LastPeriodBeforeFirstPeriod,
284		/// The last lease period cannot be more than 3 periods after the first period.
285		LastPeriodTooFarInFuture,
286		/// The campaign ends before the current block number. The end must be in the future.
287		CannotEndInPast,
288		/// The end date for this crowdloan is not sensible.
289		EndTooFarInFuture,
290		/// There was an overflow.
291		Overflow,
292		/// The contribution was below the minimum, `MinContribution`.
293		ContributionTooSmall,
294		/// Invalid fund index.
295		InvalidParaId,
296		/// Contributions exceed maximum amount.
297		CapExceeded,
298		/// The contribution period has already ended.
299		ContributionPeriodOver,
300		/// The origin of this call is invalid.
301		InvalidOrigin,
302		/// This crowdloan does not correspond to a teyrchain.
303		NotTeyrchain,
304		/// This teyrchain lease is still active and retirement cannot yet begin.
305		LeaseActive,
306		/// This teyrchain's bid or lease is still active and withdraw cannot yet begin.
307		BidOrLeaseActive,
308		/// The crowdloan has not yet ended.
309		FundNotEnded,
310		/// There are no contributions stored in this crowdloan.
311		NoContributions,
312		/// The crowdloan is not ready to dissolve. Potentially still has a slot or in retirement
313		/// period.
314		NotReadyToDissolve,
315		/// Invalid signature.
316		InvalidSignature,
317		/// The provided memo is too large.
318		MemoTooLarge,
319		/// The fund is already in `NewRaise`
320		AlreadyInNewRaise,
321		/// No contributions allowed during the VRF delay
322		VrfDelayInProgress,
323		/// A lease period has not started yet, due to an offset in the starting block.
324		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				// This is the very first block in the ending period
332				if sample.is_zero() && sub_sample.is_zero() {
333					// first block of ending period.
334					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					// Care needs to be taken by the crowdloan creator that this function will
342					// succeed given the crowdloaning configuration. We do some checks ahead of time
343					// in crowdloan `create`.
344					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		/// Create a new crowdloaning campaign for a teyrchain slot with the given lease period
364		/// range.
365		///
366		/// This applies a lock to your teyrchain configuration, ensuring that it cannot be changed
367		/// by the teyrchain manager.
368		#[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			// Here we check the lease period on the ending block is at most the first block of the
390			// period after `first_period`. If it would be larger, there is no way we could win an
391			// active auction, thus it would make no sense to have a crowdloan this long.
392			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			// Can't start a crowdloan for a lease period that already passed.
402			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			// There should not be an existing fund.
407			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		/// Contribute to a crowd sale. This will transfer some balance over to fund a teyrchain
444		/// slot. It will be withdrawable when the crowdloan has ended and the funds are unused.
445		#[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		/// Withdraw full balance of a specific contributor.
458		///
459		/// Origin must be signed, but can come from anyone.
460		///
461		/// The fund must be either in, or ready for, retirement. For a fund to be *in* retirement,
462		/// then the retirement flag must be set. For a fund to be ready for retirement, then:
463		/// - it must not already be in retirement;
464		/// - the amount of raised funds must be bigger than the _free_ balance of the account;
465		/// - and either:
466		///   - the block number must be at least `end`; or
467		///   - the current lease period must be greater than the fund's `last_period`.
468		///
469		/// In this case, the fund's retirement flag is set and its `end` is reset to the current
470		/// block number.
471		///
472		/// - `who`: The account whose contribution should be withdrawn.
473		/// - `index`: The teyrchain to whose crowdloan the contribution was made.
474		#[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		/// Automatically refund contributors of an ended crowdloan.
504		/// Due to weight restrictions, this function may need to be called multiple
505		/// times to fully refund all users. We will refund `RemoveKeysLimit` users at a time.
506		///
507		/// Origin must be signed, but can come from anyone.
508		#[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			// Try killing the crowdloan child trie
523			let contributions = Self::contribution_iterator(fund.fund_index);
524			// Assume everyone will be refunded.
525			let mut all_refunded = true;
526			for (who, (balance, _)) in contributions {
527				if refund_count >= T::RemoveKeysLimit::get() {
528					// Not everyone was able to be refunded this time around.
529					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			// Save the changes.
540			Funds::<T>::insert(index, &fund);
541
542			if all_refunded {
543				Self::deposit_event(Event::<T>::AllRefunded { para_id: index });
544				// Refund for unused refund count.
545				Ok(Some(T::WeightInfo::refund(refund_count)).into())
546			} else {
547				Self::deposit_event(Event::<T>::PartiallyRefunded { para_id: index });
548				// No weight to refund since we did not finish the loop.
549				Ok(().into())
550			}
551		}
552
553		/// Remove a fund after the retirement period has ended and all funds have been returned.
554		#[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			// Only allow dissolution when the raised funds goes to zero,
567			// and the caller is the fund creator or we are past the end date.
568			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			// Assuming state is not corrupted, the child trie should already be cleaned up
573			// and all funds in the crowdloan account have been returned. If not, governance
574			// can take care of that.
575			debug_assert!(Self::contribution_iterator(fund.fund_index).count().is_zero());
576
577			// Crowdloan over, burn all funds.
578			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		/// Edit the configuration for an in-progress crowdloan.
588		///
589		/// Can only be called by Root origin.
590		#[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		/// Add an optional memo to an existing crowdloan contribution.
626		///
627		/// Origin must be Signed, and the user must have contributed to the crowdloan.
628		#[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		/// Poke the fund into `NewRaise`
645		///
646		/// Origin must be Signed, and the fund has non-zero raise.
647		#[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		/// Contribute your entire balance to a crowd sale. This will transfer the entire balance of
660		/// a user over to fund a teyrchain slot. It will be withdrawable when the crowdloan has
661		/// ended and the funds are unused.
662		#[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	/// The account ID of the fund pot.
678	///
679	/// This actually does computation. If you need to keep using it, then make sure you cache the
680	/// value and only call this once.
681	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	/// This function checks all conditions which would qualify a crowdloan has ended.
726	/// * If we have reached the `fund.end` block OR the first lease period the fund is trying to
727	///   bid for has started already.
728	/// * And, if the fund has enough free funds to refund full raised amount.
729	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		// `fund.end` can represent the end of a failed crowdloan or the beginning of retirement
735		// If the current lease period is past the first period they are trying to bid for, then
736		// it is already too late to win the bid.
737		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		// free balance must greater than or equal amount raised, otherwise funds are being used
744		// and a bid or lease must be active.
745		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		// Make sure crowdloan has not ended
766		let now = pezframe_system::Pezpallet::<T>::block_number();
767		ensure!(now < fund.end, Error::<T>::ContributionPeriodOver);
768
769		// Make sure crowdloan is in a valid lease period
770		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		// Make sure crowdloan has not already won.
776		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		// We disallow any crowdloan contributions during the VRF Period, so that people do not
783		// sneak their contributions into the auction when it would not impact the outcome.
784		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				// In ending period; must ensure that we are in NewRaise.
806				LastContribution::Ending(n) if n == now => {
807					// do nothing - already in NewRaise
808				},
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					// Not in ending period and no auctions have ended ending since our
819					// previous bid which was also not in an ending period.
820					// `NewRaise` will contain our ID still: Do nothing.
821				},
822				_ => {
823					// Not in ending period; but an auction has been ending since our previous
824					// bid, or we never had one to begin with. Add bid.
825					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	// The testing primitives are very useful for avoiding having to work with signatures
871	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
872	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	// Emulate what would happen if we won an auction:
965	// balance is reserved and a deposit_held is recorded
966	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				// Optional VRF delay
1014				if after_end < vrf_delay() {
1015					return AuctionStatus::VrfDelay(after_end);
1016				} else {
1017					// VRF delay is done, so we just end the auction
1018					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	// This function basically just builds a genesis storage key/value store according to
1085	// our desired mockup.
1086	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			// Now try to create a crowdloan campaign
1156			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 9, None));
1157			// This is what the initial `fund_info` should look like
1158			let fund_info = FundInfo {
1159				depositor: 1,
1160				verifier: None,
1161				deposit: 1,
1162				raised: 0,
1163				// 5 blocks length + 3 block ending period + 1 starting block
1164				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			// User has deposit removed from their free balance
1173			assert_eq!(Balances::free_balance(1), 999);
1174			// Deposit is placed in reserved
1175			assert_eq!(Balances::reserved_balance(1), 1);
1176			// No new raise until first contribution
1177			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			// Now try to create a crowdloan campaign
1188			assert_ok!(Crowdloan::create(
1189				RuntimeOrigin::signed(1),
1190				para,
1191				1000,
1192				1,
1193				4,
1194				9,
1195				Some(pubkey.clone())
1196			));
1197			// This is what the initial `fund_info` should look like
1198			let fund_info = FundInfo {
1199				depositor: 1,
1200				verifier: Some(pubkey),
1201				deposit: 1,
1202				raised: 0,
1203				// 5 blocks length + 3 block ending period + 1 starting block
1204				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			// User has deposit removed from their free balance
1213			assert_eq!(Balances::free_balance(1), 999);
1214			// Deposit is placed in reserved
1215			assert_eq!(Balances::reserved_balance(1), 1);
1216			// No new raise until first contribution
1217			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			// Now try to create a crowdloan campaign
1226			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			// Cannot create a crowdloan with bad lease periods
1234			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			// Cannot create a crowdloan without some deposit funds
1240			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			// Cannot create a crowdloan with nonsense end date
1261			// This crowdloan would end in lease period 2, but is bidding for some slot that starts
1262			// in lease period 1.
1263			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			// Set up a crowdloan
1277			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 9, None));
1278
1279			// No contributions yet
1280			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
1281
1282			// User 1 contributes to their own crowdloan
1283			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None));
1284			// User 1 has spent some funds to do this, transfer fees **are** taken
1285			assert_eq!(Balances::free_balance(1), 950);
1286			// Contributions are stored in the trie
1287			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 49);
1288			// Contributions appear in free balance of crowdloan
1289			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(index)), 49);
1290			// Crowdloan is added to NewRaise
1291			assert_eq!(crowdloan::NewRaise::<Test>::get(), vec![para]);
1292
1293			let fund = crowdloan::Funds::<Test>::get(para).unwrap();
1294
1295			// Last contribution time recorded
1296			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			// Set up a crowdloan
1308			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			// No contributions yet
1319			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
1320
1321			// Missing signature
1322			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			// Invalid signature
1334			assert_noop!(
1335				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, Some(invalid_signature)),
1336				Error::<Test>::InvalidSignature
1337			);
1338
1339			// Valid signature wrong parameter
1340			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			// Valid signature
1360			assert_ok!(Crowdloan::contribute(
1361				RuntimeOrigin::signed(1),
1362				para,
1363				49,
1364				Some(valid_signature.clone())
1365			));
1366
1367			// Reuse valid signature
1368			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			// New valid signature
1377			assert_ok!(Crowdloan::contribute(
1378				RuntimeOrigin::signed(1),
1379				para,
1380				10,
1381				Some(valid_signature_2)
1382			));
1383
1384			// Contributions appear in free balance of crowdloan
1385			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(index)), 59);
1386
1387			// Contribution amount is correct
1388			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			// Cannot contribute to non-existing fund
1399			assert_noop!(
1400				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None),
1401				Error::<Test>::InvalidParaId
1402			);
1403			// Cannot contribute below minimum contribution
1404			assert_noop!(
1405				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 9, None),
1406				Error::<Test>::ContributionTooSmall
1407			);
1408
1409			// Set up a crowdloan
1410			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			// Cannot contribute past the limit
1414			assert_noop!(
1415				Crowdloan::contribute(RuntimeOrigin::signed(2), para, 900, None),
1416				Error::<Test>::CapExceeded
1417			);
1418
1419			// Move past end date
1420			System::run_to_block::<AllPalletsWithSystem>(10);
1421
1422			// Cannot contribute to ended fund
1423			assert_noop!(
1424				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None),
1425				Error::<Test>::ContributionPeriodOver
1426			);
1427
1428			// If a crowdloan has already won, it should not allow contributions.
1429			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			// Emulate a win by leasing out and putting a deposit. Slots pezpallet would normally do
1433			// this.
1434			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			// Move past lease period 1, should not be allowed to have further contributions with a
1442			// crowdloan that has starting period 1.
1443			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			// Set up a crowdloan
1467			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			// Can def contribute when auction is running.
1479			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			// Can't contribute when auction is in the VRF delay period.
1484			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			// Its fine to contribute when no auction is running.
1492			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			// Set up a crowdloan
1508			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			// Fund crowdloan
1520			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			// Endings count incremented
1540			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			// Set up a crowdloan
1551			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			// para has no reserved funds, indicating it did not win the auction.
1558			assert_eq!(Balances::reserved_balance(&account_id), 0);
1559			// but there's still the funds in its balance.
1560			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			// Set up a crowdloan
1582			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			// user sends the crowdloan funds trying to make an accounting error
1589			assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), account_id, 10));
1590
1591			// overfunded now
1592			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			// Some funds are left over
1599			assert_eq!(Balances::free_balance(&account_id), 10);
1600			// Remaining funds will be burned
1601			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			// Set up a crowdloan ending on 9
1615			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1616			// Make some contributions
1617			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			// Can't refund before the crowdloan it has ended
1624			assert_noop!(
1625				Crowdloan::refund(RuntimeOrigin::signed(1337), para),
1626				Error::<Test>::FundNotEnded,
1627			);
1628
1629			// Move to the end of the crowdloan
1630			System::run_to_block::<AllPalletsWithSystem>(10);
1631			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para));
1632
1633			// Funds are returned
1634			assert_eq!(Balances::free_balance(account_id), 0);
1635			// 1 deposit for the crowdloan which hasn't dissolved yet.
1636			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			// Set up a crowdloan ending on 9
1650			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 100000, 1, 1, 9, None));
1651			// Make more contributions than our limit
1652			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			// Move to the end of the crowdloan
1665			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			// Funds still left over
1673			assert!(!Balances::free_balance(account_id).is_zero());
1674
1675			// Call again
1676			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para));
1677			assert_eq!(last_event(), super::Event::<Test>::AllRefunded { para_id: para }.into());
1678
1679			// Funds are returned
1680			assert_eq!(Balances::free_balance(account_id), 0);
1681			// 1 deposit for the crowdloan which hasn't dissolved yet.
1682			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			// Set up a crowdloan
1695			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			// All funds are refunded
1701			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1702
1703			// Now that `fund.raised` is zero, it can be dissolved.
1704			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	// Regression test to check that a pot account with just one provider can be dissolved.
1713	#[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			// Set up a crowdloan
1720			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			// We test the historic case where crowdloan accounts only have one provider:
1727			{
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			// All funds are refunded
1735			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1736
1737			// Now that `fund.raised` is zero, it can be dissolved.
1738			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			// Set up a crowdloan
1754			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			// Can't dissolve before it ends
1759			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			// Can't dissolve when it won.
1767			assert_noop!(
1768				Crowdloan::dissolve(RuntimeOrigin::signed(1), para),
1769				Error::<Test>::NotReadyToDissolve
1770			);
1771			set_winner(para, 1, false);
1772
1773			// Can't dissolve while it still has user funds
1774			assert_noop!(
1775				Crowdloan::dissolve(RuntimeOrigin::signed(1), para),
1776				Error::<Test>::NotReadyToDissolve
1777			);
1778
1779			// All funds are refunded
1780			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1781
1782			// Now that `fund.raised` is zero, it can be dissolved.
1783			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			// Set up a crowdloan
1801			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1802
1803			// Fund crowdloans.
1804			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1805			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None));
1806			// simulate the reserving of para's funds. this actually happens in the Slots pezpallet.
1807			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			// simulate the unreserving of para's funds, now that the lease expired. this actually
1817			// happens in the Slots pezpallet.
1818			Balances::unreserve(&account_id, 150);
1819
1820			// para has no reserved funds, indicating it did ot win the auction.
1821			assert_eq!(Balances::reserved_balance(&account_id), 0);
1822			// but there's still the funds in its balance.
1823			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			// Set up crowdloans
1844			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			// Different contributions
1847			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None));
1848			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para_2, 50, None));
1849			// Original state
1850			assert_eq!(Funds::<Test>::get(para_1).unwrap().raised, 100);
1851			assert_eq!(Funds::<Test>::get(para_2).unwrap().raised, 50);
1852			// Swap
1853			Crowdloan::on_swap(para_1, para_2);
1854			// Final state
1855			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			// Cannot create a fund again
1867			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			// Some things stay the same
1887			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			// Some things change
1892			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			// Cant add a memo before you have contributed.
1905			assert_noop!(
1906				Crowdloan::add_memo(RuntimeOrigin::signed(1), para_1, b"hello, world".to_vec()),
1907				Error::<Test>::NoContributions,
1908			);
1909			// Make a contribution. Initially no memo.
1910			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para_1, 100, None));
1911			assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, vec![]));
1912			// Can't place a memo that is too large.
1913			assert_noop!(
1914				Crowdloan::add_memo(RuntimeOrigin::signed(1), para_1, vec![123; 123]),
1915				Error::<Test>::MemoTooLarge,
1916			);
1917			// Adding a memo to an existing contribution works
1918			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			// Can contribute again and data persists
1925			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			// Should fail when no contributions.
1938			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		// compare to the last event record
1969		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		// Set to the very beginning of lease period index 0.
1977		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		// Assume ed25519 is most complex signature format
1989		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		// Contribute has two arms: PreEnding and Ending, but both are equal complexity.
2081		#[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			// NewRaise is appended to, so we don't need to fill it up for worst case scenario.
2099			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		// Worst case: Refund removes `RemoveKeysLimit` keys, and is fully refunded.
2132		#[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			// Dissolve will remove at most `RemoveKeysLimit` at once.
2139			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			// Doesn't matter what we edit to, so use the same values.
2201			#[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		// Worst case scenario: N funds are all in the `NewRaise` list, we are
2244		// in the beginning of the ending period, and each fund outbids the next
2245		// over the same periods.
2246		// We test the complexity over different number of new raise
2247		#[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}