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

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

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

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

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

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

            
124
		type WeightInfo: WeightInfo;
125
	}
126

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

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

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

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

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

            
173
			// The less costly checks will go first
174

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

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

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

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

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

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

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

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

            
220
2
			reward_info.claimed_reward = first_payment;
221
2

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

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

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

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

            
238
2
			Ok(Default::default())
239
		}
240

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

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

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

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

            
276
2
			Self::verify_signatures(proofs, reward_info.clone(), payload)?;
277

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

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

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

            
290
1
			Ok(Default::default())
291
		}
292

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

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

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

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

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

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

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

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

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

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

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

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

            
372
4
			// Update new rewarded acount
373
4
			AccountsPayable::<T>::insert(&new_reward_account, &info);
374
4

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

            
378
4
			Ok(Default::default())
379
		}
380
	}
381

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

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

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

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

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

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

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

            
456
21
			let current_initialized_rewards = InitializedRewardAmount::<T>::get();
457
21

            
458
21
			let reward_difference = Self::pot().saturating_sub(current_initialized_rewards);
459
21

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

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

            
476
20
			EndVestingBlock::<T>::put(lease_ending_block);
477
20

            
478
20
			<Initialized<T>>::put(true);
479
20

            
480
20
			Ok(Default::default())
481
24
		}
482

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

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

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

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

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

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

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

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

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

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

            
563
81
				current_initialized_rewards += *reward - initial_payment;
564
81
				total_contributors += 1;
565

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

            
597
27
			Ok(Default::default())
598
31
		}
599
	}
600

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
749
11
				total_contributors += 1;
750
11
				total_rewards += *reward - initial_payment;
751
			}
752

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

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

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