crowdloan.rs 65.3 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
//!
//! The point of this module 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
23
24
25
26
//! 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.
27
28
29
30
31
32
33
34
//!
//! 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
35
//! block number) and if the fund is not successful by the closing time, then it can be dissolved.
36
37
38
39
40
//! 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.
//!
41
42
//! Contributors will get a refund of their contributions from completed funds before the crowdloan
//! can be dissolved.
43
//!
44
//! Funds may accept contributions at any point before their success or end. When a parachain
45
46
47
//! 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.
//!
48
49
50
//! Successful funds remain tracked (in the `Funds` storage item and the associated child trie) as long as
//! the parachain remains active. Users can withdraw their funds once the slot is completed and funds are
//! returned to the crowdloan account.
51

52
use frame_support::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
53
	decl_module, decl_storage, decl_event, decl_error, ensure, Identity, PalletId,
54
	storage::{child, ChildTriePrefixIterator},
55
	traits::{
56
		Currency, ReservableCurrency, Get, ExistenceRequirement::AllowDeath
57
	},
58
	pallet_prelude::{Weight, DispatchResultWithPostInfo},
59
};
60
use frame_system::{ensure_signed, ensure_root};
61
use sp_runtime::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
62
	DispatchResult, RuntimeDebug, MultiSignature, MultiSigner,
63
	traits::{
64
		AccountIdConversion, Hash, Saturating, Zero, One, CheckedAdd, Verify, IdentifyAccount,
65
	},
66
};
67
use crate::traits::{Registrar, Auctioneer};
68
use crate::slot_range::SlotRange;
69
use parity_scale_codec::{Encode, Decode};
70
use sp_std::vec::Vec;
71
72
73
74
75
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;
76

77
#[allow(dead_code)]
78
79
80
81
82
83
84
85
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;
86
87
	fn refund(k: u32, ) -> Weight;
	fn dissolve() -> Weight;
88
	fn edit() -> Weight;
89
	fn add_memo() -> Weight;
90
	fn on_initialize(n: u32, ) -> Weight;
91
	fn poke() -> Weight;
92
}
93

94
95
96
97
98
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
	fn create() -> Weight { 0 }
	fn contribute() -> Weight { 0 }
	fn withdraw() -> Weight { 0 }
99
100
	fn refund(_k: u32, ) -> Weight { 0 }
	fn dissolve() -> Weight { 0 }
101
	fn edit() -> Weight { 0 }
102
	fn add_memo() -> Weight { 0 }
103
	fn on_initialize(_n: u32, ) -> Weight { 0 }
104
	fn poke() -> Weight { 0 }
105
106
107
}

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

Shawn Tabrizi's avatar
Shawn Tabrizi committed
110
111
	/// PalletId for the crowdloan module. An appropriate value could be ```PalletId(*b"py/cfund")```
	type PalletId: Get<PalletId>;
112

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

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

Shawn Tabrizi's avatar
Shawn Tabrizi committed
120
121
	/// Max number of storage keys to remove per extrinsic call.
	type RemoveKeysLimit: Get<u32>;
122

123
124
125
126
127
128
129
130
131
132
	/// 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,
	>;
133

134
135
136
	/// The maximum length for the memo attached to a crowdloan contribution.
	type MaxMemoLength: Get<u8>;

137
138
139
140
141
	/// Weight Information for the Extrinsics in the Pallet
	type WeightInfo: WeightInfo;
}

#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
142
143
pub enum LastContribution<BlockNumber> {
	Never,
144
	PreEnding(u32),
145
146
147
	Ending(BlockNumber),
}

148
149
150
/// 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)]
151
#[codec(dumb_trait_bound)]
152
pub struct FundInfo<AccountId, Balance, BlockNumber, LeasePeriod> {
153
	/// The owning account who placed the deposit.
154
	depositor: AccountId,
155
156
	/// An optional verifier. If exists, contributions must be signed by verifier.
	verifier: Option<MultiSigner>,
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
	/// 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>,
173
174
175
176
177
178
	/// First lease period in range to bid on; it's actually a LeasePeriod, but that's the same type
	/// as BlockNumber.
	first_period: LeasePeriod,
	/// Last lease period in range to bid on; it's actually a LeasePeriod, but that's the same type
	/// as BlockNumber.
	last_period: LeasePeriod,
179
180
	/// Index used for the child trie of this fund
	trie_index: TrieIndex,
181
182
183
}

decl_storage! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
184
	trait Store for Module<T: Config> as Crowdloan {
185
		/// Info on all of the funds.
186
		Funds get(fn funds):
187
188
			map hasher(twox_64_concat) ParaId
			=> Option<FundInfo<T::AccountId, BalanceOf<T>, T::BlockNumber, LeasePeriodOf<T>>>;
189
190
191

		/// 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.
192
		NewRaise get(fn new_raise): Vec<ParaId>;
193
194

		/// The number of auctions that have entered into their ending period so far.
195
196
197
198
		EndingsCount get(fn endings_count): u32;

		/// Tracker for the next available trie index
		NextTrieIndex get(fn next_trie_index): u32;
199
200
201
202
203
	}
}

decl_event! {
	pub enum Event<T> where
204
		<T as frame_system::Config>::AccountId,
205
206
		Balance = BalanceOf<T>,
	{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
207
		/// Create a new crowdloaning campaign. [fund_index]
208
		Created(ParaId),
209
		/// Contributed to a crowd sale. [who, fund_index, amount]
210
		Contributed(AccountId, ParaId, Balance),
211
		/// Withdrew full balance of a contributor. [who, fund_index, amount]
212
		Withdrew(AccountId, ParaId, Balance),
213
214
215
216
217
		/// The loans in a fund have been partially dissolved, i.e. there are some left
		/// over child keys that still need to be killed. [fund_index]
		PartiallyRefunded(ParaId),
		/// All loans in a fund have been refunded. [fund_index]
		AllRefunded(ParaId),
218
		/// Fund is dissolved. [fund_index]
219
220
221
222
223
		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
224
		/// The result of trying to submit a new bid to the Slots pallet.
225
		HandleBidResult(ParaId, DispatchResult),
226
227
		/// The configuration to a crowdloan has been edited. [fund_index]
		Edited(ParaId),
228
229
		/// A memo has been updated. [who, fund_index, memo]
		MemoUpdated(AccountId, ParaId, Vec<u8>),
230
231
		/// A parachain has been moved to NewRaise
		AddedToNewRaise(ParaId),
232
233
234
	}
}

235
decl_error! {
236
	pub enum Error for Module<T: Config> {
237
238
239
240
241
242
243
244
		/// The current lease period is more than the first lease period.
		FirstPeriodInPast,
		/// The first lease period needs to at least be less than 3 `max_value`.
		FirstPeriodTooFarInFuture,
		/// Last lease period must be greater than first lease period.
		LastPeriodBeforeFirstPeriod,
		/// The last lease period cannot be more then 3 periods after the first period.
		LastPeriodTooFarInFuture,
245
246
		/// The campaign ends before the current block number. The end must be in the future.
		CannotEndInPast,
247
248
		/// The end date for this crowdloan is not sensible.
		EndTooFarInFuture,
249
250
251
252
253
		/// There was an overflow.
		Overflow,
		/// The contribution was below the minimum, `MinContribution`.
		ContributionTooSmall,
		/// Invalid fund index.
254
		InvalidParaId,
255
256
257
258
259
260
		/// 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
261
		/// This crowdloan does not correspond to a parachain.
262
		NotParachain,
263
264
265
266
		/// 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,
267
268
		/// Funds have not yet been returned.
		FundsNotReturned,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
269
		/// The crowdloan has not yet ended.
270
		FundNotEnded,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
271
		/// There are no contributions stored in this crowdloan.
272
		NoContributions,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
273
		/// This crowdloan has an active parachain and cannot be dissolved.
274
		HasActiveParachain,
275
276
		/// The crowdloan is not ready to dissolve. Potentially still has a slot or in retirement period.
		NotReadyToDissolve,
277
278
		/// Invalid signature.
		InvalidSignature,
279
280
		/// The provided memo is too large.
		MemoTooLarge,
281
282
		/// The fund is already in NewRaise
		AlreadyInNewRaise
283
284
285
	}
}

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

Shawn Tabrizi's avatar
Shawn Tabrizi committed
290
		const PalletId: PalletId = T::PalletId::get();
291
292
		const MinContribution: BalanceOf<T> = T::MinContribution::get();
		const RemoveKeysLimit: u32 = T::RemoveKeysLimit::get();
293

294
		fn deposit_event() = default;
295

296
		/// Create a new crowdloaning campaign for a parachain slot with the given lease period range.
297
298
299
		///
		/// This applies a lock to your parachain configuration, ensuring that it cannot be changed
		/// by the parachain manager.
300
301
302
		#[weight = T::WeightInfo::create()]
		pub fn create(origin,
			#[compact] index: ParaId,
303
			#[compact] cap: BalanceOf<T>,
304
305
			#[compact] first_period: LeasePeriodOf<T>,
			#[compact] last_period: LeasePeriodOf<T>,
306
307
			#[compact] end: T::BlockNumber,
			verifier: Option<MultiSigner>,
308
		) {
309
			let depositor = ensure_signed(origin)?;
310

311
			ensure!(first_period <= last_period, Error::<T>::LastPeriodBeforeFirstPeriod);
312
313
314
			let last_period_limit = first_period
				.checked_add(&((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into())
				.ok_or(Error::<T>::FirstPeriodTooFarInFuture)?;
315
			ensure!(last_period <= last_period_limit, Error::<T>::LastPeriodTooFarInFuture);
316
			ensure!(end > <frame_system::Pallet<T>>::block_number(), Error::<T>::CannotEndInPast);
317
318
319
			let last_possible_win_date = (first_period.saturating_add(One::one())).saturating_mul(T::Auctioneer::lease_period());
			ensure!(end <= last_possible_win_date, Error::<T>::EndTooFarInFuture);
			ensure!(first_period >= T::Auctioneer::lease_period_index(), Error::<T>::FirstPeriodInPast);
320

321
322
323
324
325
			// 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);
326
			ensure!(T::Registrar::is_registered(index), Error::<T>::InvalidParaId);
327
328
329

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

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

333
334
335
336
			CurrencyOf::<T>::reserve(&depositor, deposit)?;

			Funds::<T>::insert(index, FundInfo {
				depositor,
337
				verifier,
338
339
340
341
342
				deposit,
				raised: Zero::zero(),
				end,
				cap,
				last_contribution: LastContribution::Never,
343
344
				first_period,
				last_period,
345
				trie_index,
346
347
			});

348
			NextTrieIndex::put(new_trie_index);
349
350
			// Add a lock to the para so that the configuration cannot be changed.
			T::Registrar::apply_lock(index);
351

352
353
			Self::deposit_event(RawEvent::Created(index));
		}
354

355
		/// Contribute to a crowd sale. This will transfer some balance over to fund a parachain
356
		/// slot. It will be withdrawable when the crowdloan has ended and the funds are unused.
357
358
359
		#[weight = T::WeightInfo::contribute()]
		pub fn contribute(origin,
			#[compact] index: ParaId,
360
			#[compact] value: BalanceOf<T>,
361
			signature: Option<MultiSignature>,
362
		) {
363
364
			let who = ensure_signed(origin)?;

365
			ensure!(value >= T::MinContribution::get(), Error::<T>::ContributionTooSmall);
366
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
367
368
			fund.raised  = fund.raised.checked_add(&value).ok_or(Error::<T>::Overflow)?;
			ensure!(fund.raised <= fund.cap, Error::<T>::CapExceeded);
369

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

374
375
376
377
378
379
380
381
			// Make sure crowdloan is in a valid lease period
			let current_lease_period = T::Auctioneer::lease_period_index();
			ensure!(current_lease_period <= fund.first_period, Error::<T>::ContributionPeriodOver);

			// Make sure crowdloan has not already won.
			let fund_account = Self::fund_account_id(index);
			ensure!(!T::Auctioneer::has_won_an_auction(index, &fund_account), Error::<T>::BidOrLeaseActive);

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, &fund_account, 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
		/// 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
439
		///   - the current lease period must be greater than the fund's `last_period`.
440
441
442
443
444
445
446
447
		///
		/// 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
462
			fund.raised = fund.raised.saturating_sub(balance);

463
			Funds::<T>::insert(index, &fund);
464
465
466

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

468
469
470
		/// Automatically refund contributors of an ended crowdloan.
		/// Due to weight restrictions, this function may need to be called multiple
		/// times to fully refund all users. We will refund `RemoveKeysLimit` users at a time.
471
		///
472
473
474
475
		/// Origin must be signed, but can come from anyone.
		#[weight = T::WeightInfo::refund(T::RemoveKeysLimit::get())]
		pub fn refund(origin, #[compact] index: ParaId) -> DispatchResultWithPostInfo {
			ensure_signed(origin)?;
476

477
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
478
			let now = frame_system::Pallet::<T>::block_number();
479
480
			let fund_account = Self::fund_account_id(index);
			Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
481

482
			let mut refund_count = 0u32;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
483
			// Try killing the crowdloan child trie
484
485
486
487
488
489
490
491
492
493
494
495
496
497
			let contributions = Self::contribution_iterator(fund.trie_index);
			// Assume everyone will be refunded.
			let mut all_refunded = true;
			for (who, (balance, _)) in contributions {
				if refund_count >= T::RemoveKeysLimit::get() {
					// Not everyone was able to be refunded this time around.
					all_refunded = false;
					break;
				}
				CurrencyOf::<T>::transfer(&fund_account, &who, balance, AllowDeath)?;
				Self::contribution_kill(fund.trie_index, &who);
				fund.raised = fund.raised.saturating_sub(balance);
				refund_count += 1;
			}
498

499
500
			// Save the changes.
			Funds::<T>::insert(index, &fund);
501

502
503
504
505
506
507
508
509
510
511
			if all_refunded {
				Self::deposit_event(RawEvent::AllRefunded(index));
				// Refund for unused refund count.
				Ok(Some(T::WeightInfo::refund(refund_count)).into())
			} else {
				Self::deposit_event(RawEvent::PartiallyRefunded(index));
				// No weight to refund since we did not finish the loop.
				Ok(().into())
			}
		}
512

513
514
515
516
		/// Remove a fund after the retirement period has ended and all funds have been returned.
		#[weight = T::WeightInfo::dissolve()]
		pub fn dissolve(origin, #[compact] index: ParaId) -> DispatchResult {
			let who = ensure_signed(origin)?;
517

518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
			let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
			let now = frame_system::Pallet::<T>::block_number();

			// Only allow dissolution when the raised funds goes to zero,
			// and the caller is the fund creator or we are past the end date.
			let permitted = who == fund.depositor || now >= fund.end;
			let can_dissolve = permitted && fund.raised.is_zero();
			ensure!(can_dissolve, Error::<T>::NotReadyToDissolve);

			// Assuming state is not corrupted, the child trie should already be cleaned up
			// and all funds in the crowdloan account have been returned. If not, governance
			// can take care of that.
			debug_assert!(Self::contribution_iterator(fund.trie_index).count().is_zero());

			CurrencyOf::<T>::unreserve(&fund.depositor, fund.deposit);
			Funds::<T>::remove(index);
			Self::deposit_event(RawEvent::Dissolved(index));
			Ok(())
536
		}
537

538
539
540
541
542
543
544
		/// 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>,
545
546
			#[compact] first_period: LeasePeriodOf<T>,
			#[compact] last_period: LeasePeriodOf<T>,
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
			#[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 {
				depositor: fund.depositor,
				verifier,
				deposit: fund.deposit,
				raised: fund.raised,
				end,
				cap,
				last_contribution: fund.last_contribution,
562
563
				first_period,
				last_period,
564
565
566
567
568
569
				trie_index: fund.trie_index,
			});

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

570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
		/// 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
587
		fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight {
588
			if let Some(n) = T::Auctioneer::is_ending(n) {
589
590
591
592
				if n.is_zero() {
					// first block of ending period.
					EndingsCount::mutate(|c| *c += 1);
				}
593
594
595
				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
596
597
					// 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`.
598
599
600
					let result = T::Auctioneer::place_bid(
						Self::fund_account_id(para_id),
						para_id,
601
602
						fund.first_period,
						fund.last_period,
603
604
						fund.raised,
					);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
605

606
					Self::deposit_event(RawEvent::HandleBidResult(para_id, result));
607
				}
608
609
610
				T::WeightInfo::on_initialize(new_raise_len)
			} else {
				T::DbWeight::get().reads(1)
611
612
			}
		}
613
614
615
616
617
618
619
620
621
622
623
624
625

		/// Poke the fund into NewRaise
		///
		/// Origin must be Signed, and the fund has non-zero raise.
		#[weight = T::WeightInfo::poke()]
		pub fn poke(origin, index: ParaId) {
			ensure_signed(origin)?;
			let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
			ensure!(!fund.raised.is_zero(), Error::<T>::NoContributions);
			ensure!(!NewRaise::get().contains(&index), Error::<T>::AlreadyInNewRaise);
			NewRaise::append(index);
			Self::deposit_event(RawEvent::AddedToNewRaise(index));
		}
626
627
628
	}
}

629
impl<T: Config> Module<T> {
630
631
632
633
	/// 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.
634
	pub fn fund_account_id(index: ParaId) -> T::AccountId {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
635
		T::PalletId::get().into_sub_account(index)
636
637
	}

638
	pub fn id_from_index(index: TrieIndex) -> child::ChildInfo {
639
		let mut buf = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
640
		buf.extend_from_slice(b"crowdloan");
641
		buf.extend_from_slice(&index.encode()[..]);
642
		child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
643
644
	}

645
646
	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)));
647
648
	}

649
650
	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>)>(
651
			&Self::id_from_index(index),
652
653
			b,
		))
654
655
	}

656
	pub fn contribution_kill(index: TrieIndex, who: &T::AccountId) {
657
		who.using_encoded(|b| child::kill(&Self::id_from_index(index), b));
658
659
	}

660
	pub fn crowdloan_kill(index: TrieIndex) -> child::KillChildStorageResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
661
		child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()))
662
	}
663

664
665
666
667
668
669
	pub fn contribution_iterator(
		index: TrieIndex
	) -> ChildTriePrefixIterator<(T::AccountId, (BalanceOf<T>, Vec<u8>))> {
		ChildTriePrefixIterator::<_>::with_prefix_over_key::<Identity>(&Self::id_from_index(index), &[])
	}

670
671
672
673
674
675
676
677
678
	/// 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 {
679
680
681
			// `fund.end` can represent the end of a failed crowdloan or the beginning of retirement
			// If the current lease period is past the first period they are trying to bid for, then
			// it is already too late to win the bid.
682
			let current_lease_period = T::Auctioneer::lease_period_index();
683
			ensure!(now >= fund.end || current_lease_period > fund.first_period, Error::<T>::FundNotEnded);
684
685
686
687
688
689
			// 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(())
	}
690
691
}

692
693
694
695
696
697
698
699
700
701
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)
			)
		)
	}
}

702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
#[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()
	}
}

723
724
725
726
#[cfg(test)]
mod tests {
	use super::*;

727
	use std::{cell::RefCell, sync::Arc, collections::BTreeMap};
728
	use frame_support::{
729
		assert_ok, assert_noop, parameter_types,
730
731
		traits::{OnInitialize, OnFinalize},
	};
732
	use sp_core::H256;
733
	use primitives::v1::Id as ParaId;
734
735
	// 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.
736
	use sp_runtime::{
737
738
739
740
741
742
		testing::Header, traits::{BlakeTwo256, IdentityLookup},
	};
	use crate::{
		mock::TestRegistrar,
		traits::OnSwap,
		crowdloan,
743
	};
744
	use sp_keystore::{KeystoreExt, testing::KeyStore};
745
746
747
748
749
750
751
752
753
754

	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,
		{
755
756
757
			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
758
		}
759
	);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
760

761
762
763
	parameter_types! {
		pub const BlockHashCount: u32 = 250;
	}
764

765
766
	type BlockNumber = u64;

767
	impl frame_system::Config for Test {
768
		type BaseCallFilter = ();
769
770
771
		type BlockWeights = ();
		type BlockLength = ();
		type DbWeight = ();
772
		type Origin = Origin;
773
		type Call = Call;
774
		type Index = u64;
775
		type BlockNumber = BlockNumber;
776
777
778
779
780
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<Self::AccountId>;
		type Header = Header;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
781
		type Event = Event;
782
783
		type BlockHashCount = BlockHashCount;
		type Version = ();
784
		type PalletInfo = PalletInfo;
785
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
786
		type OnNewAccount = ();
787
		type OnKilledAccount = ();
788
		type SystemWeightInfo = ();
789
		type SS58Prefix = ();
790
		type OnSetCode = ();
791
	}
792

793
	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
794
		pub const ExistentialDeposit: u64 = 1;
795
	}
796

797
	impl pallet_balances::Config for Test {
798
		type Balance = u64;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
799
		type Event = Event;
800
		type DustRemoval = ();
801
		type ExistentialDeposit = ExistentialDeposit;
802
		type AccountStore = System;
803
		type MaxLocks = ();
804
		type WeightInfo = ();
805
806
	}

807
808
809
810
811
	#[derive(Copy, Clone, Eq, PartialEq, Debug)]
	struct BidPlaced {
		height: u64,
		bidder: u64,
		para: ParaId,
812
813
		first_period: u64,
		last_period: u64,
814
		amount: u64
815
816
	}
	thread_local! {
817
818
819
		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());
820
		static HAS_WON: RefCell<BTreeMap<(ParaId, u64), bool>> = RefCell::new(BTreeMap::new());
821
	}
822

823
824
825
826
827
828
829
830
831
832
833
834
	#[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())
835
	}
836
837
838
839
840
841
842
843
844
845
846
847
	// Emulate what would happen if we won an auction:
	// balance is reserved and a deposit_held is recorded
	fn set_winner(para: ParaId, who: u64, winner: bool) {
		let account_id = Crowdloan::fund_account_id(para);
		if winner {
			let free_balance = Balances::free_balance(&account_id);
			Balances::reserve(&account_id, free_balance).expect("should be able to reserve free balance");
		} else {
			let reserved_balance = Balances::reserved_balance(&account_id);
			Balances::unreserve(&account_id, reserved_balance);
		}
		HAS_WON.with(|p| p.borrow_mut().insert((para, who), winner));
848
	}
849

850
851
852
	pub struct TestAuctioneer;
	impl Auctioneer for TestAuctioneer {
		type AccountId = u64;
853
		type BlockNumber = BlockNumber;
854
855
		type LeasePeriod = u64;
		type Currency = Balances;
856

857
858
		fn new_auction(duration: u64, lease_period_index: u64) -> DispatchResult {
			assert!(lease_period_index >= Self::lease_period_index());
859

860
861
862
			let ending = System::block_number().saturating_add(duration);
			AUCTION.with(|p| *p.borrow_mut() = Some((lease_period_index, ending)));
			Ok(())
863
864
		}

865
866
867
868
869
870
871
872
873
		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
874
875
		}

876
877
878
		fn place_bid(
			bidder: u64,
			para: ParaId,
879
880
			first_period: u64,
			last_period: u64,
881
			amount: u64
882
		) -> DispatchResult {
883
			let height = System::block_number();
884
			BIDS_PLACED.with(|p| p.borrow_mut().push(BidPlaced { height, bidder, para, first_period, last_period, amount }));
885
			Ok(())
886
		}
887

888
		fn lease_period_index() -> u64 {
889
890
891
892
893
894
895
896
897
			System::block_number() / Self::lease_period()
		}

		fn lease_period() -> u64 {
			20
		}

		fn has_won_an_auction(para: ParaId, bidder: &u64) -> bool {
			HAS_WON.with(|p| *p.borrow().get(&(para, *bidder)).unwrap_or(&false))
898
899
900
901
902
903
		}
	}

	parameter_types! {
		pub const SubmissionDeposit: u64 = 1;
		pub const MinContribution: u64 = 10;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
904
		pub const CrowdloanPalletId: PalletId = PalletId(*b"py/cfund");
Shawn Tabrizi's avatar
Shawn Tabrizi committed
905
		pub const RemoveKeysLimit: u32 = 10;
906
		pub const MaxMemoLength: u8 = 32;
907
	}
908

909
	impl Config for Test {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
910
		type Event = Event;
911
912
		type SubmissionDeposit = SubmissionDeposit;
		type MinContribution = MinContribution;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
913
		type PalletId = CrowdloanPalletId;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
914
		type RemoveKeysLimit = RemoveKeysLimit;
915
916
		type Registrar = TestRegistrar<Test>;
		type Auctioneer = TestAuctioneer;
917
		type MaxMemoLength = MaxMemoLength;
918
		type WeightInfo = crate::crowdloan::TestWeightInfo;
919
920
	}

921
	use pallet_balances::Error as BalancesError;
922
923
924

	// This function basically just builds a genesis storage key/value store according to
	// our desired mockup.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
925
	pub fn new_test_ext() -> sp_io::TestExternalities {
926
927
		let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
		pallet_balances::GenesisConfig::<Test>{
928
929
			balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)],
		}.assimilate_storage(&mut t).unwrap();
930
931
932
933
		let keystore = KeyStore::new();
		let mut t: sp_io::TestExternalities = t.into();
		t.register_extension(KeystoreExt(Arc::new(keystore)));
		t
934
935
	}

936
937
938
939
940
941
942
943
944
945
	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!()
	}

946
947
	fn run_to_block(n: u64) {
		while System::block_number() < n {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
948
			Crowdloan::on_finalize(System::block_number());
949
950
951
952
953
			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
954
			Crowdloan::on_initialize(System::block_number());
955
956
957
		}
	}

958
959
960
961
	fn last_event() -> Event {
		System::events().pop().expect("Event expected").event
	}

962
963
	#[test]
	fn basic_setup_works() {
964
		new_test_ext().execute_with(|| {
965
			assert_eq!(System::block_number(), 0);
966
967
			assert_eq!(Crowdloan::funds(ParaId::from(0)), None);
			let empty: Vec<ParaId> = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
968
			assert_eq!(Crowdloan::new_raise(), empty);
969
			assert_eq!(Crowdloan::contribution_get(0u32, &1).0, 0);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
970
			assert_eq!(Crowdloan::endings_count(), 0);
971
972
973
974
975

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

			assert_eq!(bids(), vec![]);
			assert_ok!(TestAuctioneer::place_bid(1, 2.into(), 0, 3, 6));
976
			let b = BidPlaced { height: 0, bidder: 1, para: 2.into(), first_period: 0, last_period: 3, amount: 6 };
977
978
979
980
981
			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);
982
983
984
985
986
		});
	}

	#[test]
	fn create_works() {
987
		new_test_ext().execute_with(|| {
988
			let para = new_para();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
989
			// Now try to create a crowdloan campaign
990