referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_common/crowdloan/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot 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// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! # Parachain `Crowdloaning` pallet
18//!
19//! The point of this pallet is to allow parachain projects to offer the ability to help fund a
20//! deposit for the parachain. 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 parachain 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 parachain
45//! slot auction enters its ending period, then parachains will each place a bid; the bid will be
46//! raised once per block if the parachain 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 parachain 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::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/// Information on a funding effort for a pre-existing parachain. We assume that the parachain 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#[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	/// The in-code storage version.
180	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		/// `PalletId` for the crowdloan pallet. An appropriate value could be
193		/// `PalletId(*b"py/cfund")`
194		#[pallet::constant]
195		type PalletId: Get<PalletId>;
196
197		/// The amount to be held on deposit by the depositor of a crowdloan.
198		type SubmissionDeposit: Get<BalanceOf<Self>>;
199
200		/// The minimum amount that may be contributed into a crowdloan. Should almost certainly be
201		/// at least `ExistentialDeposit`.
202		#[pallet::constant]
203		type MinContribution: Get<BalanceOf<Self>>;
204
205		/// Max number of storage keys to remove per extrinsic call.
206		#[pallet::constant]
207		type RemoveKeysLimit: Get<u32>;
208
209		/// The parachain registrar type. We just use this to ensure that only the manager of a para
210		/// is able to start a crowdloan for its slot.
211		type Registrar: Registrar<AccountId = Self::AccountId>;
212
213		/// The type representing the auctioning system.
214		type Auctioneer: Auctioneer<
215			BlockNumberFor<Self>,
216			AccountId = Self::AccountId,
217			LeasePeriod = BlockNumberFor<Self>,
218		>;
219
220		/// The maximum length for the memo attached to a crowdloan contribution.
221		type MaxMemoLength: Get<u8>;
222
223		/// Weight Information for the Extrinsics in the Pallet
224		type WeightInfo: WeightInfo;
225	}
226
227	/// Info on all of the funds.
228	#[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	/// The funds that have had additional contributions during the last block. This is used
237	/// in order to determine which funds should submit new or updated bids.
238	#[pallet::storage]
239	pub type NewRaise<T> = StorageValue<_, Vec<ParaId>, ValueQuery>;
240
241	/// The number of auctions that have entered into their ending period so far.
242	#[pallet::storage]
243	pub type EndingsCount<T> = StorageValue<_, u32, ValueQuery>;
244
245	/// Tracker for the next available fund index
246	#[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		/// Create a new crowdloaning campaign.
253		Created { para_id: ParaId },
254		/// Contributed to a crowd sale.
255		Contributed { who: T::AccountId, fund_index: ParaId, amount: BalanceOf<T> },
256		/// Withdrew full balance of a contributor.
257		Withdrew { who: T::AccountId, fund_index: ParaId, amount: BalanceOf<T> },
258		/// The loans in a fund have been partially dissolved, i.e. there are some left
259		/// over child keys that still need to be killed.
260		PartiallyRefunded { para_id: ParaId },
261		/// All loans in a fund have been refunded.
262		AllRefunded { para_id: ParaId },
263		/// Fund is dissolved.
264		Dissolved { para_id: ParaId },
265		/// The result of trying to submit a new bid to the Slots pallet.
266		HandleBidResult { para_id: ParaId, result: DispatchResult },
267		/// The configuration to a crowdloan has been edited.
268		Edited { para_id: ParaId },
269		/// A memo has been updated.
270		MemoUpdated { who: T::AccountId, para_id: ParaId, memo: Vec<u8> },
271		/// A parachain has been moved to `NewRaise`
272		AddedToNewRaise { para_id: ParaId },
273	}
274
275	#[pallet::error]
276	pub enum Error<T> {
277		/// The current lease period is more than the first lease period.
278		FirstPeriodInPast,
279		/// The first lease period needs to at least be less than 3 `max_value`.
280		FirstPeriodTooFarInFuture,
281		/// Last lease period must be greater than first lease period.
282		LastPeriodBeforeFirstPeriod,
283		/// The last lease period cannot be more than 3 periods after the first period.
284		LastPeriodTooFarInFuture,
285		/// The campaign ends before the current block number. The end must be in the future.
286		CannotEndInPast,
287		/// The end date for this crowdloan is not sensible.
288		EndTooFarInFuture,
289		/// There was an overflow.
290		Overflow,
291		/// The contribution was below the minimum, `MinContribution`.
292		ContributionTooSmall,
293		/// Invalid fund index.
294		InvalidParaId,
295		/// Contributions exceed maximum amount.
296		CapExceeded,
297		/// The contribution period has already ended.
298		ContributionPeriodOver,
299		/// The origin of this call is invalid.
300		InvalidOrigin,
301		/// This crowdloan does not correspond to a parachain.
302		NotParachain,
303		/// This parachain lease is still active and retirement cannot yet begin.
304		LeaseActive,
305		/// This parachain's bid or lease is still active and withdraw cannot yet begin.
306		BidOrLeaseActive,
307		/// The crowdloan has not yet ended.
308		FundNotEnded,
309		/// There are no contributions stored in this crowdloan.
310		NoContributions,
311		/// The crowdloan is not ready to dissolve. Potentially still has a slot or in retirement
312		/// period.
313		NotReadyToDissolve,
314		/// Invalid signature.
315		InvalidSignature,
316		/// The provided memo is too large.
317		MemoTooLarge,
318		/// The fund is already in `NewRaise`
319		AlreadyInNewRaise,
320		/// No contributions allowed during the VRF delay
321		VrfDelayInProgress,
322		/// A lease period has not started yet, due to an offset in the starting block.
323		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				// This is the very first block in the ending period
331				if sample.is_zero() && sub_sample.is_zero() {
332					// first block of ending period.
333					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					// Care needs to be taken by the crowdloan creator that this function will
341					// succeed given the crowdloaning configuration. We do some checks ahead of time
342					// in crowdloan `create`.
343					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		/// Create a new crowdloaning campaign for a parachain slot with the given lease period
363		/// range.
364		///
365		/// This applies a lock to your parachain configuration, ensuring that it cannot be changed
366		/// by the parachain manager.
367		#[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			// Here we check the lease period on the ending block is at most the first block of the
389			// period after `first_period`. If it would be larger, there is no way we could win an
390			// active auction, thus it would make no sense to have a crowdloan this long.
391			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			// Can't start a crowdloan for a lease period that already passed.
401			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			// There should not be an existing fund.
406			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		/// Contribute to a crowd sale. This will transfer some balance over to fund a parachain
443		/// slot. It will be withdrawable when the crowdloan has ended and the funds are unused.
444		#[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		/// Withdraw full balance of a specific contributor.
457		///
458		/// Origin must be signed, but can come from anyone.
459		///
460		/// The fund must be either in, or ready for, retirement. For a fund to be *in* retirement,
461		/// then the retirement flag must be set. For a fund to be ready for retirement, then:
462		/// - it must not already be in retirement;
463		/// - the amount of raised funds must be bigger than the _free_ balance of the account;
464		/// - and either:
465		///   - the block number must be at least `end`; or
466		///   - the current lease period must be greater than the fund's `last_period`.
467		///
468		/// In this case, the fund's retirement flag is set and its `end` is reset to the current
469		/// block number.
470		///
471		/// - `who`: The account whose contribution should be withdrawn.
472		/// - `index`: The parachain to whose crowdloan the contribution was made.
473		#[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		/// Automatically refund contributors of an ended crowdloan.
503		/// Due to weight restrictions, this function may need to be called multiple
504		/// times to fully refund all users. We will refund `RemoveKeysLimit` users at a time.
505		///
506		/// Origin must be signed, but can come from anyone.
507		#[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			// Try killing the crowdloan child trie
522			let contributions = Self::contribution_iterator(fund.fund_index);
523			// Assume everyone will be refunded.
524			let mut all_refunded = true;
525			for (who, (balance, _)) in contributions {
526				if refund_count >= T::RemoveKeysLimit::get() {
527					// Not everyone was able to be refunded this time around.
528					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			// Save the changes.
539			Funds::<T>::insert(index, &fund);
540
541			if all_refunded {
542				Self::deposit_event(Event::<T>::AllRefunded { para_id: index });
543				// Refund for unused refund count.
544				Ok(Some(T::WeightInfo::refund(refund_count)).into())
545			} else {
546				Self::deposit_event(Event::<T>::PartiallyRefunded { para_id: index });
547				// No weight to refund since we did not finish the loop.
548				Ok(().into())
549			}
550		}
551
552		/// Remove a fund after the retirement period has ended and all funds have been returned.
553		#[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			// Only allow dissolution when the raised funds goes to zero,
563			// and the caller is the fund creator or we are past the end date.
564			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			// Assuming state is not corrupted, the child trie should already be cleaned up
569			// and all funds in the crowdloan account have been returned. If not, governance
570			// can take care of that.
571			debug_assert!(Self::contribution_iterator(fund.fund_index).count().is_zero());
572
573			// Crowdloan over, burn all funds.
574			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		/// Edit the configuration for an in-progress crowdloan.
584		///
585		/// Can only be called by Root origin.
586		#[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		/// Add an optional memo to an existing crowdloan contribution.
622		///
623		/// Origin must be Signed, and the user must have contributed to the crowdloan.
624		#[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		/// Poke the fund into `NewRaise`
641		///
642		/// Origin must be Signed, and the fund has non-zero raise.
643		#[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		/// Contribute your entire balance to a crowd sale. This will transfer the entire balance of
656		/// a user over to fund a parachain slot. It will be withdrawable when the crowdloan has
657		/// ended and the funds are unused.
658		#[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	/// The account ID of the fund pot.
674	///
675	/// This actually does computation. If you need to keep using it, then make sure you cache the
676	/// value and only call this once.
677	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	/// This function checks all conditions which would qualify a crowdloan has ended.
722	/// * If we have reached the `fund.end` block OR the first lease period the fund is trying to
723	///   bid for has started already.
724	/// * And, if the fund has enough free funds to refund full raised amount.
725	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		// `fund.end` can represent the end of a failed crowdloan or the beginning of retirement
731		// If the current lease period is past the first period they are trying to bid for, then
732		// it is already too late to win the bid.
733		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		// free balance must greater than or equal amount raised, otherwise funds are being used
740		// and a bid or lease must be active.
741		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		// Make sure crowdloan has not ended
762		let now = frame_system::Pallet::<T>::block_number();
763		ensure!(now < fund.end, Error::<T>::ContributionPeriodOver);
764
765		// Make sure crowdloan is in a valid lease period
766		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		// Make sure crowdloan has not already won.
772		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		// We disallow any crowdloan contributions during the VRF Period, so that people do not
779		// sneak their contributions into the auction when it would not impact the outcome.
780		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				// In ending period; must ensure that we are in NewRaise.
802				LastContribution::Ending(n) if n == now => {
803					// do nothing - already in NewRaise
804				},
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					// Not in ending period and no auctions have ended ending since our
815					// previous bid which was also not in an ending period.
816					// `NewRaise` will contain our ID still: Do nothing.
817				},
818				_ => {
819					// Not in ending period; but an auction has been ending since our previous
820					// bid, or we never had one to begin with. Add bid.
821					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	// The testing primitives are very useful for avoiding having to work with signatures
867	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
868	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	// Emulate what would happen if we won an auction:
961	// balance is reserved and a deposit_held is recorded
962	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				// Optional VRF delay
1010				if after_end < vrf_delay() {
1011					return AuctionStatus::VrfDelay(after_end);
1012				} else {
1013					// VRF delay is done, so we just end the auction
1014					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	// This function basically just builds a genesis storage key/value store according to
1081	// our desired mockup.
1082	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			// Now try to create a crowdloan campaign
1152			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 9, None));
1153			// This is what the initial `fund_info` should look like
1154			let fund_info = FundInfo {
1155				depositor: 1,
1156				verifier: None,
1157				deposit: 1,
1158				raised: 0,
1159				// 5 blocks length + 3 block ending period + 1 starting block
1160				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			// User has deposit removed from their free balance
1169			assert_eq!(Balances::free_balance(1), 999);
1170			// Deposit is placed in reserved
1171			assert_eq!(Balances::reserved_balance(1), 1);
1172			// No new raise until first contribution
1173			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			// Now try to create a crowdloan campaign
1184			assert_ok!(Crowdloan::create(
1185				RuntimeOrigin::signed(1),
1186				para,
1187				1000,
1188				1,
1189				4,
1190				9,
1191				Some(pubkey.clone())
1192			));
1193			// This is what the initial `fund_info` should look like
1194			let fund_info = FundInfo {
1195				depositor: 1,
1196				verifier: Some(pubkey),
1197				deposit: 1,
1198				raised: 0,
1199				// 5 blocks length + 3 block ending period + 1 starting block
1200				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			// User has deposit removed from their free balance
1209			assert_eq!(Balances::free_balance(1), 999);
1210			// Deposit is placed in reserved
1211			assert_eq!(Balances::reserved_balance(1), 1);
1212			// No new raise until first contribution
1213			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			// Now try to create a crowdloan campaign
1222			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			// Cannot create a crowdloan with bad lease periods
1230			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			// Cannot create a crowdloan without some deposit funds
1236			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			// Cannot create a crowdloan with nonsense end date
1257			// This crowdloan would end in lease period 2, but is bidding for some slot that starts
1258			// in lease period 1.
1259			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			// Set up a crowdloan
1273			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 4, 9, None));
1274
1275			// No contributions yet
1276			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
1277
1278			// User 1 contributes to their own crowdloan
1279			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None));
1280			// User 1 has spent some funds to do this, transfer fees **are** taken
1281			assert_eq!(Balances::free_balance(1), 950);
1282			// Contributions are stored in the trie
1283			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 49);
1284			// Contributions appear in free balance of crowdloan
1285			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(index)), 49);
1286			// Crowdloan is added to NewRaise
1287			assert_eq!(crowdloan::NewRaise::<Test>::get(), vec![para]);
1288
1289			let fund = crowdloan::Funds::<Test>::get(para).unwrap();
1290
1291			// Last contribution time recorded
1292			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			// Set up a crowdloan
1304			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			// No contributions yet
1315			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1).0, 0);
1316
1317			// Missing signature
1318			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			// Invalid signature
1330			assert_noop!(
1331				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, Some(invalid_signature)),
1332				Error::<Test>::InvalidSignature
1333			);
1334
1335			// Valid signature wrong parameter
1336			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			// Valid signature
1356			assert_ok!(Crowdloan::contribute(
1357				RuntimeOrigin::signed(1),
1358				para,
1359				49,
1360				Some(valid_signature.clone())
1361			));
1362
1363			// Reuse valid signature
1364			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			// New valid signature
1373			assert_ok!(Crowdloan::contribute(
1374				RuntimeOrigin::signed(1),
1375				para,
1376				10,
1377				Some(valid_signature_2)
1378			));
1379
1380			// Contributions appear in free balance of crowdloan
1381			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(index)), 59);
1382
1383			// Contribution amount is correct
1384			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			// Cannot contribute to non-existing fund
1395			assert_noop!(
1396				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None),
1397				Error::<Test>::InvalidParaId
1398			);
1399			// Cannot contribute below minimum contribution
1400			assert_noop!(
1401				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 9, None),
1402				Error::<Test>::ContributionTooSmall
1403			);
1404
1405			// Set up a crowdloan
1406			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			// Cannot contribute past the limit
1410			assert_noop!(
1411				Crowdloan::contribute(RuntimeOrigin::signed(2), para, 900, None),
1412				Error::<Test>::CapExceeded
1413			);
1414
1415			// Move past end date
1416			System::run_to_block::<AllPalletsWithSystem>(10);
1417
1418			// Cannot contribute to ended fund
1419			assert_noop!(
1420				Crowdloan::contribute(RuntimeOrigin::signed(1), para, 49, None),
1421				Error::<Test>::ContributionPeriodOver
1422			);
1423
1424			// If a crowdloan has already won, it should not allow contributions.
1425			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			// Emulate a win by leasing out and putting a deposit. Slots pallet would normally do
1429			// this.
1430			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			// Move past lease period 1, should not be allowed to have further contributions with a
1438			// crowdloan that has starting period 1.
1439			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			// Set up a crowdloan
1463			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			// Can def contribute when auction is running.
1475			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			// Can't contribute when auction is in the VRF delay period.
1480			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			// Its fine to contribute when no auction is running.
1488			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			// Set up a crowdloan
1504			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			// Fund crowdloan
1516			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			// Endings count incremented
1536			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			// Set up a crowdloan
1547			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			// para has no reserved funds, indicating it did not win the auction.
1554			assert_eq!(Balances::reserved_balance(&account_id), 0);
1555			// but there's still the funds in its balance.
1556			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			// Set up a crowdloan
1578			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			// user sends the crowdloan funds trying to make an accounting error
1585			assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), account_id, 10));
1586
1587			// overfunded now
1588			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			// Some funds are left over
1595			assert_eq!(Balances::free_balance(&account_id), 10);
1596			// Remaining funds will be burned
1597			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			// Set up a crowdloan ending on 9
1611			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1612			// Make some contributions
1613			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			// Can't refund before the crowdloan it has ended
1620			assert_noop!(
1621				Crowdloan::refund(RuntimeOrigin::signed(1337), para),
1622				Error::<Test>::FundNotEnded,
1623			);
1624
1625			// Move to the end of the crowdloan
1626			System::run_to_block::<AllPalletsWithSystem>(10);
1627			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para));
1628
1629			// Funds are returned
1630			assert_eq!(Balances::free_balance(account_id), 0);
1631			// 1 deposit for the crowdloan which hasn't dissolved yet.
1632			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			// Set up a crowdloan ending on 9
1646			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 100000, 1, 1, 9, None));
1647			// Make more contributions than our limit
1648			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			// Move to the end of the crowdloan
1661			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			// Funds still left over
1669			assert!(!Balances::free_balance(account_id).is_zero());
1670
1671			// Call again
1672			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para));
1673			assert_eq!(last_event(), super::Event::<Test>::AllRefunded { para_id: para }.into());
1674
1675			// Funds are returned
1676			assert_eq!(Balances::free_balance(account_id), 0);
1677			// 1 deposit for the crowdloan which hasn't dissolved yet.
1678			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			// Set up a crowdloan
1691			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			// All funds are refunded
1697			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1698
1699			// Now that `fund.raised` is zero, it can be dissolved.
1700			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	// Regression test to check that a pot account with just one provider can be dissolved.
1709	#[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			// Set up a crowdloan
1716			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			// We test the historic case where crowdloan accounts only have one provider:
1723			{
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			// All funds are refunded
1731			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1732
1733			// Now that `fund.raised` is zero, it can be dissolved.
1734			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			// Set up a crowdloan
1750			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			// Can't dissolve before it ends
1755			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			// Can't dissolve when it won.
1763			assert_noop!(
1764				Crowdloan::dissolve(RuntimeOrigin::signed(1), para),
1765				Error::<Test>::NotReadyToDissolve
1766			);
1767			set_winner(para, 1, false);
1768
1769			// Can't dissolve while it still has user funds
1770			assert_noop!(
1771				Crowdloan::dissolve(RuntimeOrigin::signed(1), para),
1772				Error::<Test>::NotReadyToDissolve
1773			);
1774
1775			// All funds are refunded
1776			assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para));
1777
1778			// Now that `fund.raised` is zero, it can be dissolved.
1779			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			// Set up a crowdloan
1797			assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None));
1798
1799			// Fund crowdloans.
1800			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None));
1801			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None));
1802			// simulate the reserving of para's funds. this actually happens in the Slots pallet.
1803			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			// simulate the unreserving of para's funds, now that the lease expired. this actually
1813			// happens in the Slots pallet.
1814			Balances::unreserve(&account_id, 150);
1815
1816			// para has no reserved funds, indicating it did ot win the auction.
1817			assert_eq!(Balances::reserved_balance(&account_id), 0);
1818			// but there's still the funds in its balance.
1819			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			// Set up crowdloans
1840			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			// Different contributions
1843			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None));
1844			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para_2, 50, None));
1845			// Original state
1846			assert_eq!(Funds::<Test>::get(para_1).unwrap().raised, 100);
1847			assert_eq!(Funds::<Test>::get(para_2).unwrap().raised, 50);
1848			// Swap
1849			Crowdloan::on_swap(para_1, para_2);
1850			// Final state
1851			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			// Cannot create a fund again
1863			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			// Some things stay the same
1883			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			// Some things change
1888			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			// Cant add a memo before you have contributed.
1901			assert_noop!(
1902				Crowdloan::add_memo(RuntimeOrigin::signed(1), para_1, b"hello, world".to_vec()),
1903				Error::<Test>::NoContributions,
1904			);
1905			// Make a contribution. Initially no memo.
1906			assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(1), para_1, 100, None));
1907			assert_eq!(Crowdloan::contribution_get(0u32, &1), (100, vec![]));
1908			// Can't place a memo that is too large.
1909			assert_noop!(
1910				Crowdloan::add_memo(RuntimeOrigin::signed(1), para_1, vec![123; 123]),
1911				Error::<Test>::MemoTooLarge,
1912			);
1913			// Adding a memo to an existing contribution works
1914			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			// Can contribute again and data persists
1921			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			// Should fail when no contributions.
1934			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		// compare to the last event record
1965		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		// Set to the very beginning of lease period index 0.
1973		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		// Assume ed25519 is most complex signature format
1985		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		// Contribute has two arms: PreEnding and Ending, but both are equal complexity.
2076		#[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			// NewRaise is appended to, so we don't need to fill it up for worst case scenario.
2094			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		// Worst case: Refund removes `RemoveKeysLimit` keys, and is fully refunded.
2127		#[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			// Dissolve will remove at most `RemoveKeysLimit` at once.
2134			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			// Doesn't matter what we edit to, so use the same values.
2196			#[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		// Worst case scenario: N funds are all in the `NewRaise` list, we are
2239		// in the beginning of the ending period, and each fund outbids the next
2240		// over the same periods.
2241		// We test the complexity over different number of new raise
2242		#[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}