crowdloan.rs 69.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/>.

Denis_P's avatar
Denis_P committed
17
//! # Parachain `Crowdloaning` pallet
18
//!
19
//! The point of this pallet is to allow parachain projects to offer the ability to help fund a
20
//! deposit for the parachain. When the crowdloan has ended, the funds are returned.
21
//!
22
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

Shawn Tabrizi's avatar
Shawn Tabrizi committed
52
53
54
55
use crate::{
	slot_range::SlotRange,
	traits::{Auctioneer, Registrar},
};
56
use frame_support::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
57
	ensure,
58
	pallet_prelude::Weight,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
59
60
61
	storage::{child, ChildTriePrefixIterator},
	traits::{Currency, ExistenceRequirement::AllowDeath, Get, ReservableCurrency},
	Identity, PalletId,
62
};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
63
64
65
pub use pallet::*;
use parity_scale_codec::{Decode, Encode};
use primitives::v1::Id as ParaId;
66
67
use sp_runtime::{
	traits::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
68
		AccountIdConversion, CheckedAdd, Hash, IdentifyAccount, One, Saturating, Verify, Zero,
69
	},
Shawn Tabrizi's avatar
Shawn Tabrizi committed
70
	MultiSignature, MultiSigner, RuntimeDebug,
71
};
72
use sp_std::vec::Vec;
73
74
75
76

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;
77

78
#[allow(dead_code)]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
79
80
type NegativeImbalanceOf<T> =
	<CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
81
82
83
84
85
86
87

type TrieIndex = u32;

pub trait WeightInfo {
	fn create() -> Weight;
	fn contribute() -> Weight;
	fn withdraw() -> Weight;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
88
	fn refund(k: u32) -> Weight;
89
	fn dissolve() -> Weight;
90
	fn edit() -> Weight;
91
	fn add_memo() -> Weight;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
92
	fn on_initialize(n: u32) -> Weight;
93
	fn poke() -> Weight;
94
}
95

96
97
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
	fn create() -> Weight {
		0
	}
	fn contribute() -> Weight {
		0
	}
	fn withdraw() -> Weight {
		0
	}
	fn refund(_k: u32) -> Weight {
		0
	}
	fn dissolve() -> Weight {
		0
	}
	fn edit() -> Weight {
		0
	}
	fn add_memo() -> Weight {
		0
	}
	fn on_initialize(_n: u32) -> Weight {
		0
	}
	fn poke() -> Weight {
		0
	}
125
126
127
}

#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
128
129
pub enum LastContribution<BlockNumber> {
	Never,
130
	PreEnding(u32),
131
132
133
	Ending(BlockNumber),
}

134
135
136
/// 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)]
137
#[codec(dumb_trait_bound)]
138
pub struct FundInfo<AccountId, Balance, BlockNumber, LeasePeriod> {
139
	/// The owning account who placed the deposit.
140
	depositor: AccountId,
141
142
	/// An optional verifier. If exists, contributions must be signed by verifier.
	verifier: Option<MultiSigner>,
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
	/// 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>,
Denis_P's avatar
Denis_P committed
159
160
	/// First lease period in range to bid on; it's actually a `LeasePeriod`, but that's the same type
	/// as `BlockNumber`.
161
	first_period: LeasePeriod,
Denis_P's avatar
Denis_P committed
162
163
	/// Last lease period in range to bid on; it's actually a `LeasePeriod`, but that's the same type
	/// as `BlockNumber`.
164
	last_period: LeasePeriod,
165
166
	/// Index used for the child trie of this fund
	trie_index: TrieIndex,
167
168
}

169
170
171
#[frame_support::pallet]
pub mod pallet {
	use super::*;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
172
173
	use frame_support::pallet_prelude::*;
	use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
174
175
176
177
178
179
180
181
182

	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	pub struct Pallet<T>(_);

	#[pallet::config]
	pub trait Config: frame_system::Config {
		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;

Denis_P's avatar
Denis_P committed
183
		/// `PalletId` for the crowdloan pallet. An appropriate value could be `PalletId(*b"py/cfund")`
184
185
186
187
188
		#[pallet::constant]
		type PalletId: Get<PalletId>;

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

190
		/// The minimum amount that may be contributed into a crowdloan. Should almost certainly be at
Denis_P's avatar
Denis_P committed
191
		/// least `ExistentialDeposit`.
192
193
		#[pallet::constant]
		type MinContribution: Get<BalanceOf<Self>>;
194

195
196
197
		/// Max number of storage keys to remove per extrinsic call.
		#[pallet::constant]
		type RemoveKeysLimit: Get<u32>;
198

Denis_P's avatar
Denis_P committed
199
		/// The parachain registrar type. We just use this to ensure that only the manager of a para is able to
200
		/// start a crowdloan for its slot.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
201
		type Registrar: Registrar<AccountId = Self::AccountId>;
202
203
204

		/// The type representing the auctioning system.
		type Auctioneer: Auctioneer<
Shawn Tabrizi's avatar
Shawn Tabrizi committed
205
206
207
			AccountId = Self::AccountId,
			BlockNumber = Self::BlockNumber,
			LeasePeriod = Self::BlockNumber,
208
209
210
211
212
213
214
		>;

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

		/// Weight Information for the Extrinsics in the Pallet
		type WeightInfo: WeightInfo;
215
216
	}

217
218
219
220
221
	/// Info on all of the funds.
	#[pallet::storage]
	#[pallet::getter(fn funds)]
	pub(super) type Funds<T: Config> = StorageMap<
		_,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
222
223
		Twox64Concat,
		ParaId,
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
		FundInfo<T::AccountId, BalanceOf<T>, T::BlockNumber, LeasePeriodOf<T>>,
	>;

	/// 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.
	#[pallet::storage]
	#[pallet::getter(fn new_raise)]
	pub(super) type NewRaise<T> = StorageValue<_, Vec<ParaId>, ValueQuery>;

	/// The number of auctions that have entered into their ending period so far.
	#[pallet::storage]
	#[pallet::getter(fn endings_count)]
	pub(super) type EndingsCount<T> = StorageValue<_, u32, ValueQuery>;

	/// Tracker for the next available trie index
	#[pallet::storage]
	#[pallet::getter(fn next_trie_index)]
	pub(super) type NextTrieIndex<T> = StorageValue<_, u32, ValueQuery>;

	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	#[pallet::metadata(T::AccountId = "AccountId", BalanceOf<T> = "Balance")]
	pub enum Event<T: Config> {
Denis_P's avatar
Denis_P committed
247
		/// Create a new crowdloaning campaign. `[fund_index]`
248
		Created(ParaId),
Denis_P's avatar
Denis_P committed
249
		/// Contributed to a crowd sale. `[who, fund_index, amount]`
250
		Contributed(T::AccountId, ParaId, BalanceOf<T>),
Denis_P's avatar
Denis_P committed
251
		/// Withdrew full balance of a contributor. `[who, fund_index, amount]`
252
		Withdrew(T::AccountId, ParaId, BalanceOf<T>),
253
		/// The loans in a fund have been partially dissolved, i.e. there are some left
Denis_P's avatar
Denis_P committed
254
		/// over child keys that still need to be killed. `[fund_index]`
255
		PartiallyRefunded(ParaId),
Denis_P's avatar
Denis_P committed
256
		/// All loans in a fund have been refunded. `[fund_index]`
257
		AllRefunded(ParaId),
Denis_P's avatar
Denis_P committed
258
		/// Fund is dissolved. `[fund_index]`
259
		Dissolved(ParaId),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
260
		/// The result of trying to submit a new bid to the Slots pallet.
261
		HandleBidResult(ParaId, DispatchResult),
Denis_P's avatar
Denis_P committed
262
		/// The configuration to a crowdloan has been edited. `[fund_index]`
263
		Edited(ParaId),
Denis_P's avatar
Denis_P committed
264
		/// A memo has been updated. `[who, fund_index, memo]`
265
		MemoUpdated(T::AccountId, ParaId, Vec<u8>),
Denis_P's avatar
Denis_P committed
266
		/// A parachain has been moved to `NewRaise`
267
		AddedToNewRaise(ParaId),
268
269
	}

270
271
	#[pallet::error]
	pub enum Error<T> {
272
273
274
275
276
277
278
279
		/// 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,
280
281
		/// The campaign ends before the current block number. The end must be in the future.
		CannotEndInPast,
282
283
		/// The end date for this crowdloan is not sensible.
		EndTooFarInFuture,
284
285
286
287
288
		/// There was an overflow.
		Overflow,
		/// The contribution was below the minimum, `MinContribution`.
		ContributionTooSmall,
		/// Invalid fund index.
289
		InvalidParaId,
290
291
292
293
294
295
		/// 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
296
		/// This crowdloan does not correspond to a parachain.
297
		NotParachain,
298
299
300
301
		/// 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,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
302
		/// The crowdloan has not yet ended.
303
		FundNotEnded,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
304
		/// There are no contributions stored in this crowdloan.
305
		NoContributions,
306
307
		/// The crowdloan is not ready to dissolve. Potentially still has a slot or in retirement period.
		NotReadyToDissolve,
308
309
		/// Invalid signature.
		InvalidSignature,
310
311
		/// The provided memo is too large.
		MemoTooLarge,
Denis_P's avatar
Denis_P committed
312
		/// The fund is already in `NewRaise`
313
314
315
		AlreadyInNewRaise,
		/// No contributions allowed during the VRF delay
		VrfDelayInProgress,
316
317
	}

318
319
	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
320
321
322
323
		fn on_initialize(num: T::BlockNumber) -> frame_support::weights::Weight {
			if let Some((sample, sub_sample)) = T::Auctioneer::auction_status(num).is_ending() {
				// This is the very first block in the ending period
				if sample.is_zero() && sub_sample.is_zero() {
324
325
326
327
328
					// first block of ending period.
					EndingsCount::<T>::mutate(|c| *c += 1);
				}
				let new_raise = NewRaise::<T>::take();
				let new_raise_len = new_raise.len() as u32;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
329
330
331
				for (fund, para_id) in
					new_raise.into_iter().filter_map(|i| Self::funds(i).map(|f| (f, i)))
				{
332
333
334
335
336
337
338
339
340
					// 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`.
					let result = T::Auctioneer::place_bid(
						Self::fund_account_id(para_id),
						para_id,
						fund.first_period,
						fund.last_period,
						fund.raised,
					);
341

342
343
344
345
346
347
348
349
					Self::deposit_event(Event::<T>::HandleBidResult(para_id, result));
				}
				T::WeightInfo::on_initialize(new_raise_len)
			} else {
				T::DbWeight::get().reads(1)
			}
		}
	}
350

351
352
	#[pallet::call]
	impl<T: Config> Pallet<T> {
353
		/// Create a new crowdloaning campaign for a parachain slot with the given lease period range.
354
355
356
		///
		/// This applies a lock to your parachain configuration, ensuring that it cannot be changed
		/// by the parachain manager.
357
358
359
360
361
362
363
364
		#[pallet::weight(T::WeightInfo::create())]
		pub fn create(
			origin: OriginFor<T>,
			#[pallet::compact] index: ParaId,
			#[pallet::compact] cap: BalanceOf<T>,
			#[pallet::compact] first_period: LeasePeriodOf<T>,
			#[pallet::compact] last_period: LeasePeriodOf<T>,
			#[pallet::compact] end: T::BlockNumber,
365
			verifier: Option<MultiSigner>,
366
		) -> DispatchResult {
367
			let depositor = ensure_signed(origin)?;
368

369
			ensure!(first_period <= last_period, Error::<T>::LastPeriodBeforeFirstPeriod);
370
371
372
			let last_period_limit = first_period
				.checked_add(&((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into())
				.ok_or(Error::<T>::FirstPeriodTooFarInFuture)?;
373
			ensure!(last_period <= last_period_limit, Error::<T>::LastPeriodTooFarInFuture);
374
			ensure!(end > <frame_system::Pallet<T>>::block_number(), Error::<T>::CannotEndInPast);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
375
376
			let last_possible_win_date = (first_period.saturating_add(One::one()))
				.saturating_mul(T::Auctioneer::lease_period());
377
			ensure!(end <= last_possible_win_date, Error::<T>::EndTooFarInFuture);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
378
379
380
381
			ensure!(
				first_period >= T::Auctioneer::lease_period_index(),
				Error::<T>::FirstPeriodInPast
			);
382

383
384
385
386
387
			// 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);
388
			ensure!(T::Registrar::is_registered(index), Error::<T>::InvalidParaId);
389
390
391

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

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

395
396
			CurrencyOf::<T>::reserve(&depositor, deposit)?;

Shawn Tabrizi's avatar
Shawn Tabrizi committed
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
			Funds::<T>::insert(
				index,
				FundInfo {
					depositor,
					verifier,
					deposit,
					raised: Zero::zero(),
					end,
					cap,
					last_contribution: LastContribution::Never,
					first_period,
					last_period,
					trie_index,
				},
			);
412

413
			NextTrieIndex::<T>::put(new_trie_index);
414
415
			// Add a lock to the para so that the configuration cannot be changed.
			T::Registrar::apply_lock(index);
416

417
418
			Self::deposit_event(Event::<T>::Created(index));
			Ok(())
419
		}
420

421
		/// Contribute to a crowd sale. This will transfer some balance over to fund a parachain
422
		/// slot. It will be withdrawable when the crowdloan has ended and the funds are unused.
423
424
425
426
427
		#[pallet::weight(T::WeightInfo::contribute())]
		pub fn contribute(
			origin: OriginFor<T>,
			#[pallet::compact] index: ParaId,
			#[pallet::compact] value: BalanceOf<T>,
428
			signature: Option<MultiSignature>,
429
		) -> DispatchResult {
430
431
			let who = ensure_signed(origin)?;

432
			ensure!(value >= T::MinContribution::get(), Error::<T>::ContributionTooSmall);
433
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
434
			fund.raised = fund.raised.checked_add(&value).ok_or(Error::<T>::Overflow)?;
435
			ensure!(fund.raised <= fund.cap, Error::<T>::CapExceeded);
436

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

441
442
443
444
445
446
			// 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);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
447
448
449
450
			ensure!(
				!T::Auctioneer::has_won_an_auction(index, &fund_account),
				Error::<T>::BidOrLeaseActive
			);
451

452
453
454
455
			// We disallow any crowdloan contributions during the VRF Period, so that people do not sneak their
			// contributions into the auction when it would not impact the outcome.
			ensure!(!T::Auctioneer::auction_status(now).is_vrf(), Error::<T>::VrfDelayInProgress);

456
			let (old_balance, memo) = Self::contribution_get(fund.trie_index, &who);
457
458
459
460

			if let Some(ref verifier) = fund.verifier {
				let signature = signature.ok_or(Error::<T>::InvalidSignature)?;
				let payload = (index, &who, old_balance, value);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
461
462
463
				let valid = payload.using_encoded(|encoded| {
					signature.verify(encoded, &verifier.clone().into_account())
				});
464
465
466
				ensure!(valid, Error::<T>::InvalidSignature);
			}

467
			CurrencyOf::<T>::transfer(&who, &fund_account, value, AllowDeath)?;
468

469
			let balance = old_balance.saturating_add(value);
470
			Self::contribution_put(fund.trie_index, &who, &balance, &memo);
471

472
			if T::Auctioneer::auction_status(now).is_ending().is_some() {
473
474
475
476
				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
477
					},
478
					_ => {
479
						NewRaise::<T>::append(index);
480
						fund.last_contribution = LastContribution::Ending(now);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
481
					},
482
483
484
485
486
487
488
489
				}
			} 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.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
490
					},
491
492
493
					_ => {
						// Not in ending period; but an auction has been ending since our previous
						// bid, or we never had one to begin with. Add bid.
494
						NewRaise::<T>::append(index);
495
						fund.last_contribution = LastContribution::PreEnding(endings_count);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
496
					},
497
498
499
				}
			}

500
			Funds::<T>::insert(index, &fund);
501

502
503
			Self::deposit_event(Event::<T>::Contributed(who, index, value));
			Ok(())
504
505
		}

506
		/// Withdraw full balance of a specific contributor.
507
		///
508
		/// Origin must be signed, but can come from anyone.
509
		///
510
511
512
513
514
515
		/// 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
516
		///   - the current lease period must be greater than the fund's `last_period`.
517
518
519
520
521
522
		///
		/// 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.
523
524
525
526
527
528
		#[pallet::weight(T::WeightInfo::withdraw())]
		pub fn withdraw(
			origin: OriginFor<T>,
			who: T::AccountId,
			#[pallet::compact] index: ParaId,
		) -> DispatchResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
529
			ensure_signed(origin)?;
530

531
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
532
			let now = frame_system::Pallet::<T>::block_number();
533
			let fund_account = Self::fund_account_id(index);
534
			Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
535

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

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

541
			Self::contribution_kill(fund.trie_index, &who);
542
543
			fund.raised = fund.raised.saturating_sub(balance);

544
			Funds::<T>::insert(index, &fund);
545

546
547
			Self::deposit_event(Event::<T>::Withdrew(who, index, balance));
			Ok(())
548
		}
549

550
551
552
		/// 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.
553
		///
554
		/// Origin must be signed, but can come from anyone.
555
556
557
558
559
		#[pallet::weight(T::WeightInfo::refund(T::RemoveKeysLimit::get()))]
		pub fn refund(
			origin: OriginFor<T>,
			#[pallet::compact] index: ParaId,
		) -> DispatchResultWithPostInfo {
560
			ensure_signed(origin)?;
561

562
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
563
			let now = frame_system::Pallet::<T>::block_number();
564
565
			let fund_account = Self::fund_account_id(index);
			Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
566

567
			let mut refund_count = 0u32;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
568
			// Try killing the crowdloan child trie
569
570
571
572
573
574
575
			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;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
576
					break
577
578
579
580
581
582
				}
				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;
			}
583

584
585
			// Save the changes.
			Funds::<T>::insert(index, &fund);
586

587
			if all_refunded {
588
				Self::deposit_event(Event::<T>::AllRefunded(index));
589
590
591
				// Refund for unused refund count.
				Ok(Some(T::WeightInfo::refund(refund_count)).into())
			} else {
592
				Self::deposit_event(Event::<T>::PartiallyRefunded(index));
593
594
595
596
				// No weight to refund since we did not finish the loop.
				Ok(().into())
			}
		}
597

598
		/// Remove a fund after the retirement period has ended and all funds have been returned.
599
600
		#[pallet::weight(T::WeightInfo::dissolve())]
		pub fn dissolve(origin: OriginFor<T>, #[pallet::compact] index: ParaId) -> DispatchResult {
601
			let who = ensure_signed(origin)?;
602

603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
			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);
619
			Self::deposit_event(Event::<T>::Dissolved(index));
620
			Ok(())
621
		}
622

623
624
625
		/// Edit the configuration for an in-progress crowdloan.
		///
		/// Can only be called by Root origin.
626
627
628
629
630
631
632
633
		#[pallet::weight(T::WeightInfo::edit())]
		pub fn edit(
			origin: OriginFor<T>,
			#[pallet::compact] index: ParaId,
			#[pallet::compact] cap: BalanceOf<T>,
			#[pallet::compact] first_period: LeasePeriodOf<T>,
			#[pallet::compact] last_period: LeasePeriodOf<T>,
			#[pallet::compact] end: T::BlockNumber,
634
			verifier: Option<MultiSigner>,
635
		) -> DispatchResult {
636
637
638
639
			ensure_root(origin)?;

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

Shawn Tabrizi's avatar
Shawn Tabrizi committed
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
			Funds::<T>::insert(
				index,
				FundInfo {
					depositor: fund.depositor,
					verifier,
					deposit: fund.deposit,
					raised: fund.raised,
					end,
					cap,
					last_contribution: fund.last_contribution,
					first_period,
					last_period,
					trie_index: fund.trie_index,
				},
			);
655

656
657
			Self::deposit_event(Event::<T>::Edited(index));
			Ok(())
658
659
		}

660
661
662
		/// Add an optional memo to an existing crowdloan contribution.
		///
		/// Origin must be Signed, and the user must have contributed to the crowdloan.
663
664
		#[pallet::weight(T::WeightInfo::add_memo())]
		pub fn add_memo(origin: OriginFor<T>, index: ParaId, memo: Vec<u8>) -> DispatchResult {
665
666
667
668
669
670
671
672
673
			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);
674
675
			Self::deposit_event(Event::<T>::MemoUpdated(who, index, memo));
			Ok(())
676
		}
677

Denis_P's avatar
Denis_P committed
678
		/// Poke the fund into `NewRaise`
679
680
		///
		/// Origin must be Signed, and the fund has non-zero raise.
681
682
		#[pallet::weight(T::WeightInfo::poke())]
		pub fn poke(origin: OriginFor<T>, index: ParaId) -> DispatchResult {
683
684
685
			ensure_signed(origin)?;
			let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
			ensure!(!fund.raised.is_zero(), Error::<T>::NoContributions);
686
687
688
689
			ensure!(!NewRaise::<T>::get().contains(&index), Error::<T>::AlreadyInNewRaise);
			NewRaise::<T>::append(index);
			Self::deposit_event(Event::<T>::AddedToNewRaise(index));
			Ok(())
690
		}
691
692
693
	}
}

694
impl<T: Config> Pallet<T> {
695
696
697
698
	/// 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.
699
	pub fn fund_account_id(index: ParaId) -> T::AccountId {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
700
		T::PalletId::get().into_sub_account(index)
701
702
	}

703
	pub fn id_from_index(index: TrieIndex) -> child::ChildInfo {
704
		let mut buf = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
705
		buf.extend_from_slice(b"crowdloan");
706
		buf.extend_from_slice(&index.encode()[..]);
707
		child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
708
709
	}

Shawn Tabrizi's avatar
Shawn Tabrizi committed
710
711
712
713
714
715
	pub fn contribution_put(
		index: TrieIndex,
		who: &T::AccountId,
		balance: &BalanceOf<T>,
		memo: &[u8],
	) {
716
		who.using_encoded(|b| child::put(&Self::id_from_index(index), b, &(balance, memo)));
717
718
	}

719
	pub fn contribution_get(index: TrieIndex, who: &T::AccountId) -> (BalanceOf<T>, Vec<u8>) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
720
721
722
		who.using_encoded(|b| {
			child::get_or_default::<(BalanceOf<T>, Vec<u8>)>(&Self::id_from_index(index), b)
		})
723
724
	}

725
	pub fn contribution_kill(index: TrieIndex, who: &T::AccountId) {
726
		who.using_encoded(|b| child::kill(&Self::id_from_index(index), b));
727
728
	}

729
	pub fn crowdloan_kill(index: TrieIndex) -> child::KillStorageResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
730
		child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()))
731
	}
732

733
	pub fn contribution_iterator(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
734
		index: TrieIndex,
735
	) -> ChildTriePrefixIterator<(T::AccountId, (BalanceOf<T>, Vec<u8>))> {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
736
737
738
739
		ChildTriePrefixIterator::<_>::with_prefix_over_key::<Identity>(
			&Self::id_from_index(index),
			&[],
		)
740
741
	}

742
743
744
745
746
747
748
	/// 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,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
749
		fund: &FundInfo<T::AccountId, BalanceOf<T>, T::BlockNumber, LeasePeriodOf<T>>,
750
	) -> sp_runtime::DispatchResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
		// `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.
		let current_lease_period = T::Auctioneer::lease_period_index();
		ensure!(
			now >= fund.end || current_lease_period > fund.first_period,
			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(())
767
	}
768
769
}

770
impl<T: Config> crate::traits::OnSwap for Pallet<T> {
771
	fn on_swap(one: ParaId, other: ParaId) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
772
		Funds::<T>::mutate(one, |x| Funds::<T>::mutate(other, |y| sp_std::mem::swap(x, y)))
773
774
775
	}
}

776
777
778
#[cfg(any(feature = "runtime-benchmarks", test))]
mod crypto {
	use sp_core::ed25519;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
779
780
781
	use sp_io::crypto::{ed25519_generate, ed25519_sign};
	use sp_runtime::{MultiSignature, MultiSigner};
	use sp_std::{convert::TryFrom, vec::Vec};
782
783
784
785
786
787
788
789
790
791
792
793

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

794
795
796
797
#[cfg(test)]
mod tests {
	use super::*;

798
	use frame_support::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
799
800
		assert_noop, assert_ok, parameter_types,
		traits::{OnFinalize, OnInitialize},
801
	};
802
	use primitives::v1::Id as ParaId;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
803
804
	use sp_core::H256;
	use std::{cell::RefCell, collections::BTreeMap, sync::Arc};
805
806
	// 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.
807
808
	use crate::{
		crowdloan,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
809
810
811
812
813
814
815
816
		mock::TestRegistrar,
		traits::{AuctionStatus, OnSwap},
	};
	use sp_keystore::{testing::KeyStore, KeystoreExt};
	use sp_runtime::{
		testing::Header,
		traits::{BlakeTwo256, IdentityLookup},
		DispatchResult,
817
	};
818
819
820
821
822
823
824
825
826
827

	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,
		{
828
829
830
			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
831
		}
832
	);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
833

834
835
836
	parameter_types! {
		pub const BlockHashCount: u32 = 250;
	}
837

838
839
	type BlockNumber = u64;

840
	impl frame_system::Config for Test {
841
		type BaseCallFilter = frame_support::traits::AllowAll;
842
843
844
		type BlockWeights = ();
		type BlockLength = ();
		type DbWeight = ();
845
		type Origin = Origin;
846
		type Call = Call;
847
		type Index = u64;
848
		type BlockNumber = BlockNumber;
849
850
851
852
853
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<Self::AccountId>;
		type Header = Header;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
854
		type Event = Event;
855
856
		type BlockHashCount = BlockHashCount;
		type Version = ();
857
		type PalletInfo = PalletInfo;
858
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
859
		type OnNewAccount = ();
860
		type OnKilledAccount = ();
861
		type SystemWeightInfo = ();
862
		type SS58Prefix = ();
863
		type OnSetCode = ();
864
	}
865

866
	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
867
		pub const ExistentialDeposit: u64 = 1;
868
	}
869

870
	impl pallet_balances::Config for Test {
871
		type Balance = u64;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
872
		type Event = Event;
873
		type DustRemoval = ();
874
		type ExistentialDeposit = ExistentialDeposit;
875
		type AccountStore = System;
876
		type MaxLocks = ();
Gavin Wood's avatar
Gavin Wood committed
877
878
		type MaxReserves = ();
		type ReserveIdentifier = [u8; 8];
879
		type WeightInfo = ();
880
881
	}

882
883
884
885
886
	#[derive(Copy, Clone, Eq, PartialEq, Debug)]
	struct BidPlaced {
		height: u64,
		bidder: u64,
		para: ParaId,
887
888
		first_period: u64,
		last_period: u64,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
889
		amount: u64,
890
891
	}
	thread_local! {
892
		static AUCTION: RefCell<Option<(u64, u64)>> = RefCell::new(None);
893
		static VRF_DELAY: RefCell<u64> = RefCell::new(0);
894
895
		static ENDING_PERIOD: RefCell<u64> = RefCell::new(5);
		static BIDS_PLACED: RefCell<Vec<BidPlaced>> = RefCell::new(Vec::new());
896
		static HAS_WON: RefCell<BTreeMap<(ParaId, u64), bool>> = RefCell::new(BTreeMap::new());
897
	}
898

899
900
901
902
903
904
905
906
907
908
909
910
	#[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())
911
	}
912
913
914
915
916
917
	fn vrf_delay() -> u64 {
		VRF_DELAY.with(|p| p.borrow().clone())
	}
	fn set_vrf_delay(delay: u64) {
		VRF_DELAY.with(|p| *p.borrow_mut() = delay);
	}
918
919
920
921
922
923
	// 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);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
924
925
			Balances::reserve(&account_id, free_balance)
				.expect("should be able to reserve free balance");
926
927
928
929
930
		} 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));
931
	}
932

933
934
935
	pub struct TestAuctioneer;
	impl Auctioneer for TestAuctioneer {
		type AccountId = u64;
936
		type BlockNumber = BlockNumber;
937
938
		type LeasePeriod = u64;
		type Currency = Balances;
939

940
941
		fn new_auction(duration: u64, lease_period_index: u64) -> DispatchResult {
			assert!(lease_period_index >= Self::lease_period_index());
942

943
944
945
			let ending = System::block_number().saturating_add(duration);
			AUCTION.with(|p| *p.borrow_mut() = Some((lease_period_index, ending)));
			Ok(())
946
947
		}

948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
		fn auction_status(now: u64) -> AuctionStatus<u64> {
			let early_end = match auction() {
				Some((_, early_end)) => early_end,
				None => return AuctionStatus::NotStarted,
			};
			let after_early_end = match now.checked_sub(early_end) {
				Some(after_early_end) => after_early_end,
				None => return AuctionStatus::StartingPeriod,
			};

			let ending_period = ending_period();
			if after_early_end < ending_period {
				return AuctionStatus::EndingPeriod(after_early_end, 0)
			} else {
				let after_end = after_early_end - ending_period;
				// Optional VRF delay
				if after_end < vrf_delay() {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
965
					return AuctionStatus::VrfDelay(after_end)
966
967
				} else {
					// VRF delay is done, so we just end the auction
Shawn Tabrizi's avatar
Shawn Tabrizi committed
968
					return AuctionStatus::NotStarted
969
970
				}
			}
971
972
		}

973
974
975
		fn place_bid(
			bidder: u64,
			para: ParaId,
976
977
			first_period: u64,
			last_period: u64,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
978
			amount: u64,
979
		) -> DispatchResult {
980
			let height = System::block_number();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
981
982
983
984
985
986
987
988
989
990
			BIDS_PLACED.with(|p| {
				p.borrow_mut().push(BidPlaced {
					height,
					bidder,
					para,
					first_period,
					last_period,
					amount,
				})
			});
991
			Ok(())
992
		}
993

994
		fn lease_period_index() -> u64 {
995
996
997
998
999
1000