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

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
use scale_info::TypeInfo;
67
68
use sp_runtime::{
	traits::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
69
		AccountIdConversion, CheckedAdd, Hash, IdentifyAccount, One, Saturating, Verify, Zero,
70
	},
Shawn Tabrizi's avatar
Shawn Tabrizi committed
71
	MultiSignature, MultiSigner, RuntimeDebug,
72
};
73
use sp_std::vec::Vec;
74

75
76
77
78
79
type CurrencyOf<T> =
	<<T as Config>::Auctioneer as Auctioneer<<T as frame_system::Config>::BlockNumber>>::Currency;
type LeasePeriodOf<T> = <<T as Config>::Auctioneer as Auctioneer<
	<T as frame_system::Config>::BlockNumber,
>>::LeasePeriod;
80
type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
81

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

type TrieIndex = u32;

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

100
101
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
	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
	}
129
130
}

131
#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
132
133
pub enum LastContribution<BlockNumber> {
	Never,
134
	PreEnding(u32),
135
136
137
	Ending(BlockNumber),
}

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

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

	#[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
187
		/// `PalletId` for the crowdloan pallet. An appropriate value could be `PalletId(*b"py/cfund")`
188
189
190
191
192
		#[pallet::constant]
		type PalletId: Get<PalletId>;

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

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

199
200
201
		/// Max number of storage keys to remove per extrinsic call.
		#[pallet::constant]
		type RemoveKeysLimit: Get<u32>;
202

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

		/// The type representing the auctioning system.
		type Auctioneer: Auctioneer<
209
			Self::BlockNumber,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
210
211
			AccountId = Self::AccountId,
			LeasePeriod = Self::BlockNumber,
212
213
214
215
216
217
218
		>;

		/// 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;
219
220
	}

221
222
223
224
225
	/// 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
226
227
		Twox64Concat,
		ParaId,
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
		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)]
	pub enum Event<T: Config> {
Denis_P's avatar
Denis_P committed
250
		/// Create a new crowdloaning campaign. `[fund_index]`
251
		Created(ParaId),
Denis_P's avatar
Denis_P committed
252
		/// Contributed to a crowd sale. `[who, fund_index, amount]`
253
		Contributed(T::AccountId, ParaId, BalanceOf<T>),
Denis_P's avatar
Denis_P committed
254
		/// Withdrew full balance of a contributor. `[who, fund_index, amount]`
255
		Withdrew(T::AccountId, ParaId, BalanceOf<T>),
256
		/// The loans in a fund have been partially dissolved, i.e. there are some left
Denis_P's avatar
Denis_P committed
257
		/// over child keys that still need to be killed. `[fund_index]`
258
		PartiallyRefunded(ParaId),
Denis_P's avatar
Denis_P committed
259
		/// All loans in a fund have been refunded. `[fund_index]`
260
		AllRefunded(ParaId),
Denis_P's avatar
Denis_P committed
261
		/// Fund is dissolved. `[fund_index]`
262
		Dissolved(ParaId),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
263
		/// The result of trying to submit a new bid to the Slots pallet.
264
		HandleBidResult(ParaId, DispatchResult),
Denis_P's avatar
Denis_P committed
265
		/// The configuration to a crowdloan has been edited. `[fund_index]`
266
		Edited(ParaId),
Denis_P's avatar
Denis_P committed
267
		/// A memo has been updated. `[who, fund_index, memo]`
268
		MemoUpdated(T::AccountId, ParaId, Vec<u8>),
Denis_P's avatar
Denis_P committed
269
		/// A parachain has been moved to `NewRaise`
270
		AddedToNewRaise(ParaId),
271
272
	}

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

323
324
	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
325
326
327
328
		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() {
329
330
331
332
333
					// 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
334
335
336
				for (fund, para_id) in
					new_raise.into_iter().filter_map(|i| Self::funds(i).map(|f| (f, i)))
				{
337
338
339
340
341
342
343
344
345
					// 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,
					);
346

347
348
349
350
351
352
353
354
					Self::deposit_event(Event::<T>::HandleBidResult(para_id, result));
				}
				T::WeightInfo::on_initialize(new_raise_len)
			} else {
				T::DbWeight::get().reads(1)
			}
		}
	}
355

356
357
	#[pallet::call]
	impl<T: Config> Pallet<T> {
358
		/// Create a new crowdloaning campaign for a parachain slot with the given lease period range.
359
360
361
		///
		/// This applies a lock to your parachain configuration, ensuring that it cannot be changed
		/// by the parachain manager.
362
363
364
365
366
367
368
369
		#[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,
370
			verifier: Option<MultiSigner>,
371
		) -> DispatchResult {
372
			let depositor = ensure_signed(origin)?;
373
			let now = frame_system::Pallet::<T>::block_number();
374

375
			ensure!(first_period <= last_period, Error::<T>::LastPeriodBeforeFirstPeriod);
376
377
378
			let last_period_limit = first_period
				.checked_add(&((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into())
				.ok_or(Error::<T>::FirstPeriodTooFarInFuture)?;
379
			ensure!(last_period <= last_period_limit, Error::<T>::LastPeriodTooFarInFuture);
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
			ensure!(end > now, Error::<T>::CannotEndInPast);

			// Here we check the lease period on the ending block is at most the first block of the
			// period after `first_period`. If it would be larger, there is no way we could win an
			// active auction, thus it would make no sense to have a crowdloan this long.
			let (lease_period_at_end, is_first_block) =
				T::Auctioneer::lease_period_index(end).ok_or(Error::<T>::NoLeasePeriod)?;
			let adjusted_lease_period_at_end = if is_first_block {
				lease_period_at_end.saturating_sub(One::one())
			} else {
				lease_period_at_end
			};
			ensure!(adjusted_lease_period_at_end <= first_period, Error::<T>::EndTooFarInFuture);

			// Can't start a crowdloan for a lease period that already passed.
			if let Some((current_lease_period, _)) = T::Auctioneer::lease_period_index(now) {
				ensure!(first_period >= current_lease_period, Error::<T>::FirstPeriodInPast);
			}
398

399
400
401
402
403
			// 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);
404
			ensure!(T::Registrar::is_registered(index), Error::<T>::InvalidParaId);
405
406
407

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

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

411
412
			CurrencyOf::<T>::reserve(&depositor, deposit)?;

Shawn Tabrizi's avatar
Shawn Tabrizi committed
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
			Funds::<T>::insert(
				index,
				FundInfo {
					depositor,
					verifier,
					deposit,
					raised: Zero::zero(),
					end,
					cap,
					last_contribution: LastContribution::Never,
					first_period,
					last_period,
					trie_index,
				},
			);
428

429
			NextTrieIndex::<T>::put(new_trie_index);
430
431
			// Add a lock to the para so that the configuration cannot be changed.
			T::Registrar::apply_lock(index);
432

433
434
			Self::deposit_event(Event::<T>::Created(index));
			Ok(())
435
		}
436

437
		/// Contribute to a crowd sale. This will transfer some balance over to fund a parachain
438
		/// slot. It will be withdrawable when the crowdloan has ended and the funds are unused.
439
440
441
442
443
		#[pallet::weight(T::WeightInfo::contribute())]
		pub fn contribute(
			origin: OriginFor<T>,
			#[pallet::compact] index: ParaId,
			#[pallet::compact] value: BalanceOf<T>,
444
			signature: Option<MultiSignature>,
445
		) -> DispatchResult {
446
447
			let who = ensure_signed(origin)?;

448
			ensure!(value >= T::MinContribution::get(), Error::<T>::ContributionTooSmall);
449
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
450
			fund.raised = fund.raised.checked_add(&value).ok_or(Error::<T>::Overflow)?;
451
			ensure!(fund.raised <= fund.cap, Error::<T>::CapExceeded);
452

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

457
			// Make sure crowdloan is in a valid lease period
458
459
460
			let now = frame_system::Pallet::<T>::block_number();
			let (current_lease_period, _) =
				T::Auctioneer::lease_period_index(now).ok_or(Error::<T>::NoLeasePeriod)?;
461
462
463
464
			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
465
466
467
468
			ensure!(
				!T::Auctioneer::has_won_an_auction(index, &fund_account),
				Error::<T>::BidOrLeaseActive
			);
469

470
471
472
473
			// 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);

474
			let (old_balance, memo) = Self::contribution_get(fund.trie_index, &who);
475
476
477
478

			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
479
480
481
				let valid = payload.using_encoded(|encoded| {
					signature.verify(encoded, &verifier.clone().into_account())
				});
482
483
484
				ensure!(valid, Error::<T>::InvalidSignature);
			}

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

487
			let balance = old_balance.saturating_add(value);
488
			Self::contribution_put(fund.trie_index, &who, &balance, &memo);
489

490
			if T::Auctioneer::auction_status(now).is_ending().is_some() {
491
492
493
494
				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
495
					},
496
					_ => {
497
						NewRaise::<T>::append(index);
498
						fund.last_contribution = LastContribution::Ending(now);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
499
					},
500
501
502
503
504
505
506
507
				}
			} 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
508
					},
509
510
511
					_ => {
						// Not in ending period; but an auction has been ending since our previous
						// bid, or we never had one to begin with. Add bid.
512
						NewRaise::<T>::append(index);
513
						fund.last_contribution = LastContribution::PreEnding(endings_count);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
514
					},
515
516
517
				}
			}

518
			Funds::<T>::insert(index, &fund);
519

520
521
			Self::deposit_event(Event::<T>::Contributed(who, index, value));
			Ok(())
522
523
		}

524
		/// Withdraw full balance of a specific contributor.
525
		///
526
		/// Origin must be signed, but can come from anyone.
527
		///
528
529
530
531
532
533
		/// 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
534
		///   - the current lease period must be greater than the fund's `last_period`.
535
536
537
538
539
540
		///
		/// 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.
541
542
543
544
545
546
		#[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
547
			ensure_signed(origin)?;
548

549
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
550
			let now = frame_system::Pallet::<T>::block_number();
551
			let fund_account = Self::fund_account_id(index);
552
			Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
553

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

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

559
			Self::contribution_kill(fund.trie_index, &who);
560
561
			fund.raised = fund.raised.saturating_sub(balance);

562
			Funds::<T>::insert(index, &fund);
563

564
565
			Self::deposit_event(Event::<T>::Withdrew(who, index, balance));
			Ok(())
566
		}
567

568
569
570
		/// 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.
571
		///
572
		/// Origin must be signed, but can come from anyone.
573
574
575
576
577
		#[pallet::weight(T::WeightInfo::refund(T::RemoveKeysLimit::get()))]
		pub fn refund(
			origin: OriginFor<T>,
			#[pallet::compact] index: ParaId,
		) -> DispatchResultWithPostInfo {
578
			ensure_signed(origin)?;
579

580
			let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
581
			let now = frame_system::Pallet::<T>::block_number();
582
583
			let fund_account = Self::fund_account_id(index);
			Self::ensure_crowdloan_ended(now, &fund_account, &fund)?;
584

585
			let mut refund_count = 0u32;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
586
			// Try killing the crowdloan child trie
587
588
589
590
591
592
593
			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
594
					break
595
596
597
598
599
600
				}
				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;
			}
601

602
603
			// Save the changes.
			Funds::<T>::insert(index, &fund);
604

605
			if all_refunded {
606
				Self::deposit_event(Event::<T>::AllRefunded(index));
607
608
609
				// Refund for unused refund count.
				Ok(Some(T::WeightInfo::refund(refund_count)).into())
			} else {
610
				Self::deposit_event(Event::<T>::PartiallyRefunded(index));
611
612
613
614
				// No weight to refund since we did not finish the loop.
				Ok(().into())
			}
		}
615

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

621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
			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);
637
			Self::deposit_event(Event::<T>::Dissolved(index));
638
			Ok(())
639
		}
640

641
642
643
		/// Edit the configuration for an in-progress crowdloan.
		///
		/// Can only be called by Root origin.
644
645
646
647
648
649
650
651
		#[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,
652
			verifier: Option<MultiSigner>,
653
		) -> DispatchResult {
654
655
656
657
			ensure_root(origin)?;

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

Shawn Tabrizi's avatar
Shawn Tabrizi committed
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
			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,
				},
			);
673

674
675
			Self::deposit_event(Event::<T>::Edited(index));
			Ok(())
676
677
		}

678
679
680
		/// Add an optional memo to an existing crowdloan contribution.
		///
		/// Origin must be Signed, and the user must have contributed to the crowdloan.
681
682
		#[pallet::weight(T::WeightInfo::add_memo())]
		pub fn add_memo(origin: OriginFor<T>, index: ParaId, memo: Vec<u8>) -> DispatchResult {
683
684
685
686
687
688
689
690
691
			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);
692
693
			Self::deposit_event(Event::<T>::MemoUpdated(who, index, memo));
			Ok(())
694
		}
695

Denis_P's avatar
Denis_P committed
696
		/// Poke the fund into `NewRaise`
697
698
		///
		/// Origin must be Signed, and the fund has non-zero raise.
699
700
		#[pallet::weight(T::WeightInfo::poke())]
		pub fn poke(origin: OriginFor<T>, index: ParaId) -> DispatchResult {
701
702
703
			ensure_signed(origin)?;
			let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?;
			ensure!(!fund.raised.is_zero(), Error::<T>::NoContributions);
704
705
706
707
			ensure!(!NewRaise::<T>::get().contains(&index), Error::<T>::AlreadyInNewRaise);
			NewRaise::<T>::append(index);
			Self::deposit_event(Event::<T>::AddedToNewRaise(index));
			Ok(())
708
		}
709
710
711
	}
}

712
impl<T: Config> Pallet<T> {
713
714
715
716
	/// 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.
717
	pub fn fund_account_id(index: ParaId) -> T::AccountId {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
718
		T::PalletId::get().into_sub_account(index)
719
720
	}

721
	pub fn id_from_index(index: TrieIndex) -> child::ChildInfo {
722
		let mut buf = Vec::new();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
723
		buf.extend_from_slice(b"crowdloan");
724
		buf.extend_from_slice(&index.encode()[..]);
725
		child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref())
726
727
	}

Shawn Tabrizi's avatar
Shawn Tabrizi committed
728
729
730
731
732
733
	pub fn contribution_put(
		index: TrieIndex,
		who: &T::AccountId,
		balance: &BalanceOf<T>,
		memo: &[u8],
	) {
734
		who.using_encoded(|b| child::put(&Self::id_from_index(index), b, &(balance, memo)));
735
736
	}

737
	pub fn contribution_get(index: TrieIndex, who: &T::AccountId) -> (BalanceOf<T>, Vec<u8>) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
738
739
740
		who.using_encoded(|b| {
			child::get_or_default::<(BalanceOf<T>, Vec<u8>)>(&Self::id_from_index(index), b)
		})
741
742
	}

743
	pub fn contribution_kill(index: TrieIndex, who: &T::AccountId) {
744
		who.using_encoded(|b| child::kill(&Self::id_from_index(index), b));
745
746
	}

747
	pub fn crowdloan_kill(index: TrieIndex) -> child::KillStorageResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
748
		child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()))
749
	}
750

751
	pub fn contribution_iterator(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
752
		index: TrieIndex,
753
	) -> ChildTriePrefixIterator<(T::AccountId, (BalanceOf<T>, Vec<u8>))> {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
754
755
756
757
		ChildTriePrefixIterator::<_>::with_prefix_over_key::<Identity>(
			&Self::id_from_index(index),
			&[],
		)
758
759
	}

760
761
762
763
764
765
766
	/// 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
767
		fund: &FundInfo<T::AccountId, BalanceOf<T>, T::BlockNumber, LeasePeriodOf<T>>,
768
	) -> sp_runtime::DispatchResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
769
770
771
		// `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.
772
773
		let (current_lease_period, _) =
			T::Auctioneer::lease_period_index(now).ok_or(Error::<T>::NoLeasePeriod)?;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
774
775
776
777
778
779
780
781
782
783
784
785
		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(())
786
	}
787
788
}

789
impl<T: Config> crate::traits::OnSwap for Pallet<T> {
790
	fn on_swap(one: ParaId, other: ParaId) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
791
		Funds::<T>::mutate(one, |x| Funds::<T>::mutate(other, |y| sp_std::mem::swap(x, y)))
792
793
794
	}
}

795
796
797
#[cfg(any(feature = "runtime-benchmarks", test))]
mod crypto {
	use sp_core::ed25519;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
798
799
800
	use sp_io::crypto::{ed25519_generate, ed25519_sign};
	use sp_runtime::{MultiSignature, MultiSigner};
	use sp_std::{convert::TryFrom, vec::Vec};
801
802
803
804
805
806
807
808
809
810
811
812

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

813
814
815
816
#[cfg(test)]
mod tests {
	use super::*;

817
	use frame_support::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
818
819
		assert_noop, assert_ok, parameter_types,
		traits::{OnFinalize, OnInitialize},
820
	};
821
	use primitives::v1::Id as ParaId;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
822
823
	use sp_core::H256;
	use std::{cell::RefCell, collections::BTreeMap, sync::Arc};
824
825
	// 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.
826
827
	use crate::{
		crowdloan,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
828
829
830
831
832
833
834
835
		mock::TestRegistrar,
		traits::{AuctionStatus, OnSwap},
	};
	use sp_keystore::{testing::KeyStore, KeystoreExt};
	use sp_runtime::{
		testing::Header,
		traits::{BlakeTwo256, IdentityLookup},
		DispatchResult,
836
	};
837
838
839
840
841
842
843
844
845
846

	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,
		{
847
848
849
			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
850
		}
851
	);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
852

853
854
855
	parameter_types! {
		pub const BlockHashCount: u32 = 250;
	}
856

857
858
	type BlockNumber = u64;

859
	impl frame_system::Config for Test {
860
		type BaseCallFilter = frame_support::traits::Everything;
861
862
863
		type BlockWeights = ();
		type BlockLength = ();
		type DbWeight = ();
864
		type Origin = Origin;
865
		type Call = Call;
866
		type Index = u64;
867
		type BlockNumber = BlockNumber;
868
869
870
871
872
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<Self::AccountId>;
		type Header = Header;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
873
		type Event = Event;
874
875
		type BlockHashCount = BlockHashCount;
		type Version = ();
876
		type PalletInfo = PalletInfo;
877
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
878
		type OnNewAccount = ();
879
		type OnKilledAccount = ();
880
		type SystemWeightInfo = ();
881
		type SS58Prefix = ();
882
		type OnSetCode = ();
883
	}
884

885
	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
886
		pub const ExistentialDeposit: u64 = 1;
887
	}
888

889
	impl pallet_balances::Config for Test {
890
		type Balance = u64;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
891
		type Event = Event;
892
		type DustRemoval = ();
893
		type ExistentialDeposit = ExistentialDeposit;
894
		type AccountStore = System;
895
		type MaxLocks = ();
Gavin Wood's avatar
Gavin Wood committed
896
897
		type MaxReserves = ();
		type ReserveIdentifier = [u8; 8];
898
		type WeightInfo = ();
899
900
	}

901
902
903
904
905
	#[derive(Copy, Clone, Eq, PartialEq, Debug)]
	struct BidPlaced {
		height: u64,
		bidder: u64,
		para: ParaId,
906
907
		first_period: u64,
		last_period: u64,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
908
		amount: u64,
909
910
	}
	thread_local! {
911
		static AUCTION: RefCell<Option<(u64, u64)>> = RefCell::new(None);
912
		static VRF_DELAY: RefCell<u64> = RefCell::new(0);
913
914
		static ENDING_PERIOD: RefCell<u64> = RefCell::new(5);
		static BIDS_PLACED: RefCell<Vec<BidPlaced>> = RefCell::new(Vec::new());
915
		static HAS_WON: RefCell<BTreeMap<(ParaId, u64), bool>> = RefCell::new(BTreeMap::new());
916
	}
917

918
919
920
921
922
923
924
925
926
927
928
929
	#[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())
930
	}
931
932
933
934
935
936
	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);
	}
937
938
939
940
941
942
	// 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
943
944
			Balances::reserve(&account_id, free_balance)
				.expect("should be able to reserve free balance");
945
946
947
948
949
		} 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));
950
	}
951

952
	pub struct TestAuctioneer;
953
	impl Auctioneer<u64> for TestAuctioneer {
954
955
956
		type AccountId = u64;
		type LeasePeriod = u64;
		type Currency = Balances;
957

958
		fn new_auction(duration: u64, lease_period_index: u64) -> DispatchResult {
959
960
961
962
			let now = System::block_number();
			let (current_lease_period, _) =
				Self::lease_period_index(now).ok_or("no lease period yet")?;
			assert!(lease_period_index >= current_lease_period);
963

964
965
966
			let ending = System::block_number().saturating_add(duration);
			AUCTION.with(|p| *p.borrow_mut() = Some((lease_period_index, ending)));
			Ok(())
967
968
		}

969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
		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
986
					return AuctionStatus::VrfDelay(after_end)
987
988
				} else {
					// VRF delay is done, so we just end the auction
Shawn Tabrizi's avatar
Shawn Tabrizi committed
989
					return AuctionStatus::NotStarted