1
// Copyright 2019-2022 PureStake Inc.
2
// This file is part of Moonbeam.
3

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

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

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

            
17
//! # Crowdloan Rewards Pallet
18
//!
19
//! This pallet is DEPRECATED, the remaining code handle only unclaimed rewards.
20
//!
21
//! ## Payout Mechanism
22
//!
23
//! The current payout mechanism requires contributors to claim their payouts. Because they are
24
//! paying the transaction fees for this themselves, they can do it as often as every block, or
25
//! wait and claim the entire thing once it is fully vested. We could consider auto payouts if we
26
//! want.
27

            
28
#![cfg_attr(not(feature = "std"), no_std)]
29

            
30
pub use crate::weights::WeightInfo;
31
use frame_support::pallet;
32
pub use pallet::*;
33

            
34
#[cfg(any(test, feature = "runtime-benchmarks"))]
35
mod benchmarks;
36
#[cfg(test)]
37
mod mock;
38
#[cfg(test)]
39
mod tests;
40
pub mod weights;
41

            
42
#[pallet]
43
pub mod pallet {
44
	use super::*;
45
	use frame_support::{
46
		pallet_prelude::*,
47
		traits::{Currency, ExistenceRequirement::AllowDeath, WithdrawReasons},
48
		PalletId,
49
	};
50
	use frame_system::pallet_prelude::*;
51
	use parity_scale_codec::{Decode, Encode};
52
	use sp_core::crypto::AccountId32;
53
	use sp_runtime::traits::{
54
		AccountIdConversion, AtLeast32BitUnsigned, BlockNumberProvider, Saturating, Verify,
55
	};
56
	use sp_runtime::{MultiSignature, Perbill};
57
	use sp_std::collections::btree_map::BTreeMap;
58
	use sp_std::vec;
59
	use sp_std::vec::Vec;
60
	#[pallet::pallet]
61
	#[pallet::without_storage_info]
62
	// The crowdloan rewards pallet
63
	pub struct Pallet<T>(PhantomData<T>);
64

            
65
	pub const PALLET_ID: PalletId = PalletId(*b"Crowdloa");
66

            
67
	// The wrapper around which the reward changing message needs to be wrapped
68
	pub const WRAPPED_BYTES_PREFIX: &[u8] = b"<Bytes>";
69
	pub const WRAPPED_BYTES_POSTFIX: &[u8] = b"</Bytes>";
70

            
71
	/// Configuration trait of this pallet.
72
	#[pallet::config]
73
	pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
74
		/// Checker for the reward vec, is it initalized already?
75
		type Initialized: Get<bool>;
76
		/// Percentage to be payed at initialization
77
		#[pallet::constant]
78
		type InitializationPayment: Get<Perbill>;
79
		// Max number of contributors that can be inserted at once in initialize_reward_vec
80
		#[pallet::constant]
81
		type MaxInitContributors: Get<u32>;
82
		/// The minimum contribution to which rewards will be paid.
83
		type MinimumReward: Get<BalanceOf<Self>>;
84
		/// A fraction representing the percentage of proofs
85
		/// that need to be presented to change a reward address through the relay keys
86
		#[pallet::constant]
87
		type RewardAddressRelayVoteThreshold: Get<Perbill>;
88
		/// The currency in which the rewards will be paid (probably the parachain native currency)
89
		type RewardCurrency: Currency<Self::AccountId>;
90
		/// The AccountId type contributors used on the relay chain.
91
		type RelayChainAccountId: Parameter
92
			//TODO these AccountId32 bounds feel a little extraneous. I wonder if we can remove them.
93
			+ Into<AccountId32>
94
			+ From<AccountId32>
95
			+ Ord
96
			+ sp_runtime::serde::Serialize
97
			+ for<'a> sp_runtime::serde::Deserialize<'a>;
98

            
99
		// The origin that is allowed to change the reward address with relay signatures
100
		type RewardAddressChangeOrigin: EnsureOrigin<Self::RuntimeOrigin>;
101

            
102
		/// Network Identifier to be appended into the signatures for reward address change/association
103
		/// Prevents replay attacks from one network to the other
104
		#[pallet::constant]
105
		type SignatureNetworkIdentifier: Get<&'static [u8]>;
106

            
107
		// The origin that is allowed to change the reward address with relay signatures
108
		type RewardAddressAssociateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
109

            
110
		/// The type that will be used to track vesting progress
111
		type VestingBlockNumber: AtLeast32BitUnsigned
112
			+ Parameter
113
			+ Default
114
			+ Into<BalanceOf<Self>>
115
			+ sp_runtime::serde::Serialize
116
			+ for<'a> sp_runtime::serde::Deserialize<'a>;
117

            
118
		/// The notion of time that will be used for vesting. Probably
119
		/// either the relay chain or sovereign chain block number.
120
		type VestingBlockProvider: BlockNumberProvider<BlockNumber = Self::VestingBlockNumber>;
121

            
122
		type WeightInfo: WeightInfo;
123
	}
124

            
125
	pub type BalanceOf<T> = <<T as Config>::RewardCurrency as Currency<
126
		<T as frame_system::Config>::AccountId,
127
	>>::Balance;
128

            
129
	/// Type alias for contributor data: (relay_account, optional_native_account, reward)
130
	pub type ContributorData<T> = (
131
		<T as Config>::RelayChainAccountId,
132
		Option<<T as frame_system::Config>::AccountId>,
133
		BalanceOf<T>,
134
	);
135

            
136
	/// Stores info about the rewards owed as well as how much has been vested so far.
137
	/// For a primer on this kind of design, see the recipe on compounding interest
138
	/// https://substrate.dev/recipes/fixed-point.html#continuously-compounding
139
	#[derive(Default, Clone, Encode, Decode, RuntimeDebug, PartialEq, scale_info::TypeInfo)]
140
	#[scale_info(skip_type_params(T))]
141
	pub struct RewardInfo<T: Config> {
142
		pub total_reward: BalanceOf<T>,
143
		pub claimed_reward: BalanceOf<T>,
144
		pub contributed_relay_addresses: Vec<T::RelayChainAccountId>,
145
	}
146

            
147
	#[pallet::call]
148
	impl<T: Config> Pallet<T> {
149
		/// Associate a native rewards_destination identity with a crowdloan contribution.
150
		///
151
		/// The caller needs to provide the unassociated relay account and a proof to succeed
152
		/// with the association
153
		/// The proof is nothing but a signature over the reward_address using the relay keys
154
		#[pallet::call_index(0)]
155
		#[pallet::weight(T::WeightInfo::associate_native_identity())]
156
		pub fn associate_native_identity(
157
			origin: OriginFor<T>,
158
			reward_account: T::AccountId,
159
			relay_account: T::RelayChainAccountId,
160
			proof: MultiSignature,
161
5
		) -> DispatchResultWithPostInfo {
162
			// Check that the origin is the one able to asociate the reward addrss
163
5
			T::RewardAddressChangeOrigin::ensure_origin(origin)?;
164

            
165
			// Check the proof:
166
			// 1. Is signed by an actual unassociated contributor
167
			// 2. Signs a valid native identity
168
			// Check the proof. The Proof consists of a Signature of the rewarded account with the
169
			// claimer key
170

            
171
			// The less costly checks will go first
172

            
173
			// The relay account should be unassociated
174
5
			let mut reward_info = UnassociatedContributions::<T>::get(&relay_account)
175
5
				.ok_or(Error::<T>::NoAssociatedClaim)?;
176

            
177
			// We ensure the relay chain id wast not yet associated to avoid multi-claiming
178
			// We dont need this right now, as it will always be true if the above check is true
179
4
			ensure!(
180
4
				ClaimedRelayChainIds::<T>::get(&relay_account).is_none(),
181
				Error::<T>::AlreadyAssociated
182
			);
183

            
184
			// For now I prefer that we dont support providing an existing account here
185
4
			ensure!(
186
4
				AccountsPayable::<T>::get(&reward_account).is_none(),
187
1
				Error::<T>::AlreadyAssociated
188
			);
189

            
190
			// b"<Bytes>" "SignatureNetworkIdentifier" + "new_account" + b"</Bytes>"
191
3
			let mut payload = WRAPPED_BYTES_PREFIX.to_vec();
192
3
			payload.append(&mut T::SignatureNetworkIdentifier::get().to_vec());
193
3
			payload.append(&mut reward_account.encode());
194
3
			payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec());
195

            
196
			// Check the signature
197
3
			Self::verify_signatures(
198
3
				vec![(relay_account.clone(), proof)],
199
3
				reward_info.clone(),
200
3
				payload,
201
1
			)?;
202

            
203
			// Make the first payment
204
2
			let first_payment = T::InitializationPayment::get() * reward_info.total_reward;
205

            
206
2
			T::RewardCurrency::transfer(
207
2
				&PALLET_ID.into_account_truncating(),
208
2
				&reward_account,
209
2
				first_payment,
210
2
				AllowDeath,
211
			)?;
212

            
213
2
			Self::deposit_event(Event::InitialPaymentMade(
214
2
				reward_account.clone(),
215
2
				first_payment,
216
2
			));
217

            
218
2
			reward_info.claimed_reward = first_payment;
219

            
220
			// Insert on payable
221
2
			AccountsPayable::<T>::insert(&reward_account, &reward_info);
222

            
223
			// Remove from unassociated
224
2
			<UnassociatedContributions<T>>::remove(&relay_account);
225

            
226
			// Insert in mapping
227
2
			ClaimedRelayChainIds::<T>::insert(&relay_account, ());
228

            
229
			// Emit Event
230
2
			Self::deposit_event(Event::NativeIdentityAssociated(
231
2
				relay_account,
232
2
				reward_account,
233
2
				reward_info.total_reward,
234
2
			));
235

            
236
2
			Ok(Default::default())
237
		}
238

            
239
		/// Change reward account by submitting proofs from relay accounts
240
		///
241
		/// The number of valid proofs needs to be bigger than 'RewardAddressRelayVoteThreshold'
242
		/// The account to be changed needs to be submitted as 'previous_account'
243
		/// Origin must be RewardAddressChangeOrigin
244
		#[pallet::call_index(1)]
245
		#[pallet::weight(T::WeightInfo::change_association_with_relay_keys(proofs.len() as u32))]
246
		pub fn change_association_with_relay_keys(
247
			origin: OriginFor<T>,
248
			reward_account: T::AccountId,
249
			previous_account: T::AccountId,
250
			proofs: Vec<(T::RelayChainAccountId, MultiSignature)>,
251
2
		) -> DispatchResultWithPostInfo {
252
			// Check that the origin is the one able to change the reward addrss
253
2
			T::RewardAddressChangeOrigin::ensure_origin(origin)?;
254

            
255
			// For now I prefer that we dont support providing an existing account here
256
2
			ensure!(
257
2
				AccountsPayable::<T>::get(&reward_account).is_none(),
258
				Error::<T>::AlreadyAssociated
259
			);
260

            
261
			// To avoid replay attacks, we make sure the payload contains the previous address too
262
			// I am assuming no rational user will go back to a previously changed reward address
263
			// b"<Bytes>" + "SignatureNetworkIdentifier" + new_account" + "previous_account" + b"</Bytes>"
264
2
			let mut payload = WRAPPED_BYTES_PREFIX.to_vec();
265
2
			payload.append(&mut T::SignatureNetworkIdentifier::get().to_vec());
266
2
			payload.append(&mut reward_account.encode());
267
2
			payload.append(&mut previous_account.encode());
268
2
			payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec());
269

            
270
			// Get the reward info for the account to be changed
271
2
			let reward_info = AccountsPayable::<T>::get(&previous_account)
272
2
				.ok_or(Error::<T>::NoAssociatedClaim)?;
273

            
274
2
			Self::verify_signatures(proofs, reward_info.clone(), payload)?;
275

            
276
			// Remove fromon payable
277
1
			AccountsPayable::<T>::remove(&previous_account);
278

            
279
			// Insert on payable
280
1
			AccountsPayable::<T>::insert(&reward_account, &reward_info);
281

            
282
			// Emit Event
283
1
			Self::deposit_event(Event::RewardAddressUpdated(
284
1
				previous_account,
285
1
				reward_account,
286
1
			));
287

            
288
1
			Ok(Default::default())
289
		}
290

            
291
		/// Collect whatever portion of your reward are currently vested.
292
		#[pallet::call_index(2)]
293
		#[pallet::weight(T::WeightInfo::claim())]
294
45
		pub fn claim(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
295
45
			let payee = ensure_signed(origin)?;
296
45
			let initialized = <Initialized<T>>::get();
297
45
			ensure!(initialized, Error::<T>::RewardVecNotFullyInitializedYet);
298
			// Calculate the veted amount on demand.
299
37
			let mut info =
300
43
				AccountsPayable::<T>::get(&payee).ok_or(Error::<T>::NoAssociatedClaim)?;
301
37
			ensure!(
302
37
				info.claimed_reward < info.total_reward,
303
4
				Error::<T>::RewardsAlreadyClaimed
304
			);
305

            
306
			// Get the current block used for vesting purposes
307
33
			let now = T::VestingBlockProvider::current_block_number();
308

            
309
			// Substract the first payment from the vested amount
310
33
			let first_paid = T::InitializationPayment::get() * info.total_reward;
311

            
312
			// To calculate how much could the user have claimed already
313
33
			let payable_period = now.saturating_sub(<InitVestingBlock<T>>::get());
314

            
315
			// How much should the contributor have already claimed by this block?
316
			// By multiplying first we allow the conversion to integer done with the biggest number
317
33
			let period = EndVestingBlock::<T>::get() - InitVestingBlock::<T>::get();
318
33
			let should_have_claimed = if period == 0u32.into() {
319
				// Pallet is configured with a zero vesting period.
320
1
				info.total_reward - first_paid
321
			} else {
322
32
				(info.total_reward - first_paid).saturating_mul(payable_period.into())
323
32
					/ period.into()
324
			};
325

            
326
			// If the period is bigger than whats missing to pay, then return whats missing to pay
327
33
			let payable_amount = if should_have_claimed >= (info.total_reward - first_paid) {
328
7
				info.total_reward.saturating_sub(info.claimed_reward)
329
			} else {
330
26
				should_have_claimed + first_paid - info.claimed_reward
331
			};
332

            
333
33
			info.claimed_reward = info.claimed_reward.saturating_add(payable_amount);
334
33
			AccountsPayable::<T>::insert(&payee, &info);
335

            
336
			// This pallet controls an amount of funds and transfers them to each of the contributors
337
			//TODO: contributors should have the balance locked for transfers but not for democracy
338
33
			T::RewardCurrency::transfer(
339
33
				&PALLET_ID.into_account_truncating(),
340
33
				&payee,
341
33
				payable_amount,
342
33
				AllowDeath,
343
			)?;
344
			// Emit event
345
33
			Self::deposit_event(Event::RewardsPaid(payee, payable_amount));
346
33
			Ok(Default::default())
347
		}
348

            
349
		/// Update reward address, proving that the caller owns the current native key
350
		#[pallet::call_index(3)]
351
		#[pallet::weight(T::WeightInfo::update_reward_address())]
352
		pub fn update_reward_address(
353
			origin: OriginFor<T>,
354
			new_reward_account: T::AccountId,
355
7
		) -> DispatchResultWithPostInfo {
356
7
			let signer = ensure_signed(origin)?;
357

            
358
			// Calculate the veted amount on demand.
359
7
			let info = AccountsPayable::<T>::get(&signer).ok_or(Error::<T>::NoAssociatedClaim)?;
360

            
361
			// For now I prefer that we dont support providing an existing account here
362
6
			ensure!(
363
6
				AccountsPayable::<T>::get(&new_reward_account).is_none(),
364
2
				Error::<T>::AlreadyAssociated
365
			);
366

            
367
			// Remove previous rewarded account
368
4
			AccountsPayable::<T>::remove(&signer);
369

            
370
			// Update new rewarded account
371
4
			AccountsPayable::<T>::insert(&new_reward_account, &info);
372

            
373
			// Emit event
374
4
			Self::deposit_event(Event::RewardAddressUpdated(signer, new_reward_account));
375

            
376
4
			Ok(Default::default())
377
		}
378
	}
379

            
380
	impl<T: Config> Pallet<T> {
381
		/// The account ID that holds the Crowdloan's funds
382
88
		pub fn account_id() -> T::AccountId {
383
88
			PALLET_ID.into_account_truncating()
384
88
		}
385
		/// The Account Id's balance
386
51
		pub fn pot() -> BalanceOf<T> {
387
51
			T::RewardCurrency::free_balance(&Self::account_id())
388
51
		}
389
		/// Verify a set of signatures made with relay chain accounts
390
		/// We are verifying all the signatures, and then counting
391
		/// We could do something more efficient like count as we verify
392
		/// In any of the cases the weight will need to account for all the signatures,
393
		/// as we dont know beforehand whether they will be valid
394
		#[allow(clippy::map_entry)] // Cannot use entry API due to ensure! checks before insert
395
5
		fn verify_signatures(
396
5
			proofs: Vec<(T::RelayChainAccountId, MultiSignature)>,
397
5
			reward_info: RewardInfo<T>,
398
5
			payload: Vec<u8>,
399
5
		) -> DispatchResult {
400
			// The proofs should
401
			// 1. be signed by contributors to this address, otherwise they are not counted
402
			// 2. Signs a valid native identity
403
			// 3. The sum of the valid proofs needs to be bigger than InsufficientNumberOfValidProofs
404

            
405
			// I use a map here for faster lookups
406
5
			let mut voted: BTreeMap<T::RelayChainAccountId, ()> = BTreeMap::new();
407
12
			for (relay_account, signature) in proofs {
408
				// We just count votes that we have not seen
409
8
				if !voted.contains_key(&relay_account) {
410
					// Maybe I should not error here?
411
8
					ensure!(
412
8
						reward_info
413
8
							.contributed_relay_addresses
414
8
							.contains(&relay_account),
415
						Error::<T>::NonContributedAddressProvided
416
					);
417

            
418
					// I am erroring here as I think it is good to know the reason in the single-case
419
					// signature
420
8
					ensure!(
421
8
						signature.verify(payload.as_slice(), &relay_account.clone().into()),
422
1
						Error::<T>::InvalidClaimSignature
423
					);
424
7
					voted.insert(relay_account, ());
425
				}
426
			}
427

            
428
			// Ensure the votes are sufficient
429
4
			ensure!(
430
4
				Perbill::from_rational(
431
4
					voted.len() as u32,
432
4
					reward_info.contributed_relay_addresses.len() as u32
433
4
				) >= T::RewardAddressRelayVoteThreshold::get(),
434
1
				Error::<T>::InsufficientNumberOfValidProofs
435
			);
436
3
			Ok(())
437
5
		}
438

            
439
24
		pub fn complete_initialization(
440
24
			lease_ending_block: T::VestingBlockNumber,
441
24
		) -> DispatchResultWithPostInfo {
442
24
			let initialized = <Initialized<T>>::get();
443

            
444
			// This ensures there was no prior initialization
445
24
			ensure!(!initialized, Error::<T>::RewardVecAlreadyInitialized);
446

            
447
			// This ensures the end vesting block (when all funds are fully vested)
448
			// is bigger than the init vesting block
449
22
			ensure!(
450
22
				lease_ending_block > InitVestingBlock::<T>::get(),
451
1
				Error::<T>::VestingPeriodNonValid
452
			);
453

            
454
21
			let current_initialized_rewards = InitializedRewardAmount::<T>::get();
455

            
456
21
			let reward_difference = Self::pot().saturating_sub(current_initialized_rewards);
457

            
458
			// Ensure the difference is not bigger than the total number of contributors
459
21
			ensure!(
460
21
				reward_difference < TotalContributors::<T>::get().into(),
461
1
				Error::<T>::RewardsDoNotMatchFund
462
			);
463

            
464
			// Burn the difference
465
20
			let imbalance = T::RewardCurrency::withdraw(
466
20
				&PALLET_ID.into_account_truncating(),
467
20
				reward_difference,
468
				WithdrawReasons::TRANSFER,
469
20
				AllowDeath,
470
			)
471
20
			.expect("Shouldnt fail, as the fund should be enough to burn and nothing is locked");
472
20
			drop(imbalance);
473

            
474
20
			EndVestingBlock::<T>::put(lease_ending_block);
475

            
476
20
			<Initialized<T>>::put(true);
477

            
478
20
			Ok(Default::default())
479
24
		}
480

            
481
31
		pub fn initialize_reward_vec(
482
31
			rewards: Vec<ContributorData<T>>,
483
31
		) -> DispatchResultWithPostInfo {
484
31
			let initialized = <Initialized<T>>::get();
485
31
			ensure!(!initialized, Error::<T>::RewardVecAlreadyInitialized);
486

            
487
			// Ensure we are below the max number of contributors
488
29
			ensure!(
489
29
				rewards.len() as u32 <= T::MaxInitContributors::get(),
490
1
				Error::<T>::TooManyContributors
491
			);
492

            
493
			// What is the amount initialized so far?
494
28
			let mut current_initialized_rewards = InitializedRewardAmount::<T>::get();
495

            
496
			// Total number of contributors
497
28
			let mut total_contributors = TotalContributors::<T>::get();
498

            
499
28
			let incoming_rewards: BalanceOf<T> = rewards
500
28
				.iter()
501
82
				.fold(0u32.into(), |acc: BalanceOf<T>, (_, _, reward)| {
502
82
					acc + *reward
503
82
				});
504

            
505
			// Ensure we dont go over funds
506
28
			ensure!(
507
28
				current_initialized_rewards + incoming_rewards <= Self::pot(),
508
1
				Error::<T>::BatchBeyondFundPot
509
			);
510

            
511
108
			for (relay_account, native_account, reward) in &rewards {
512
81
				if ClaimedRelayChainIds::<T>::get(relay_account).is_some()
513
81
					|| UnassociatedContributions::<T>::get(relay_account).is_some()
514
				{
515
					// Dont fail as this is supposed to be called with batch calls and we
516
					// dont want to stall the rest of the contributions
517
					Self::deposit_event(Event::InitializedAlreadyInitializedAccount(
518
						relay_account.clone(),
519
						native_account.clone(),
520
						*reward,
521
					));
522
					continue;
523
81
				}
524

            
525
81
				if *reward < T::MinimumReward::get() {
526
					// Don't fail as this is supposed to be called with batch calls and we
527
					// dont want to stall the rest of the contributions
528
					Self::deposit_event(Event::InitializedAccountWithNotEnoughContribution(
529
						relay_account.clone(),
530
						native_account.clone(),
531
						*reward,
532
					));
533
					continue;
534
81
				}
535

            
536
				// If we have a native_account, we make the payment
537
81
				let initial_payment = if let Some(native_account) = native_account {
538
47
					let first_payment = T::InitializationPayment::get() * (*reward);
539
47
					T::RewardCurrency::transfer(
540
47
						&PALLET_ID.into_account_truncating(),
541
47
						native_account,
542
47
						first_payment,
543
47
						AllowDeath,
544
					)?;
545
47
					Self::deposit_event(Event::InitialPaymentMade(
546
47
						native_account.clone(),
547
47
						first_payment,
548
47
					));
549
47
					first_payment
550
				} else {
551
34
					0u32.into()
552
				};
553

            
554
				// Calculate the reward info to store after the initial payment has been made.
555
81
				let mut reward_info = RewardInfo {
556
81
					total_reward: *reward,
557
81
					claimed_reward: initial_payment,
558
81
					contributed_relay_addresses: vec![relay_account.clone()],
559
81
				};
560

            
561
81
				current_initialized_rewards += *reward - initial_payment;
562
81
				total_contributors += 1;
563

            
564
81
				if let Some(native_account) = native_account {
565
6
					if let Some(mut inserted_reward_info) =
566
47
						AccountsPayable::<T>::get(native_account)
567
6
					{
568
6
						inserted_reward_info
569
6
							.contributed_relay_addresses
570
6
							.append(&mut reward_info.contributed_relay_addresses);
571
6
						// the native account has already some rewards in, we add the new ones
572
6
						AccountsPayable::<T>::insert(
573
6
							native_account,
574
6
							RewardInfo {
575
6
								total_reward: inserted_reward_info.total_reward
576
6
									+ reward_info.total_reward,
577
6
								claimed_reward: inserted_reward_info.claimed_reward
578
6
									+ reward_info.claimed_reward,
579
6
								contributed_relay_addresses: inserted_reward_info
580
6
									.contributed_relay_addresses,
581
6
							},
582
6
						);
583
41
					} else {
584
41
						// First reward association
585
41
						AccountsPayable::<T>::insert(native_account, reward_info);
586
41
					}
587
47
					ClaimedRelayChainIds::<T>::insert(relay_account, ());
588
34
				} else {
589
34
					UnassociatedContributions::<T>::insert(relay_account, reward_info);
590
34
				}
591
			}
592
27
			InitializedRewardAmount::<T>::put(current_initialized_rewards);
593
27
			TotalContributors::<T>::put(total_contributors);
594

            
595
27
			Ok(Default::default())
596
31
		}
597
	}
598

            
599
	#[pallet::error]
600
	pub enum Error<T> {
601
		/// User trying to associate a native identity with a relay chain identity for posterior
602
		/// reward claiming provided an already associated relay chain identity
603
		AlreadyAssociated,
604
		/// Trying to introduce a batch that goes beyond the limits of the funds
605
		BatchBeyondFundPot,
606
		/// First claim already done
607
		FirstClaimAlreadyDone,
608
		/// The contribution is not high enough to be eligible for rewards
609
		RewardNotHighEnough,
610
		/// User trying to associate a native identity with a relay chain identity for posterior
611
		/// reward claiming provided a wrong signature
612
		InvalidClaimSignature,
613
		/// User trying to claim the first free reward provided the wrong signature
614
		InvalidFreeClaimSignature,
615
		/// User trying to claim an award did not have an claim associated with it. This may mean
616
		/// they did not contribute to the crowdloan, or they have not yet associated a native id
617
		/// with their contribution
618
		NoAssociatedClaim,
619
		/// User trying to claim rewards has already claimed all rewards associated with its
620
		/// identity and contribution
621
		RewardsAlreadyClaimed,
622
		/// Reward vec has already been initialized
623
		RewardVecAlreadyInitialized,
624
		/// Reward vec has not yet been fully initialized
625
		RewardVecNotFullyInitializedYet,
626
		/// Rewards should match funds of the pallet
627
		RewardsDoNotMatchFund,
628
		/// Initialize_reward_vec received too many contributors
629
		TooManyContributors,
630
		/// Provided vesting period is not valid
631
		VestingPeriodNonValid,
632
		/// User provided a signature from a non-contributor relay account
633
		NonContributedAddressProvided,
634
		/// User submitted an unsifficient number of proofs to change the reward address
635
		InsufficientNumberOfValidProofs,
636
	}
637

            
638
	#[pallet::storage]
639
	#[pallet::getter(fn accounts_payable)]
640
	pub type AccountsPayable<T: Config> =
641
		StorageMap<_, Blake2_128Concat, T::AccountId, RewardInfo<T>>;
642
	#[pallet::storage]
643
	#[pallet::getter(fn claimed_relay_chain_ids)]
644
	pub type ClaimedRelayChainIds<T: Config> =
645
		StorageMap<_, Blake2_128Concat, T::RelayChainAccountId, ()>;
646
	#[pallet::storage]
647
	#[pallet::getter(fn unassociated_contributions)]
648
	pub type UnassociatedContributions<T: Config> =
649
		StorageMap<_, Blake2_128Concat, T::RelayChainAccountId, RewardInfo<T>>;
650
	#[pallet::storage]
651
	#[pallet::getter(fn initialized)]
652
	pub type Initialized<T: Config> = StorageValue<_, bool, ValueQuery, T::Initialized>;
653

            
654
	#[pallet::storage]
655
	#[pallet::storage_prefix = "InitRelayBlock"]
656
	#[pallet::getter(fn init_vesting_block)]
657
	/// Vesting block height at the initialization of the pallet
658
	pub(crate) type InitVestingBlock<T: Config> =
659
		StorageValue<_, T::VestingBlockNumber, ValueQuery>;
660

            
661
	#[pallet::storage]
662
	#[pallet::storage_prefix = "EndRelayBlock"]
663
	#[pallet::getter(fn end_vesting_block)]
664
	/// Vesting block height at the initialization of the pallet
665
	pub(crate) type EndVestingBlock<T: Config> = StorageValue<_, T::VestingBlockNumber, ValueQuery>;
666

            
667
	#[pallet::storage]
668
	#[pallet::getter(fn init_reward_amount)]
669
	/// Total initialized amount so far. We store this to make pallet funds == contributors reward
670
	/// check easier and more efficient
671
	pub(crate) type InitializedRewardAmount<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
672

            
673
	#[pallet::storage]
674
	#[pallet::getter(fn total_contributors)]
675
	/// Total number of contributors to aid hinting benchmarking
676
	pub(crate) type TotalContributors<T: Config> = StorageValue<_, u32, ValueQuery>;
677

            
678
	#[pallet::genesis_config]
679
	pub struct GenesisConfig<T: Config> {
680
		/// List of contributors with their relay account, optional native account, and reward amount
681
		pub funded_accounts: Vec<ContributorData<T>>,
682
		/// Initial vesting block number
683
		pub init_vesting_block: T::VestingBlockNumber,
684
		/// End vesting block number
685
		pub end_vesting_block: T::VestingBlockNumber,
686
	}
687

            
688
	impl<T: Config> Default for GenesisConfig<T> {
689
3
		fn default() -> Self {
690
3
			Self {
691
3
				funded_accounts: vec![],
692
3
				init_vesting_block: Default::default(),
693
3
				end_vesting_block: Default::default(),
694
3
			}
695
3
		}
696
	}
697

            
698
	#[pallet::genesis_build]
699
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
700
41
		fn build(&self) {
701
			// Set the vesting blocks
702
41
			InitVestingBlock::<T>::put(self.init_vesting_block.clone());
703
41
			EndVestingBlock::<T>::put(self.end_vesting_block.clone());
704

            
705
41
			let mut total_contributors = 0u32;
706
41
			let mut total_rewards = BalanceOf::<T>::default();
707

            
708
			// Process each funded account
709
52
			for (relay_account, native_account_opt, reward) in &self.funded_accounts {
710
				// Skip if reward is less than minimum
711
11
				if *reward < T::MinimumReward::get() {
712
					continue;
713
11
				}
714

            
715
				// Calculate the initial payment
716
11
				let initial_payment = if native_account_opt.is_some() {
717
11
					let payment = T::InitializationPayment::get() * (*reward);
718
					// Transfer the initial payment to the native account
719
11
					if let Some(native_account) = native_account_opt {
720
11
						let _ = T::RewardCurrency::transfer(
721
11
							&PALLET_ID.into_account_truncating(),
722
11
							native_account,
723
11
							payment,
724
11
							AllowDeath,
725
11
						);
726
11
					}
727
11
					payment
728
				} else {
729
					BalanceOf::<T>::default()
730
				};
731

            
732
				// Create reward info
733
11
				let reward_info = RewardInfo {
734
11
					total_reward: *reward,
735
11
					claimed_reward: initial_payment,
736
11
					contributed_relay_addresses: vec![relay_account.clone()],
737
11
				};
738

            
739
				// Store the reward info based on whether account is associated
740
11
				if let Some(native_account) = native_account_opt {
741
11
					AccountsPayable::<T>::insert(native_account, &reward_info);
742
11
					ClaimedRelayChainIds::<T>::insert(relay_account, ());
743
11
				} else {
744
					UnassociatedContributions::<T>::insert(relay_account, &reward_info);
745
				}
746

            
747
11
				total_contributors += 1;
748
11
				total_rewards += *reward - initial_payment;
749
			}
750

            
751
			// Update total contributors and initialized reward amount
752
41
			TotalContributors::<T>::put(total_contributors);
753
41
			InitializedRewardAmount::<T>::put(total_rewards);
754

            
755
			// Mark as initialized only if there are funded accounts
756
41
			if !self.funded_accounts.is_empty() {
757
10
				<Initialized<T>>::put(true);
758
31
			}
759
41
		}
760
	}
761

            
762
	#[pallet::event]
763
	#[pallet::generate_deposit(fn deposit_event)]
764
	pub enum Event<T: Config> {
765
		/// The initial payment of InitializationPayment % was paid
766
		InitialPaymentMade(T::AccountId, BalanceOf<T>),
767
		/// Someone has proven they made a contribution and associated a native identity with it.
768
		/// Data is the relay account,  native account and the total amount of _rewards_ that will be paid
769
		NativeIdentityAssociated(T::RelayChainAccountId, T::AccountId, BalanceOf<T>),
770
		/// A contributor has claimed some rewards.
771
		/// Data is the account getting paid and the amount of rewards paid.
772
		RewardsPaid(T::AccountId, BalanceOf<T>),
773
		/// A contributor has updated the reward address.
774
		RewardAddressUpdated(T::AccountId, T::AccountId),
775
		/// When initializing the reward vec an already initialized account was found
776
		InitializedAlreadyInitializedAccount(
777
			T::RelayChainAccountId,
778
			Option<T::AccountId>,
779
			BalanceOf<T>,
780
		),
781
		/// When initializing the reward vec an already initialized account was found
782
		InitializedAccountWithNotEnoughContribution(
783
			T::RelayChainAccountId,
784
			Option<T::AccountId>,
785
			BalanceOf<T>,
786
		),
787
	}
788
}