crowdloan.rs 59.1 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, ensure_root};
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
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;
103
	fn edit() -> Weight;
104
	fn add_memo() -> Weight;
105
106
	fn on_initialize(n: u32, ) -> Weight;
}
107

108
109
110
111
112
113
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 }
114
	fn edit() -> Weight { 0 }
115
	fn add_memo() -> Weight { 0 }
116
117
118
119
	fn on_initialize(_n: u32, ) -> Weight { 0 }
}

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

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

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

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

Shawn Tabrizi's avatar
Shawn Tabrizi committed
132
	/// The period of time (in blocks) after an unsuccessful crowdloan ending when
133
134
135
136
137
	/// 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
138
139
140

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

142
143
144
145
146
147
148
149
150
151
	/// 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,
	>;
152

153
154
155
	/// The maximum length for the memo attached to a crowdloan contribution.
	type MaxMemoLength: Get<u8>;

156
157
158
159
160
	/// Weight Information for the Extrinsics in the Pallet
	type WeightInfo: WeightInfo;
}

#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
161
162
pub enum LastContribution<BlockNumber> {
	Never,
163
	PreEnding(u32),
164
165
166
	Ending(BlockNumber),
}

167
168
169
/// 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)]
170
#[codec(dumb_trait_bound)]
171
172
173
174
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,
175
	/// The owning account who placed the deposit.
176
	depositor: AccountId,
177
178
	/// An optional verifier. If exists, contributions must be signed by verifier.
	verifier: Option<MultiSigner>,
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
	/// 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.
197
	first_slot: LeasePeriod,
198
199
	/// Last slot in range to bid on; it's actually a LeasePeriod, but that's the same type as
	/// BlockNumber.
200
201
202
	last_slot: LeasePeriod,
	/// Index used for the child trie of this fund
	trie_index: TrieIndex,
203
204
205
}

decl_storage! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
206
	trait Store for Module<T: Config> as Crowdloan {
207
		/// Info on all of the funds.
208
		Funds get(fn funds):
209
210
			map hasher(twox_64_concat) ParaId
			=> Option<FundInfo<T::AccountId, BalanceOf<T>, T::BlockNumber, LeasePeriodOf<T>>>;
211
212
213

		/// 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.
214
		NewRaise get(fn new_raise): Vec<ParaId>;
215
216

		/// The number of auctions that have entered into their ending period so far.
217
218
219
220
		EndingsCount get(fn endings_count): u32;

		/// Tracker for the next available trie index
		NextTrieIndex get(fn next_trie_index): u32;
221
222
223
224
225
	}
}

decl_event! {
	pub enum Event<T> where
226
		<T as frame_system::Config>::AccountId,
227
228
		Balance = BalanceOf<T>,
	{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
229
		/// Create a new crowdloaning campaign. [fund_index]
230
		Created(ParaId),
231
		/// Contributed to a crowd sale. [who, fund_index, amount]
232
		Contributed(AccountId, ParaId, Balance),
233
		/// Withdrew full balance of a contributor. [who, fund_index, amount]
234
		Withdrew(AccountId, ParaId, Balance),
235
		/// Fund is placed into retirement. [fund_index]
236
		Retiring(ParaId),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
237
238
		/// Fund is partially dissolved, i.e. there are some left over child
		/// keys that still need to be killed. [fund_index]
239
		PartiallyDissolved(ParaId),
240
		/// Fund is dissolved. [fund_index]
241
242
243
244
245
		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
246
		/// The result of trying to submit a new bid to the Slots pallet.
247
		HandleBidResult(ParaId, DispatchResult),
248
249
		/// The configuration to a crowdloan has been edited. [fund_index]
		Edited(ParaId),
250
251
		/// A memo has been updated. [who, fund_index, memo]
		MemoUpdated(AccountId, ParaId, Vec<u8>),
252
253
254
	}
}

255
decl_error! {
256
	pub enum Error for Module<T: Config> {
257
258
		/// The first slot needs to at least be less than 3 `max_value`.
		FirstSlotTooFarInFuture,
259
260
261
262
263
264
265
266
267
268
269
		/// 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.
270
		InvalidParaId,
271
272
273
274
275
276
		/// 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
277
		/// This crowdloan does not correspond to a parachain.
278
		NotParachain,
279
280
281
282
		/// 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,
283
284
285
286
		/// Funds have not yet been returned.
		FundsNotReturned,
		/// Fund has not yet retired.
		FundNotRetired,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
287
		/// The crowdloan has not yet ended.
288
		FundNotEnded,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
289
		/// There are no contributions stored in this crowdloan.
290
		NoContributions,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
291
		/// This crowdloan has an active parachain and cannot be dissolved.
292
		HasActiveParachain,
293
294
		/// The crowdloan is not ready to dissolve. Potentially still has a slot or in retirement period.
		NotReadyToDissolve,
295
296
		/// Invalid signature.
		InvalidSignature,
297
298
		/// The provided memo is too large.
		MemoTooLarge,
299
300
301
	}
}

302
decl_module! {
303
	pub struct Module<T: Config> for enum Call where origin: <T as frame_system::Config>::Origin {
304
305
		type Error = Error<T>;

306
		const ModuleId: ModuleId = T::ModuleId::get();
307
308
309
		const MinContribution: BalanceOf<T> = T::MinContribution::get();
		const RemoveKeysLimit: u32 = T::RemoveKeysLimit::get();
		const RetirementPeriod: T::BlockNumber = T::RetirementPeriod::get();
310

311
		fn deposit_event() = default;
312

Shawn Tabrizi's avatar
Shawn Tabrizi committed
313
		/// Create a new crowdloaning campaign for a parachain slot deposit for the current auction.
314
315
316
		#[weight = T::WeightInfo::create()]
		pub fn create(origin,
			#[compact] index: ParaId,
317
			#[compact] cap: BalanceOf<T>,
318
319
			#[compact] first_slot: LeasePeriodOf<T>,
			#[compact] last_slot: LeasePeriodOf<T>,
320
321
			#[compact] end: T::BlockNumber,
			verifier: Option<MultiSigner>,
322
		) {
323
			let depositor = ensure_signed(origin)?;
324

325
326
327
			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);
328
			ensure!(end > <frame_system::Pallet<T>>::block_number(), Error::<T>::CannotEndInPast);
329

330
331
332
333
334
335
336
337
			// 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)?;
338

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

341
342
343
344
345
			CurrencyOf::<T>::reserve(&depositor, deposit)?;

			Funds::<T>::insert(index, FundInfo {
				retiring: false,
				depositor,
346
				verifier,
347
348
349
350
351
352
353
				deposit,
				raised: Zero::zero(),
				end,
				cap,
				last_contribution: LastContribution::Never,
				first_slot,
				last_slot,
354
				trie_index,
355
356
			});

357
358
			NextTrieIndex::put(new_trie_index);

359
360
			Self::deposit_event(RawEvent::Created(index));
		}
361

362
363
		/// 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
364
		/// slot is unable to be purchased and the timeout expires.
365
366
367
		#[weight = T::WeightInfo::contribute()]
		pub fn contribute(origin,
			#[compact] index: ParaId,
368
			#[compact] value: BalanceOf<T>,
369
			signature: Option<MultiSignature>,
370
		) {
371
372
			let who = ensure_signed(origin)?;

373
			ensure!(value >= T::MinContribution::get(), Error::<T>::ContributionTooSmall);
374
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
375
376
			fund.raised  = fund.raised.checked_add(&value).ok_or(Error::<T>::Overflow)?;
			ensure!(fund.raised <= fund.cap, Error::<T>::CapExceeded);
377

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

382
			let (old_balance, memo) = Self::contribution_get(fund.trie_index, &who);
383
384
385
386
387
388
389
390

			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);
			}

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

393
			let balance = old_balance.saturating_add(value);
394
			Self::contribution_put(fund.trie_index, &who, &balance, &memo);
395

396
			if T::Auctioneer::is_ending(now).is_some() {
397
398
399
400
401
402
				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
403
						NewRaise::append(index);
404
405
406
407
408
409
410
411
412
413
414
415
416
417
						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
418
						NewRaise::append(index);
419
420
421
422
423
						fund.last_contribution = LastContribution::PreEnding(endings_count);
					}
				}
			}

424
			Funds::<T>::insert(index, &fund);
425
426
427
428

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

429
		/// Withdraw full balance of a specific contributor.
430
		///
431
		/// Origin must be signed, but can come from anyone.
432
		///
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
		/// 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
448
			ensure_signed(origin)?;
449

450
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
451
			let now = frame_system::Pallet::<T>::block_number();
452
			let fund_account = Self::fund_account_id(index);
453
			Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
454

455
			let (balance, _) = Self::contribution_get(fund.trie_index, &who);
456
			ensure!(balance > Zero::zero(), Error::<T>::NoContributions);
457

458
			CurrencyOf::<T>::transfer(&fund_account, &who, balance, AllowDeath)?;
459

460
			Self::contribution_kill(fund.trie_index, &who);
461
			fund.raised = fund.raised.saturating_sub(balance);
462
463
464
465
			if !fund.retiring {
				fund.retiring = true;
				fund.end = now;
			}
466

467
			Funds::<T>::insert(index, &fund);
468
469
470

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

472
		/// Remove a fund after the retirement period has ended and all funds have been returned.
473
474
475
476
		///
		/// 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 {
477
			let who = ensure_signed(origin)?;
478

479
			let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
480
			let now = frame_system::Pallet::<T>::block_number();
481
			let dissolution = fund.end.saturating_add(T::RetirementPeriod::get());
482
483
484
485

			let can_dissolve = (fund.retiring && now >= dissolution) ||
				(fund.raised.is_zero() && who == fund.depositor);
			ensure!(can_dissolve, Error::<T>::NotReadyToDissolve);
486

Shawn Tabrizi's avatar
Shawn Tabrizi committed
487
			// Try killing the crowdloan child trie
488
489
490
			match Self::crowdloan_kill(fund.trie_index) {
				child::KillChildStorageResult::AllRemoved(num_removed) => {
					CurrencyOf::<T>::unreserve(&fund.depositor, fund.deposit);
491

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

497
					Funds::<T>::remove(index);
498

Shawn Tabrizi's avatar
Shawn Tabrizi committed
499
					Self::deposit_event(RawEvent::Dissolved(index));
500
501

					Ok(Some(T::WeightInfo::dissolve(num_removed)).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
502
				},
503
				child::KillChildStorageResult::SomeRemaining(num_removed) => {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
504
					Self::deposit_event(RawEvent::PartiallyDissolved(index));
505
					Ok(Some(T::WeightInfo::dissolve(num_removed)).into())
Shawn Tabrizi's avatar
Shawn Tabrizi committed
506
507
				}
			}
508
		}
509

510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
		/// Edit the configuration for an in-progress crowdloan.
		///
		/// Can only be called by Root origin.
		#[weight = T::WeightInfo::edit()]
		pub fn edit(origin,
			#[compact] index: ParaId,
			#[compact] cap: BalanceOf<T>,
			#[compact] first_slot: LeasePeriodOf<T>,
			#[compact] last_slot: LeasePeriodOf<T>,
			#[compact] end: T::BlockNumber,
			verifier: Option<MultiSigner>,
		) {
			ensure_root(origin)?;

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

			Funds::<T>::insert(index, FundInfo {
				retiring: fund.retiring,
				depositor: fund.depositor,
				verifier,
				deposit: fund.deposit,
				raised: fund.raised,
				end,
				cap,
				last_contribution: fund.last_contribution,
				first_slot,
				last_slot,
				trie_index: fund.trie_index,
			});

			Self::deposit_event(RawEvent::Edited(index));
		}

543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
		/// Add an optional memo to an existing crowdloan contribution.
		///
		/// Origin must be Signed, and the user must have contributed to the crowdloan.
		#[weight = T::WeightInfo::add_memo()]
		pub fn add_memo(origin, index: ParaId, memo: Vec<u8>) {
			let who = ensure_signed(origin)?;

			ensure!(memo.len() <= T::MaxMemoLength::get().into(), Error::<T>::MemoTooLarge);
			let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;

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

			Self::contribution_put(fund.trie_index, &who, &balance, &memo);
			Self::deposit_event(RawEvent::MemoUpdated(who, index, memo));
		}

Shawn Tabrizi's avatar
Shawn Tabrizi committed
560
		fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight {
561
			if let Some(n) = T::Auctioneer::is_ending(n) {
562
563
564
565
				if n.is_zero() {
					// first block of ending period.
					EndingsCount::mutate(|c| *c += 1);
				}
566
567
568
				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
569
570
					// 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`.
571
572
573
					let result = T::Auctioneer::place_bid(
						Self::fund_account_id(para_id),
						para_id,
574
575
576
577
						fund.first_slot,
						fund.last_slot,
						fund.raised,
					);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
578

579
					Self::deposit_event(RawEvent::HandleBidResult(para_id, result));
580
				}
581
582
583
				T::WeightInfo::on_initialize(new_raise_len)
			} else {
				T::DbWeight::get().reads(1)
584
585
586
587
588
			}
		}
	}
}

589
impl<T: Config> Module<T> {
590
591
592
593
	/// 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.
594
	pub fn fund_account_id(index: ParaId) -> T::AccountId {
595
		T::ModuleId::get().into_sub_account(index)
596
597
	}

598
	pub fn id_from_index(index: TrieIndex) -> child::ChildInfo {
599
		let mut buf = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
600
		buf.extend_from_slice(b"crowdloan");
601
		buf.extend_from_slice(&index.encode()[..]);
602
		child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
603
604
	}

605
606
	pub fn contribution_put(index: TrieIndex, who: &T::AccountId, balance: &BalanceOf<T>, memo: &[u8]) {
		who.using_encoded(|b| child::put(&Self::id_from_index(index), b, &(balance, memo)));
607
608
	}

609
610
	pub fn contribution_get(index: TrieIndex, who: &T::AccountId) -> (BalanceOf<T>, Vec<u8>) {
		who.using_encoded(|b| child::get_or_default::<(BalanceOf<T>, Vec<u8>)>(
611
			&Self::id_from_index(index),
612
613
			b,
		))
614
615
	}

616
	pub fn contribution_kill(index: TrieIndex, who: &T::AccountId) {
617
		who.using_encoded(|b| child::kill(&Self::id_from_index(index), b));
618
619
	}

620
	pub fn crowdloan_kill(index: TrieIndex) -> child::KillChildStorageResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
621
		child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()))
622
	}
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643

	/// This function checks all conditions which would qualify a crowdloan has ended.
	/// * If we have reached the `fund.end` block OR the first lease period the fund is
	///   trying to bid for has started already.
	/// * And, if the fund has enough free funds to refund full raised amount.
	fn ensure_crowdloan_ended(
		now: T::BlockNumber,
		fund_account: &T::AccountId,
		fund: &FundInfo<T::AccountId, BalanceOf<T>, T::BlockNumber, LeasePeriodOf<T>>
	) -> DispatchResult {
			// `fund.end` can represent the end of a failed crowdsale or the beginning of retirement
			// If the current lease period is past the first slot they are trying to bid for, then it is already too late
			// to win the bid.
			let current_lease_period = T::Auctioneer::lease_period_index();
			ensure!(now >= fund.end || current_lease_period > fund.first_slot, Error::<T>::FundNotEnded);
			// free balance must greater than or equal amount raised, otherwise funds are being used
			// and a bid or lease must be active.
			ensure!(CurrencyOf::<T>::free_balance(&fund_account) >= fund.raised, Error::<T>::BidOrLeaseActive);

			Ok(())
	}
644
645
}

646
647
648
649
650
651
652
653
654
655
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)
			)
		)
	}
}

656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
#[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()
	}
}

677
678
679
680
#[cfg(test)]
mod tests {
	use super::*;

681
	use std::{cell::RefCell, sync::Arc};
682
	use frame_support::{
683
		assert_ok, assert_noop, parameter_types,
684
685
		traits::{OnInitialize, OnFinalize},
	};
686
	use sp_core::H256;
687
	use primitives::v1::Id as ParaId;
688
689
	// 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.
690
	use sp_runtime::{
691
692
693
694
695
696
		testing::Header, traits::{BlakeTwo256, IdentityLookup},
	};
	use crate::{
		mock::TestRegistrar,
		traits::OnSwap,
		crowdloan,
697
	};
698
	use sp_keystore::{KeystoreExt, testing::KeyStore};
699
700
701
702
703
704
705
706
707
708

	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,
		{
709
710
711
			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
712
		}
713
	);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
714

715
716
717
	parameter_types! {
		pub const BlockHashCount: u32 = 250;
	}
718

719
	impl frame_system::Config for Test {
720
		type BaseCallFilter = ();
721
722
723
		type BlockWeights = ();
		type BlockLength = ();
		type DbWeight = ();
724
		type Origin = Origin;
725
		type Call = Call;
726
727
728
729
730
731
732
		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
733
		type Event = Event;
734
735
		type BlockHashCount = BlockHashCount;
		type Version = ();
736
		type PalletInfo = PalletInfo;
737
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
738
		type OnNewAccount = ();
739
		type OnKilledAccount = ();
740
		type SystemWeightInfo = ();
741
		type SS58Prefix = ();
742
	}
743

744
	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
745
		pub const ExistentialDeposit: u64 = 1;
746
	}
747

748
	impl pallet_balances::Config for Test {
749
		type Balance = u64;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
750
		type Event = Event;
751
		type DustRemoval = ();
752
		type ExistentialDeposit = ExistentialDeposit;
753
		type AccountStore = System;
754
		type MaxLocks = ();
755
		type WeightInfo = ();
756
757
	}

758
759
760
761
762
763
764
765
	#[derive(Copy, Clone, Eq, PartialEq, Debug)]
	struct BidPlaced {
		height: u64,
		bidder: u64,
		para: ParaId,
		first_slot: u64,
		last_slot: u64,
		amount: u64
766
767
	}
	thread_local! {
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
		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())
784
785
	}

786
787
788
789
790
791
	pub struct TestAuctioneer;
	impl Auctioneer for TestAuctioneer {
		type AccountId = u64;
		type BlockNumber = u64;
		type LeasePeriod = u64;
		type Currency = Balances;
792

793
794
		fn new_auction(duration: u64, lease_period_index: u64) -> DispatchResult {
			assert!(lease_period_index >= Self::lease_period_index());
795

796
797
798
			let ending = System::block_number().saturating_add(duration);
			AUCTION.with(|p| *p.borrow_mut() = Some((lease_period_index, ending)));
			Ok(())
799
800
		}

801
802
803
804
805
806
807
808
809
		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
810
811
		}

812
813
814
815
816
817
		fn place_bid(
			bidder: u64,
			para: ParaId,
			first_slot: u64,
			last_slot: u64,
			amount: u64
818
		) -> DispatchResult {
819
820
821
			let height = System::block_number();
			BIDS_PLACED.with(|p| p.borrow_mut().push(BidPlaced { height, bidder, para, first_slot, last_slot, amount }));
			Ok(())
822
		}
823

824
825
		fn lease_period_index() -> u64 {
			System::block_number() / 20
826
827
828
829
830
831
832
		}
	}

	parameter_types! {
		pub const SubmissionDeposit: u64 = 1;
		pub const MinContribution: u64 = 10;
		pub const RetirementPeriod: u64 = 5;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
833
834
		pub const CrowdloanModuleId: ModuleId = ModuleId(*b"py/cfund");
		pub const RemoveKeysLimit: u32 = 10;
835
		pub const MaxMemoLength: u8 = 32;
836
	}
837

838
	impl Config for Test {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
839
		type Event = Event;
840
841
842
		type SubmissionDeposit = SubmissionDeposit;
		type MinContribution = MinContribution;
		type RetirementPeriod = RetirementPeriod;
843
		type OrphanedFunds = ();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
844
845
		type ModuleId = CrowdloanModuleId;
		type RemoveKeysLimit = RemoveKeysLimit;
846
847
		type Registrar = TestRegistrar<Test>;
		type Auctioneer = TestAuctioneer;
848
		type MaxMemoLength = MaxMemoLength;
849
		type WeightInfo = crate::crowdloan::TestWeightInfo;
850
851
	}

852
	use pallet_balances::Error as BalancesError;
853
854
855

	// This function basically just builds a genesis storage key/value store according to
	// our desired mockup.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
856
	pub fn new_test_ext() -> sp_io::TestExternalities {
857
858
		let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
		pallet_balances::GenesisConfig::<Test>{
859
860
			balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)],
		}.assimilate_storage(&mut t).unwrap();
861
862
863
864
		let keystore = KeyStore::new();
		let mut t: sp_io::TestExternalities = t.into();
		t.register_extension(KeystoreExt(Arc::new(keystore)));
		t
865
866
	}

867
868
869
870
871
872
873
874
875
876
	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!()
	}

877
878
	fn run_to_block(n: u64) {
		while System::block_number() < n {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
879
			Crowdloan::on_finalize(System::block_number());
880
881
882
883
884
			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
885
			Crowdloan::on_initialize(System::block_number());
886
887
888
889
890
		}
	}

	#[test]
	fn basic_setup_works() {
891
		new_test_ext().execute_with(|| {
892
			assert_eq!(System::block_number(), 0);
893
894
			assert_eq!(Crowdloan::funds(ParaId::from(0)), None);
			let empty: Vec<ParaId> = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
895
			assert_eq!(Crowdloan::new_raise(), empty);
896
			assert_eq!(Crowdloan::contribution_get(0u32, &1).0, 0);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
897
			assert_eq!(Crowdloan::endings_count(), 0);
898
899
900
901
902
903
904
905
906
907
908

			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);
909
910
911
912
913
		});
	}

	#[test]
	fn create_works() {
914
		new_test_ext().execute_with(|| {
915
			let para = new_para();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
916
			// Now try to create a crowdloan campaign
917
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, None));
918
919
			// This is what the initial `fund_info` should look like
			let fund_info = FundInfo {
920
921
				retiring: false,
				depositor: 1,
922
923
924
925
926
927
928
929
930
				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,
931
				trie_index: 0,
932
			};
933
			assert_eq!(Crowdloan::funds(para), Some(fund_info));
934
935
			// User has deposit removed from their free balance
			assert_eq!(Balances::free_balance(1), 999);
936
937
			// Deposit is placed in reserved
			assert_eq!(Balances::reserved_balance(1), 1);
938
			// No new raise until first contribution
939
			let empty: Vec<ParaId> = Vec::new();
940
941
942
943
944
945
946
947
			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());
948
			let para = new_para();
949
			// Now try to create a crowdloan campaign
950
			assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 4, 9, Some(pubkey.clone())));
951
952
			// This is what the initial `fund_info` should look like
			let fund_info = FundInfo {
953
954
				retiring: false,
				depositor: 1,
955
				verifier: Some(pubkey),
956
957
958
959
960
961
962
963
				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,
964
				trie_index: 0,
965
			};
966
			assert_eq!(Crowdloan::funds(ParaId::from(0)), Some(fund_info));
967
968
			// User has deposit removed from their free balance
			assert_eq!(Balances::free_balance(1), 999);
969
970
			// Deposit is placed in reserved
			assert_eq!(Balances::reserved_balance(1), 1);
971
			// No new raise until first contribution
972
			let empty: Vec<ParaId> = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
973
			assert_eq!(Crowdloan::new_raise(), empty);
974
975
976
977
978
		});
	}

	#[test]
	fn create_handles_basic_errors() {
979
		new_test_ext().execute_with(|| {
980
981
982
983
984
			// 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
985
			// Cannot create a crowdloan with bad slots
986
987
988
989
			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);
990

Shawn Tabrizi's avatar
Shawn Tabrizi committed
991
			// Cannot create a crowdloan without some deposit funds
992
993
994
			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);
995
996
997
998
999
		});
	}

	#[test]
	fn contribute_works() {
1000
		new_test_ext().execute_with(|| {
For faster browsing, not all history is shown. View entire blame