crowdloan.rs 51.5 KB
Newer Older
Shawn Tabrizi's avatar
Shawn Tabrizi committed
1
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

Shawn Tabrizi's avatar
Shawn Tabrizi committed
17
//! # Parachain Crowdloaning module
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//!
//! The point of this module is to allow parachain projects to offer the ability to help fund a
//! deposit for the parachain. When the parachain is retired, the funds may be returned.
//!
//! Contributing funds is permissionless. Each fund has a child-trie which stores all
//! contributors account IDs together with the amount they contributed; the root of this can then be
//! used by the parachain to allow contributors to prove that they made some particular contribution
//! to the project (e.g. to be rewarded through some token or badge). The trie is retained for later
//! (efficient) redistribution back to the contributors.
//!
//! Contributions must be of at least `MinContribution` (to account for the resources taken in
//! tracking contributions), and may never tally greater than the fund's `cap`, set and fixed at the
//! time of creation. The `create` call may be used to create a new fund. In order to do this, then
//! a deposit must be paid of the amount `SubmissionDeposit`. Substantial resources are taken on
//! the main trie in tracking a fund and this accounts for that.
//!
//! Funds may be set up during an auction period; their closing time is fixed at creation (as a
//! block number) and if the fund is not successful by the closing time, then it will become *retired*.
//! Funds may span multiple auctions, and even auctions that sell differing periods. However, for a
//! fund to be active in bidding for an auction, it *must* have had *at least one bid* since the end
//! of the last auction. Until a fund takes a further bid following the end of an auction, then it
//! will be inactive.
//!
//! Contributors may get a refund of their contributions from retired funds. After a period (`RetirementPeriod`)
//! the fund may be dissolved entirely. At this point any non-refunded contributions are considered
//! `orphaned` and are disposed of through the `OrphanedFunds` handler (which may e.g. place them
//! into the treasury).
//!
//! Funds may accept contributions at any point before their success or retirement. When a parachain
//! slot auction enters its ending period, then parachains will each place a bid; the bid will be
//! raised once per block if the parachain had additional funds contributed since the last bid.
//!
//! Funds may set their deploy data (the code hash and head data of their parachain) at any point.
//! It may only be done once and once set cannot be changed. Good procedure would be to set them
//! ahead of receiving any contributions in order that contributors may verify that their parachain
//! contains all expected functionality. However, this is not enforced and deploy data may happen
//! at any point, even after a slot has been successfully won or, indeed, never.
//!
//! Funds that are successful winners of a slot may have their slot claimed through the `onboard`
//! call. This may only be done once and must be after the deploy data has been fixed. Successful
//! funds remain tracked (in the `Funds` storage item and the associated child trie) as long as
//! the parachain remains active. Once it does not, it is up to the parachain to ensure that the
//! funds are returned to this module's fund sub-account in order that they be redistributed back to
//! contributors. *Retirement* may be initiated by any account (using the `begin_retirement` call)
//! once the parachain is removed from the its slot.
//!
//! @WARNING: For funds to be returned, it is imperative that this module's account is provided as
//! the offboarding account for the slot. In the case that a parachain supplemented these funds in
//! order to win a later auction, then it is the parachain's duty to ensure that the right amount of
//! funds ultimately end up in module's fund sub-account.

69
use frame_support::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
70
71
	decl_module, decl_storage, decl_event, decl_error, ensure,
	storage::child,
72
	traits::{
73
		Currency, ReservableCurrency, Get, OnUnbalanced, ExistenceRequirement::AllowDeath
74
	},
75
	pallet_prelude::{Weight, DispatchResultWithPostInfo},
76
};
77
use frame_system::ensure_signed;
78
use sp_runtime::{
79
	ModuleId, DispatchResult, RuntimeDebug, MultiSignature, MultiSigner,
80
81
82
	traits::{
		AccountIdConversion, Hash, Saturating, Zero, CheckedAdd, Bounded, Verify, IdentifyAccount,
	},
83
};
84
use crate::traits::{Registrar, Auctioneer};
85
use parity_scale_codec::{Encode, Decode};
86
use sp_std::vec::Vec;
87
88
89
90
91
use primitives::v1::Id as ParaId;

type CurrencyOf<T> = <<T as Config>::Auctioneer as Auctioneer>::Currency;
type LeasePeriodOf<T> = <<T as Config>::Auctioneer as Auctioneer>::LeasePeriod;
type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
92

93
#[allow(dead_code)]
94
95
96
97
98
99
100
101
102
103
104
type NegativeImbalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;

type TrieIndex = u32;

pub trait WeightInfo {
	fn create() -> Weight;
	fn contribute() -> Weight;
	fn withdraw() -> Weight;
	fn dissolve(k: u32, ) -> Weight;
	fn on_initialize(n: u32, ) -> Weight;
}
105

106
107
108
109
110
111
112
113
114
115
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
	fn create() -> Weight { 0 }
	fn contribute() -> Weight { 0 }
	fn withdraw() -> Weight { 0 }
	fn dissolve(_k: u32, ) -> Weight { 0 }
	fn on_initialize(_n: u32, ) -> Weight { 0 }
}

pub trait Config: frame_system::Config {
116
	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
117

Shawn Tabrizi's avatar
Shawn Tabrizi committed
118
	/// ModuleID for the crowdloan module. An appropriate value could be ```ModuleId(*b"py/cfund")```
119
120
	type ModuleId: Get<ModuleId>;

121
	/// The amount to be held on deposit by the depositor of a crowdloan.
122
123
	type SubmissionDeposit: Get<BalanceOf<Self>>;

Shawn Tabrizi's avatar
Shawn Tabrizi committed
124
	/// The minimum amount that may be contributed into a crowdloan. Should almost certainly be at
125
126
127
	/// least ExistentialDeposit.
	type MinContribution: Get<BalanceOf<Self>>;

Shawn Tabrizi's avatar
Shawn Tabrizi committed
128
	/// The period of time (in blocks) after an unsuccessful crowdloan ending when
129
130
131
132
133
	/// contributors are able to withdraw their funds. After this period, their funds are lost.
	type RetirementPeriod: Get<Self::BlockNumber>;

	/// What to do with funds that were not withdrawn.
	type OrphanedFunds: OnUnbalanced<NegativeImbalanceOf<Self>>;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
134
135
136

	/// Max number of storage keys to remove per extrinsic call.
	type RemoveKeysLimit: Get<u32>;
137

138
139
140
141
142
143
144
145
146
147
	/// The parachain registrar type. We jus use this to ensure that only the manager of a para is able to
	/// start a crowdloan for its slot.
	type Registrar: Registrar<AccountId=Self::AccountId>;

	/// The type representing the auctioning system.
	type Auctioneer: Auctioneer<
		AccountId=Self::AccountId,
		BlockNumber=Self::BlockNumber,
		LeasePeriod=Self::BlockNumber,
	>;
148

149
150
151
152
153
	/// Weight Information for the Extrinsics in the Pallet
	type WeightInfo: WeightInfo;
}

#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
154
155
pub enum LastContribution<BlockNumber> {
	Never,
156
	PreEnding(u32),
157
158
159
	Ending(BlockNumber),
}

160
161
162
/// Information on a funding effort for a pre-existing parachain. We assume that the parachain ID
/// is known as it's used for the key of the storage item for which this is the value (`Funds`).
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
163
#[codec(dumb_trait_bound)]
164
165
166
167
pub struct FundInfo<AccountId, Balance, BlockNumber, LeasePeriod> {
	/// True if the fund is being retired. This can only be set once and only when the current
	/// lease period is greater than the `last_slot`.
	retiring: bool,
168
	/// The owning account who placed the deposit.
169
	depositor: AccountId,
170
171
	/// An optional verifier. If exists, contributions must be signed by verifier.
	verifier: Option<MultiSigner>,
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
	/// The amount of deposit placed.
	deposit: Balance,
	/// The total amount raised.
	raised: Balance,
	/// Block number after which the funding must have succeeded. If not successful at this number
	/// then everyone may withdraw their funds.
	end: BlockNumber,
	/// A hard-cap on the amount that may be contributed.
	cap: Balance,
	/// The most recent block that this had a contribution. Determines if we make a bid or not.
	/// If this is `Never`, this fund has never received a contribution.
	/// If this is `PreEnding(n)`, this fund received a contribution sometime in auction
	/// number `n` before the ending period.
	/// If this is `Ending(n)`, this fund received a contribution during the current ending period,
	/// where `n` is how far into the ending period the contribution was made.
	last_contribution: LastContribution<BlockNumber>,
	/// First slot in range to bid on; it's actually a LeasePeriod, but that's the same type as
	/// BlockNumber.
190
	first_slot: LeasePeriod,
191
192
	/// Last slot in range to bid on; it's actually a LeasePeriod, but that's the same type as
	/// BlockNumber.
193
194
195
	last_slot: LeasePeriod,
	/// Index used for the child trie of this fund
	trie_index: TrieIndex,
196
197
198
}

decl_storage! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
199
	trait Store for Module<T: Config> as Crowdloan {
200
		/// Info on all of the funds.
201
		Funds get(fn funds):
202
203
			map hasher(twox_64_concat) ParaId
			=> Option<FundInfo<T::AccountId, BalanceOf<T>, T::BlockNumber, LeasePeriodOf<T>>>;
204
205
206

		/// The funds that have had additional contributions during the last block. This is used
		/// in order to determine which funds should submit new or updated bids.
207
		NewRaise get(fn new_raise): Vec<ParaId>;
208
209

		/// The number of auctions that have entered into their ending period so far.
210
211
212
213
		EndingsCount get(fn endings_count): u32;

		/// Tracker for the next available trie index
		NextTrieIndex get(fn next_trie_index): u32;
214
215
216
217
218
	}
}

decl_event! {
	pub enum Event<T> where
219
		<T as frame_system::Config>::AccountId,
220
221
		Balance = BalanceOf<T>,
	{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
222
		/// Create a new crowdloaning campaign. [fund_index]
223
		Created(ParaId),
224
		/// Contributed to a crowd sale. [who, fund_index, amount]
225
		Contributed(AccountId, ParaId, Balance),
226
		/// Withdrew full balance of a contributor. [who, fund_index, amount]
227
		Withdrew(AccountId, ParaId, Balance),
228
		/// Fund is placed into retirement. [fund_index]
229
		Retiring(ParaId),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
230
231
		/// Fund is partially dissolved, i.e. there are some left over child
		/// keys that still need to be killed. [fund_index]
232
		PartiallyDissolved(ParaId),
233
		/// Fund is dissolved. [fund_index]
234
235
236
237
238
		Dissolved(ParaId),
		/// The deploy data of the funded parachain is set. [fund_index]
		DeployDataFixed(ParaId),
		/// On-boarding process for a winning parachain fund is completed. [find_index, parachain_id]
		Onboarded(ParaId, ParaId),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
239
		/// The result of trying to submit a new bid to the Slots pallet.
240
		HandleBidResult(ParaId, DispatchResult),
241
242
243
	}
}

244
decl_error! {
245
	pub enum Error for Module<T: Config> {
246
247
		/// The first slot needs to at least be less than 3 `max_value`.
		FirstSlotTooFarInFuture,
248
249
250
251
252
253
254
255
256
257
258
		/// Last slot must be greater than first slot.
		LastSlotBeforeFirstSlot,
		/// The last slot cannot be more then 3 slots after the first slot.
		LastSlotTooFarInFuture,
		/// The campaign ends before the current block number. The end must be in the future.
		CannotEndInPast,
		/// There was an overflow.
		Overflow,
		/// The contribution was below the minimum, `MinContribution`.
		ContributionTooSmall,
		/// Invalid fund index.
259
		InvalidParaId,
260
261
262
263
264
265
		/// Contributions exceed maximum amount.
		CapExceeded,
		/// The contribution period has already ended.
		ContributionPeriodOver,
		/// The origin of this call is invalid.
		InvalidOrigin,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
266
		/// This crowdloan does not correspond to a parachain.
267
		NotParachain,
268
269
270
271
		/// This parachain lease is still active and retirement cannot yet begin.
		LeaseActive,
		/// This parachain's bid or lease is still active and withdraw cannot yet begin.
		BidOrLeaseActive,
272
273
274
275
		/// Funds have not yet been returned.
		FundsNotReturned,
		/// Fund has not yet retired.
		FundNotRetired,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
276
		/// The crowdloan has not yet ended.
277
		FundNotEnded,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
278
		/// There are no contributions stored in this crowdloan.
279
		NoContributions,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
280
		/// This crowdloan has an active parachain and cannot be dissolved.
281
		HasActiveParachain,
282
283
		/// The crowdloan is not ready to dissolve. Potentially still has a slot or in retirement period.
		NotReadyToDissolve,
284
285
		/// Invalid signature.
		InvalidSignature,
286
287
288
	}
}

289
decl_module! {
290
	pub struct Module<T: Config> for enum Call where origin: <T as frame_system::Config>::Origin {
291
292
		type Error = Error<T>;

293
		const ModuleId: ModuleId = T::ModuleId::get();
294
295
296
		const MinContribution: BalanceOf<T> = T::MinContribution::get();
		const RemoveKeysLimit: u32 = T::RemoveKeysLimit::get();
		const RetirementPeriod: T::BlockNumber = T::RetirementPeriod::get();
297

298
		fn deposit_event() = default;
299

Shawn Tabrizi's avatar
Shawn Tabrizi committed
300
		/// Create a new crowdloaning campaign for a parachain slot deposit for the current auction.
301
302
303
		#[weight = T::WeightInfo::create()]
		pub fn create(origin,
			#[compact] index: ParaId,
304
			#[compact] cap: BalanceOf<T>,
305
306
			#[compact] first_slot: LeasePeriodOf<T>,
			#[compact] last_slot: LeasePeriodOf<T>,
307
308
			#[compact] end: T::BlockNumber,
			verifier: Option<MultiSigner>,
309
		) {
310
			let depositor = ensure_signed(origin)?;
311

312
313
314
			ensure!(first_slot <= last_slot, Error::<T>::LastSlotBeforeFirstSlot);
			let last_slot_limit = first_slot.checked_add(&3u32.into()).ok_or(Error::<T>::FirstSlotTooFarInFuture)?;
			ensure!(last_slot <= last_slot_limit, Error::<T>::LastSlotTooFarInFuture);
315
			ensure!(end > <frame_system::Pallet<T>>::block_number(), Error::<T>::CannotEndInPast);
316

317
318
319
320
321
322
323
324
			// There should not be an existing fund.
			ensure!(!Funds::<T>::contains_key(index), Error::<T>::FundNotEnded);

			let manager = T::Registrar::manager_of(index).ok_or(Error::<T>::InvalidParaId)?;
			ensure!(depositor == manager, Error::<T>::InvalidOrigin);

			let trie_index = Self::next_trie_index();
			let new_trie_index = trie_index.checked_add(1).ok_or(Error::<T>::Overflow)?;
325

Shawn Tabrizi's avatar
Shawn Tabrizi committed
326
			let deposit = T::SubmissionDeposit::get();
327

328
329
330
331
332
			CurrencyOf::<T>::reserve(&depositor, deposit)?;

			Funds::<T>::insert(index, FundInfo {
				retiring: false,
				depositor,
333
				verifier,
334
335
336
337
338
339
340
				deposit,
				raised: Zero::zero(),
				end,
				cap,
				last_contribution: LastContribution::Never,
				first_slot,
				last_slot,
341
				trie_index,
342
343
			});

344
345
			NextTrieIndex::put(new_trie_index);

346
347
			Self::deposit_event(RawEvent::Created(index));
		}
348

349
350
		/// Contribute to a crowd sale. This will transfer some balance over to fund a parachain
		/// slot. It will be withdrawable in two instances: the parachain becomes retired; or the
Gavin Wood's avatar
Gavin Wood committed
351
		/// slot is unable to be purchased and the timeout expires.
352
353
354
		#[weight = T::WeightInfo::contribute()]
		pub fn contribute(origin,
			#[compact] index: ParaId,
355
356
357
			#[compact] value: BalanceOf<T>,
			signature: Option<MultiSignature>
		) {
358
359
			let who = ensure_signed(origin)?;

360
			ensure!(value >= T::MinContribution::get(), Error::<T>::ContributionTooSmall);
361
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
362
363
			fund.raised  = fund.raised.checked_add(&value).ok_or(Error::<T>::Overflow)?;
			ensure!(fund.raised <= fund.cap, Error::<T>::CapExceeded);
364

Shawn Tabrizi's avatar
Shawn Tabrizi committed
365
			// Make sure crowdloan has not ended
366
			let now = <frame_system::Pallet<T>>::block_number();
367
			ensure!(now < fund.end, Error::<T>::ContributionPeriodOver);
368

369
			let old_balance = Self::contribution_get(fund.trie_index, &who);
370
371
372
373
374
375
376
377

			if let Some(ref verifier) = fund.verifier {
				let signature = signature.ok_or(Error::<T>::InvalidSignature)?;
				let payload = (index, &who, old_balance, value);
				let valid = payload.using_encoded(|encoded| signature.verify(encoded, &verifier.clone().into_account()));
				ensure!(valid, Error::<T>::InvalidSignature);
			}

378
			CurrencyOf::<T>::transfer(&who, &Self::fund_account_id(index), value, AllowDeath)?;
379

380
			let balance = old_balance.saturating_add(value);
381
			Self::contribution_put(fund.trie_index, &who, &balance);
382

383
			if T::Auctioneer::is_ending(now).is_some() {
384
385
386
387
388
389
				match fund.last_contribution {
					// In ending period; must ensure that we are in NewRaise.
					LastContribution::Ending(n) if n == now => {
						// do nothing - already in NewRaise
					}
					_ => {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
390
						NewRaise::append(index);
391
392
393
394
395
396
397
398
399
400
401
402
403
404
						fund.last_contribution = LastContribution::Ending(now);
					}
				}
			} else {
				let endings_count = Self::endings_count();
				match fund.last_contribution {
					LastContribution::PreEnding(a) if a == endings_count => {
						// Not in ending period and no auctions have ended ending since our
						// previous bid which was also not in an ending period.
						// `NewRaise` will contain our ID still: Do nothing.
					}
					_ => {
						// Not in ending period; but an auction has been ending since our previous
						// bid, or we never had one to begin with. Add bid.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
405
						NewRaise::append(index);
406
407
408
409
410
						fund.last_contribution = LastContribution::PreEnding(endings_count);
					}
				}
			}

411
			Funds::<T>::insert(index, &fund);
412
413
414
415

			Self::deposit_event(RawEvent::Contributed(who, index, value));
		}

416
		/// Withdraw full balance of a contributor.
417
		///
418
		/// Origin must be signed.
419
		///
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
		/// The fund must be either in, or ready for, retirement. For a fund to be *in* retirement, then the retirement
		/// flag must be set. For a fund to be ready for retirement, then:
		/// - it must not already be in retirement;
		/// - the amount of raised funds must be bigger than the _free_ balance of the account;
		/// - and either:
		///   - the block number must be at least `end`; or
		///   - the current lease period must be greater than the fund's `last_slot`.
		///
		/// In this case, the fund's retirement flag is set and its `end` is reset to the current block
		/// number.
		///
		/// - `who`: The account whose contribution should be withdrawn.
		/// - `index`: The parachain to whose crowdloan the contribution was made.
		#[weight = T::WeightInfo::withdraw()]
		pub fn withdraw(origin, who: T::AccountId, #[compact] index: ParaId) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
435
			ensure_signed(origin)?;
436

437
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
438
439

			// `fund.end` can represent the end of a failed crowdsale or the beginning of retirement
440
			let now = frame_system::Pallet::<T>::block_number();
441
442
443
444
445
446
			let current_lease_period = T::Auctioneer::lease_period_index();
			ensure!(now >= fund.end || current_lease_period > fund.last_slot, Error::<T>::FundNotEnded);

			let fund_account = Self::fund_account_id(index);
			// free balance must equal amount raised, otherwise a bid or lease must be active.
			ensure!(CurrencyOf::<T>::free_balance(&fund_account) == fund.raised, Error::<T>::BidOrLeaseActive);
447

448
			let balance = Self::contribution_get(fund.trie_index, &who);
449
			ensure!(balance > Zero::zero(), Error::<T>::NoContributions);
450
451

			// Avoid using transfer to ensure we don't pay any fees.
452
			CurrencyOf::<T>::transfer(&fund_account, &who, balance, AllowDeath)?;
453

454
			Self::contribution_kill(fund.trie_index, &who);
455
			fund.raised = fund.raised.saturating_sub(balance);
456
457
458
459
			if !fund.retiring {
				fund.retiring = true;
				fund.end = now;
			}
460

461
			Funds::<T>::insert(index, &fund);
462
463
464

			Self::deposit_event(RawEvent::Withdrew(who, index, balance));
		}
465

466
467
468
469
470
		/// Remove a fund after the retirement period has ended.
		///
		/// This places any deposits that were not withdrawn into the treasury.
		#[weight = T::WeightInfo::dissolve(T::RemoveKeysLimit::get())]
		pub fn dissolve(origin, #[compact] index: ParaId) -> DispatchResultWithPostInfo {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
471
			ensure_signed(origin)?;
472

473
			let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
474
			let now = frame_system::Pallet::<T>::block_number();
475
476
			let dissolution = fund.end.saturating_add(T::RetirementPeriod::get());
			ensure!((fund.retiring && now >= dissolution) || fund.raised.is_zero(), Error::<T>::NotReadyToDissolve);
477

Shawn Tabrizi's avatar
Shawn Tabrizi committed
478
			// Try killing the crowdloan child trie
479
480
481
			match Self::crowdloan_kill(fund.trie_index) {
				child::KillChildStorageResult::AllRemoved(num_removed) => {
					CurrencyOf::<T>::unreserve(&fund.depositor, fund.deposit);
482

Shawn Tabrizi's avatar
Shawn Tabrizi committed
483
					// Remove all other balance from the account into orphaned funds.
484
485
					let account = Self::fund_account_id(index);
					let (imbalance, _) = CurrencyOf::<T>::slash(&account, BalanceOf::<T>::max_value());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
486
					T::OrphanedFunds::on_unbalanced(imbalance);
487

488
					Funds::<T>::remove(index);
489

Shawn Tabrizi's avatar
Shawn Tabrizi committed
490
					Self::deposit_event(RawEvent::Dissolved(index));
491
492

					Ok(Some(T::WeightInfo::dissolve(num_removed)).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
493
				},
494
				child::KillChildStorageResult::SomeRemaining(num_removed) => {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
495
					Self::deposit_event(RawEvent::PartiallyDissolved(index));
496
					Ok(Some(T::WeightInfo::dissolve(num_removed)).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
497
498
				}
			}
499
		}
500

Shawn Tabrizi's avatar
Shawn Tabrizi committed
501
		fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight {
502
			if let Some(n) = T::Auctioneer::is_ending(n) {
503
504
505
506
				if n.is_zero() {
					// first block of ending period.
					EndingsCount::mutate(|c| *c += 1);
				}
507
508
509
				let new_raise = NewRaise::take();
				let new_raise_len = new_raise.len() as u32;
				for (fund, para_id) in new_raise.into_iter().filter_map(|i| Self::funds(i).map(|f| (f, i))) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
510
511
					// Care needs to be taken by the crowdloan creator that this function will succeed given
					// the crowdloaning configuration. We do some checks ahead of time in crowdloan `create`.
512
513
514
					let result = T::Auctioneer::place_bid(
						Self::fund_account_id(para_id),
						para_id,
515
516
517
518
						fund.first_slot,
						fund.last_slot,
						fund.raised,
					);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
519

520
					Self::deposit_event(RawEvent::HandleBidResult(para_id, result));
521
				}
522
523
524
				T::WeightInfo::on_initialize(new_raise_len)
			} else {
				T::DbWeight::get().reads(1)
525
526
527
528
529
			}
		}
	}
}

530
impl<T: Config> Module<T> {
531
532
533
534
	/// The account ID of the fund pot.
	///
	/// This actually does computation. If you need to keep using it, then make sure you cache the
	/// value and only call this once.
535
	pub fn fund_account_id(index: ParaId) -> T::AccountId {
536
		T::ModuleId::get().into_sub_account(index)
537
538
	}

539
	pub fn id_from_index(index: TrieIndex) -> child::ChildInfo {
540
		let mut buf = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
541
		buf.extend_from_slice(b"crowdloan");
542
		buf.extend_from_slice(&index.encode()[..]);
543
		child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
544
545
	}

546
	pub fn contribution_put(index: TrieIndex, who: &T::AccountId, balance: &BalanceOf<T>) {
547
		who.using_encoded(|b| child::put(&Self::id_from_index(index), b, balance));
548
549
	}

550
	pub fn contribution_get(index: TrieIndex, who: &T::AccountId) -> BalanceOf<T> {
551
		who.using_encoded(|b| child::get_or_default::<BalanceOf<T>>(
552
			&Self::id_from_index(index),
553
554
			b,
		))
555
556
	}

557
	pub fn contribution_kill(index: TrieIndex, who: &T::AccountId) {
558
		who.using_encoded(|b| child::kill(&Self::id_from_index(index), b));
559
560
	}

561
	pub fn crowdloan_kill(index: TrieIndex) -> child::KillChildStorageResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
562
		child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()))
563
564
565
	}
}

566
567
568
569
570
571
572
573
574
575
impl<T: Config> crate::traits::OnSwap for Module<T> {
	fn on_swap(one: ParaId, other: ParaId) {
		Funds::<T>::mutate(one, |x|
			Funds::<T>::mutate(other, |y|
				sp_std::mem::swap(x, y)
			)
		)
	}
}

576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
#[cfg(any(feature = "runtime-benchmarks", test))]
mod crypto {
	use sp_core::ed25519;
	use sp_io::crypto::{ed25519_sign, ed25519_generate};
	use sp_std::{
		vec::Vec,
		convert::TryFrom,
	};
	use sp_runtime::{MultiSigner, MultiSignature};

	pub fn create_ed25519_pubkey(seed: Vec<u8>) -> MultiSigner {
		ed25519_generate(0.into(), Some(seed)).into()
	}

	pub fn create_ed25519_signature(payload: &[u8], pubkey: MultiSigner) -> MultiSignature {
		let edpubkey = ed25519::Public::try_from(pubkey).unwrap();
		let edsig = ed25519_sign(0.into(), &edpubkey, payload).unwrap();
		edsig.into()
	}
}

597
598
599
600
#[cfg(test)]
mod tests {
	use super::*;

601
	use std::{cell::RefCell, sync::Arc};
602
	use frame_support::{
603
		assert_ok, assert_noop, parameter_types,
604
605
		traits::{OnInitialize, OnFinalize},
	};
606
	use sp_core::H256;
607
	use primitives::v1::Id as ParaId;
608
609
	// The testing primitives are very useful for avoiding having to work with signatures
	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried.
610
	use sp_runtime::{
611
612
613
614
615
616
		testing::Header, traits::{BlakeTwo256, IdentityLookup},
	};
	use crate::{
		mock::TestRegistrar,
		traits::OnSwap,
		crowdloan,
617
	};
618
	use sp_keystore::{KeystoreExt, testing::KeyStore};
619
620
621
622
623
624
625
626
627
628

	type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
	type Block = frame_system::mocking::MockBlock<Test>;

	frame_support::construct_runtime!(
		pub enum Test where
			Block = Block,
			NodeBlock = Block,
			UncheckedExtrinsic = UncheckedExtrinsic,
		{
629
630
631
			System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
			Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
			Crowdloan: crowdloan::{Pallet, Call, Storage, Event<T>},
Shawn Tabrizi's avatar
Shawn Tabrizi committed
632
		}
633
	);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
634

635
636
637
	parameter_types! {
		pub const BlockHashCount: u32 = 250;
	}
638

639
	impl frame_system::Config for Test {
640
		type BaseCallFilter = ();
641
642
643
		type BlockWeights = ();
		type BlockLength = ();
		type DbWeight = ();
644
		type Origin = Origin;
645
		type Call = Call;
646
647
648
649
650
651
652
		type Index = u64;
		type BlockNumber = u64;
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<Self::AccountId>;
		type Header = Header;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
653
		type Event = Event;
654
655
		type BlockHashCount = BlockHashCount;
		type Version = ();
656
		type PalletInfo = PalletInfo;
657
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
658
		type OnNewAccount = ();
659
		type OnKilledAccount = ();
660
		type SystemWeightInfo = ();
661
		type SS58Prefix = ();
662
	}
663

664
	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
665
		pub const ExistentialDeposit: u64 = 1;
666
	}
667

668
	impl pallet_balances::Config for Test {
669
		type Balance = u64;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
670
		type Event = Event;
671
		type DustRemoval = ();
672
		type ExistentialDeposit = ExistentialDeposit;
673
		type AccountStore = System;
674
		type MaxLocks = ();
675
		type WeightInfo = ();
676
677
	}

678
679
680
681
682
683
684
685
	#[derive(Copy, Clone, Eq, PartialEq, Debug)]
	struct BidPlaced {
		height: u64,
		bidder: u64,
		para: ParaId,
		first_slot: u64,
		last_slot: u64,
		amount: u64
686
687
	}
	thread_local! {
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
		static AUCTION: RefCell<Option<(u64, u64)>> = RefCell::new(None);
		static ENDING_PERIOD: RefCell<u64> = RefCell::new(5);
		static BIDS_PLACED: RefCell<Vec<BidPlaced>> = RefCell::new(Vec::new());
	}
	#[allow(unused)]
	fn set_ending_period(ending_period: u64) {
		ENDING_PERIOD.with(|p| *p.borrow_mut() = ending_period);
	}
	fn auction() -> Option<(u64, u64)> {
		AUCTION.with(|p| p.borrow().clone())
	}
	fn ending_period() -> u64 {
		ENDING_PERIOD.with(|p| p.borrow().clone())
	}
	fn bids() -> Vec<BidPlaced> {
		BIDS_PLACED.with(|p| p.borrow().clone())
704
705
	}

706
707
708
709
710
711
	pub struct TestAuctioneer;
	impl Auctioneer for TestAuctioneer {
		type AccountId = u64;
		type BlockNumber = u64;
		type LeasePeriod = u64;
		type Currency = Balances;
712

713
714
		fn new_auction(duration: u64, lease_period_index: u64) -> DispatchResult {
			assert!(lease_period_index >= Self::lease_period_index());
715

716
717
718
			let ending = System::block_number().saturating_add(duration);
			AUCTION.with(|p| *p.borrow_mut() = Some((lease_period_index, ending)));
			Ok(())
719
720
		}

721
722
723
724
725
726
727
728
729
		fn is_ending(now: u64) -> Option<u64> {
			if let Some((_, early_end)) = auction() {
				if let Some(after_early_end) = now.checked_sub(early_end) {
					if after_early_end < ending_period() {
						return Some(after_early_end)
					}
				}
			}
			None
730
731
		}

732
733
734
735
736
737
		fn place_bid(
			bidder: u64,
			para: ParaId,
			first_slot: u64,
			last_slot: u64,
			amount: u64
738
		) -> DispatchResult {
739
740
741
			let height = System::block_number();
			BIDS_PLACED.with(|p| p.borrow_mut().push(BidPlaced { height, bidder, para, first_slot, last_slot, amount }));
			Ok(())
742
		}
743

744
745
		fn lease_period_index() -> u64 {
			System::block_number() / 20
746
747
748
749
750
751
752
		}
	}

	parameter_types! {
		pub const SubmissionDeposit: u64 = 1;
		pub const MinContribution: u64 = 10;
		pub const RetirementPeriod: u64 = 5;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
753
754
		pub const CrowdloanModuleId: ModuleId = ModuleId(*b"py/cfund");
		pub const RemoveKeysLimit: u32 = 10;
755
	}
756

757
	impl Config for Test {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
758
		type Event = Event;
759
760
761
		type SubmissionDeposit = SubmissionDeposit;
		type MinContribution = MinContribution;
		type RetirementPeriod = RetirementPeriod;
762
		type OrphanedFunds = ();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
763
764
		type ModuleId = CrowdloanModuleId;
		type RemoveKeysLimit = RemoveKeysLimit;
765
766
767
		type Registrar = TestRegistrar<Test>;
		type Auctioneer = TestAuctioneer;
		type WeightInfo = crate::crowdloan::TestWeightInfo;
768
769
	}

770
	use pallet_balances::Error as BalancesError;
771
772
773

	// This function basically just builds a genesis storage key/value store according to
	// our desired mockup.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
774
	pub fn new_test_ext() -> sp_io::TestExternalities {
775
776
		let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
		pallet_balances::GenesisConfig::<Test>{
777
778
			balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)],
		}.assimilate_storage(&mut t).unwrap();
779
780
781
782
		let keystore = KeyStore::new();
		let mut t: sp_io::TestExternalities = t.into();
		t.register_extension(KeystoreExt(Arc::new(keystore)));
		t
783
784
	}

785
786
787
788
789
790
791
792
793
794
	fn new_para() -> ParaId {
		for i in 0.. {
			let para: ParaId = i.into();
			if TestRegistrar::<Test>::is_registered(para) { continue }
			assert_ok!(TestRegistrar::<Test>::register(1, para, Default::default(), Default::default()));
			return para;
		}
		unreachable!()
	}

795
796
	fn run_to_block(n: u64) {
		while System::block_number() < n {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
797
			Crowdloan::on_finalize(System::block_number());
798
799
800
801
802
			Balances::on_finalize(System::block_number());
			System::on_finalize(System::block_number());
			System::set_block_number(System::block_number() + 1);
			System::on_initialize(System::block_number());
			Balances::on_initialize(System::block_number());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
803
			Crowdloan::on_initialize(System::block_number());
804
805
806
807
808
		}
	}

	#[test]
	fn basic_setup_works() {
809
		new_test_ext().execute_with(|| {
810
			assert_eq!(System::block_number(), 0);
811
812
			assert_eq!(Crowdloan::funds(ParaId::from(0)), None);
			let empty: Vec<ParaId> = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
813
			assert_eq!(Crowdloan::new_raise(), empty);
814
			assert_eq!(Crowdloan::contribution_get(0u32, &1), 0);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
815
			assert_eq!(Crowdloan::endings_count(), 0);
816
817
818
819
820
821
822
823
824
825
826

			assert_ok!(TestAuctioneer::new_auction(5, 0));

			assert_eq!(bids(), vec![]);
			assert_ok!(TestAuctioneer::place_bid(1, 2.into(), 0, 3, 6));
			let b = BidPlaced { height: 0, bidder: 1, para: 2.into(), first_slot: 0, last_slot: 3, amount: 6 };
			assert_eq!(bids(), vec![b]);
			assert_eq!(TestAuctioneer::is_ending(4), None);
			assert_eq!(TestAuctioneer::is_ending(5), Some(0));
			assert_eq!(TestAuctioneer::is_ending(9), Some(4));
			assert_eq!(TestAuctioneer::is_ending(11), None);
827
828
829
830
831
		});
	}

	#[test]
	fn create_works() {
832
		new_test_ext().execute_with(|| {
833
			let para = new_para();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
834
			// Now try to create a crowdloan campaign
835
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));
836
837
			// This is what the initial `fund_info` should look like
			let fund_info = FundInfo {
838
839
				retiring: false,
				depositor: 1,
840
841
842
843
844
845
846
847
848
				verifier: None,
				deposit: 1,
				raised: 0,
				// 5 blocks length + 3 block ending period + 1 starting block
				end: 9,
				cap: 1000,
				last_contribution: LastContribution::Never,
				first_slot: 1,
				last_slot: 4,
849
				trie_index: 0,
850
			};
851
			assert_eq!(Crowdloan::funds(para), Some(fund_info));
852
853
			// User has deposit removed from their free balance
			assert_eq!(Balances::free_balance(1), 999);
854
855
			// Deposit is placed in reserved
			assert_eq!(Balances::reserved_balance(1), 1);
856
			// No new raise until first contribution
857
			let empty: Vec<ParaId> = Vec::new();
858
859
860
861
862
863
864
865
			assert_eq!(Crowdloan::new_raise(), empty);
		});
	}

	#[test]
	fn create_with_verifier_works() {
		new_test_ext().execute_with(|| {
			let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
866
			let para = new_para();
867
			// Now try to create a crowdloan campaign
868
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, Some(pubkey.clone())));
869
870
			// This is what the initial `fund_info` should look like
			let fund_info = FundInfo {
871
872
				retiring: false,
				depositor: 1,
873
				verifier: Some(pubkey),
874
875
876
877
878
879
880
881
				deposit: 1,
				raised: 0,
				// 5 blocks length + 3 block ending period + 1 starting block
				end: 9,
				cap: 1000,
				last_contribution: LastContribution::Never,
				first_slot: 1,
				last_slot: 4,
882
				trie_index: 0,
883
			};
884
			assert_eq!(Crowdloan::funds(ParaId::from(0)), Some(fund_info));
885
886
			// User has deposit removed from their free balance
			assert_eq!(Balances::free_balance(1), 999);
887
888
			// Deposit is placed in reserved
			assert_eq!(Balances::reserved_balance(1), 1);
889
			// No new raise until first contribution
890
			let empty: Vec<ParaId> = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
891
			assert_eq!(Crowdloan::new_raise(), empty);
892
893
894
895
896
		});
	}

	#[test]
	fn create_handles_basic_errors() {
897
		new_test_ext().execute_with(|| {
898
899
900
901
902
			// Now try to create a crowdloan campaign
			let para = new_para();

			let e = Error::<Test>::InvalidParaId;
			assert_noop!(Crowdloan::create(Origin::signed(1), 1.into(), 1000, 1, 4, 9, None), e);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
903
			// Cannot create a crowdloan with bad slots
904
905
906
907
			let e = Error::<Test>::LastSlotBeforeFirstSlot;
			assert_noop!(Crowdloan::create(Origin::signed(1), para, 1000, 4, 1, 9, None), e);
			let e = Error::<Test>::LastSlotTooFarInFuture;
			assert_noop!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 5, 9, None), e);
908

Shawn Tabrizi's avatar
Shawn Tabrizi committed
909
			// Cannot create a crowdloan without some deposit funds
910
911
912
			assert_ok!(TestRegistrar::<Test>::register(1337, ParaId::from(1234), Default::default(), Default::default()));
			let e = BalancesError::<Test, _>::InsufficientBalance;
			assert_noop!(Crowdloan::create(Origin::signed(1337), ParaId::from(1234), 1000, 1, 3, 9, None), e);
913
914
915
916
917
		});
	}

	#[test]
	fn contribute_works() {
918
		new_test_ext().execute_with(|| {
919
920
			let para = new_para();

Shawn Tabrizi's avatar
Shawn Tabrizi committed
921
			// Set up a crowdloan
922
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));
923
924

			// No contributions yet
925
			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1), 0);
926

Shawn Tabrizi's avatar
Shawn Tabrizi committed
927
			// User 1 contributes to their own crowdloan
928
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 49, None));
929
			// User 1 has spent some funds to do this, transfer fees **are** taken
Gavin Wood's avatar
Gavin Wood committed
930
			assert_eq!(Balances::free_balance(1), 950);
931
			// Contributions are stored in the trie
932
			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1), 49);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
933
			// Contributions appear in free balance of crowdloan
934
			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(para)), 49);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
935
			// Crowdloan is added to NewRaise
936
			assert_eq!(Crowdloan::new_raise(), vec![para]);
937

938
			let fund = Crowdloan::funds(para).unwrap();
939
940
941
942
943
944
945

			// Last contribution time recorded
			assert_eq!(fund.last_contribution, LastContribution::PreEnding(0));
			assert_eq!(fund.raised, 49);
		});
	}

946
947
948
	#[test]
	fn contribute_with_verifier_works() {
		new_test_ext().execute_with(|| {
949
			let para = new_para();
950
951
			let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec());
			// Set up a crowdloan
952
953
954
955
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, Some(pubkey.clone())));

			// No contributions yet
			assert_eq!(Crowdloan::contribution_get(u32::from(para), &1), 0);
956
957

			// Missing signature
958
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, None), Error::<Test>::InvalidSignature);
959
960
961
962
963
964

			let payload = (0u32, 1u64, 0u64, 49u64);
			let valid_signature = crypto::create_ed25519_signature(&payload.encode(), pubkey.clone());
			let invalid_signature = MultiSignature::default();

			// Invalid signature
965
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, Some(invalid_signature)), Error::<Test>::InvalidSignature);
966
967

			// Valid signature wrong parameter
968
969
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 50, Some(valid_signature.clone())), Error::<Test>::InvalidSignature);
			assert_noop!(Crowdloan::contribute(Origin::signed(2), para, 49, Some(valid_signature.clone())), Error::<Test>::InvalidSignature);
970
971

			// Valid signature
972
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 49, Some(valid_signature.clone())));
973
974

			// Reuse valid signature
975
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, Some(valid_signature)), Error::<Test>::InvalidSignature);
976
977
978
979
980

			let payload_2 = (0u32, 1u64, 49u64, 10u64);
			let valid_signature_2 = crypto::create_ed25519_signature(&payload_2.encode(), pubkey);

			// New valid signature
981
			assert_ok!(Crowdloan::contribute(Origin::signed(1), para, 10, Some(valid_signature_2)));
982
983

			// Contributions appear in free balance of crowdloan
984
			assert_eq!(Balances::free_balance(Crowdloan::fund_account_id(para)), 59);
985
986

			// Contribution amount is correct
987
			let fund = Crowdloan::funds(para).unwrap();
988
989
990
991
			assert_eq!(fund.raised, 59);
		});
	}

992
993
	#[test]
	fn contribute_handles_basic_errors() {
994
		new_test_ext().execute_with(|| {
995
996
			let para = new_para();

997
			// Cannot contribute to non-existing fund
998
			assert_noop!(Crowdloan::contribute(Origin::signed(1), para, 49, None), Error::<Test>::InvalidParaId);
999
			// Cannot contribute below minimum contribution
1000
			assert_noop!(<