1
// Copyright (C) Parity Technologies (UK) Ltd.
2
// This file is part of Polkadot.
3

            
4
// Substrate 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
// Substrate 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 Substrate.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! Pallet to process purchase of DOTs.
18

            
19
use alloc::vec::Vec;
20
use codec::{Decode, Encode};
21
use frame_support::{
22
	pallet_prelude::*,
23
	traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, VestingSchedule},
24
};
25
use frame_system::pallet_prelude::*;
26
pub use pallet::*;
27
use scale_info::TypeInfo;
28
use sp_core::sr25519;
29
use sp_runtime::{
30
	traits::{CheckedAdd, Saturating, Verify, Zero},
31
	AnySignature, DispatchError, DispatchResult, Permill, RuntimeDebug,
32
};
33

            
34
type BalanceOf<T> =
35
	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
36

            
37
/// The kind of statement an account needs to make for a claim to be valid.
38
#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)]
39
pub enum AccountValidity {
40
	/// Account is not valid.
41
	Invalid,
42
	/// Account has initiated the account creation process.
43
	Initiated,
44
	/// Account is pending validation.
45
	Pending,
46
	/// Account is valid with a low contribution amount.
47
	ValidLow,
48
	/// Account is valid with a high contribution amount.
49
	ValidHigh,
50
	/// Account has completed the purchase process.
51
	Completed,
52
}
53

            
54
impl Default for AccountValidity {
55
	fn default() -> Self {
56
		AccountValidity::Invalid
57
	}
58
}
59

            
60
impl AccountValidity {
61
	fn is_valid(&self) -> bool {
62
		match self {
63
			Self::Invalid => false,
64
			Self::Initiated => false,
65
			Self::Pending => false,
66
			Self::ValidLow => true,
67
			Self::ValidHigh => true,
68
			Self::Completed => false,
69
		}
70
	}
71
}
72

            
73
/// All information about an account regarding the purchase of DOTs.
74
#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
75
pub struct AccountStatus<Balance> {
76
	/// The current validity status of the user. Will denote if the user has passed KYC,
77
	/// how much they are able to purchase, and when their purchase process has completed.
78
	validity: AccountValidity,
79
	/// The amount of free DOTs they have purchased.
80
	free_balance: Balance,
81
	/// The amount of locked DOTs they have purchased.
82
	locked_balance: Balance,
83
	/// Their sr25519/ed25519 signature verifying they have signed our required statement.
84
	signature: Vec<u8>,
85
	/// The percentage of VAT the purchaser is responsible for. This is already factored into
86
	/// account balance.
87
	vat: Permill,
88
}
89

            
90
#[frame_support::pallet]
91
pub mod pallet {
92
	use super::*;
93

            
94
	#[pallet::pallet]
95
	#[pallet::without_storage_info]
96
	pub struct Pallet<T>(_);
97

            
98
	#[pallet::config]
99
	pub trait Config: frame_system::Config {
100
		/// The overarching event type.
101
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
102

            
103
		/// Balances Pallet
104
		type Currency: Currency<Self::AccountId>;
105

            
106
		/// Vesting Pallet
107
		type VestingSchedule: VestingSchedule<
108
			Self::AccountId,
109
			Moment = BlockNumberFor<Self>,
110
			Currency = Self::Currency,
111
		>;
112

            
113
		/// The origin allowed to set account status.
114
		type ValidityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
115

            
116
		/// The origin allowed to make configurations to the pallet.
117
		type ConfigurationOrigin: EnsureOrigin<Self::RuntimeOrigin>;
118

            
119
		/// The maximum statement length for the statement users to sign when creating an account.
120
		#[pallet::constant]
121
		type MaxStatementLength: Get<u32>;
122

            
123
		/// The amount of purchased locked DOTs that we will unlock for basic actions on the chain.
124
		#[pallet::constant]
125
		type UnlockedProportion: Get<Permill>;
126

            
127
		/// The maximum amount of locked DOTs that we will unlock.
128
		#[pallet::constant]
129
		type MaxUnlocked: Get<BalanceOf<Self>>;
130
	}
131

            
132
	#[pallet::event]
133
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
134
	pub enum Event<T: Config> {
135
		/// A new account was created.
136
		AccountCreated { who: T::AccountId },
137
		/// Someone's account validity was updated.
138
		ValidityUpdated { who: T::AccountId, validity: AccountValidity },
139
		/// Someone's purchase balance was updated.
140
		BalanceUpdated { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> },
141
		/// A payout was made to a purchaser.
142
		PaymentComplete { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> },
143
		/// A new payment account was set.
144
		PaymentAccountSet { who: T::AccountId },
145
		/// A new statement was set.
146
		StatementUpdated,
147
		/// A new statement was set. `[block_number]`
148
		UnlockBlockUpdated { block_number: BlockNumberFor<T> },
149
	}
150

            
151
	#[pallet::error]
152
	pub enum Error<T> {
153
		/// Account is not currently valid to use.
154
		InvalidAccount,
155
		/// Account used in the purchase already exists.
156
		ExistingAccount,
157
		/// Provided signature is invalid
158
		InvalidSignature,
159
		/// Account has already completed the purchase process.
160
		AlreadyCompleted,
161
		/// An overflow occurred when doing calculations.
162
		Overflow,
163
		/// The statement is too long to be stored on chain.
164
		InvalidStatement,
165
		/// The unlock block is in the past!
166
		InvalidUnlockBlock,
167
		/// Vesting schedule already exists for this account.
168
		VestingScheduleExists,
169
	}
170

            
171
	// A map of all participants in the DOT purchase process.
172
	#[pallet::storage]
173
	pub(super) type Accounts<T: Config> =
174
		StorageMap<_, Blake2_128Concat, T::AccountId, AccountStatus<BalanceOf<T>>, ValueQuery>;
175

            
176
	// The account that will be used to payout participants of the DOT purchase process.
177
	#[pallet::storage]
178
	pub(super) type PaymentAccount<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
179

            
180
	// The statement purchasers will need to sign to participate.
181
	#[pallet::storage]
182
	pub(super) type Statement<T> = StorageValue<_, Vec<u8>, ValueQuery>;
183

            
184
	// The block where all locked dots will unlock.
185
	#[pallet::storage]
186
	pub(super) type UnlockBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
187

            
188
	#[pallet::hooks]
189
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
190

            
191
	#[pallet::call]
192
	impl<T: Config> Pallet<T> {
193
		/// Create a new account. Proof of existence through a valid signed message.
194
		///
195
		/// We check that the account does not exist at this stage.
196
		///
197
		/// Origin must match the `ValidityOrigin`.
198
		#[pallet::call_index(0)]
199
		#[pallet::weight(Weight::from_parts(200_000_000, 0) + T::DbWeight::get().reads_writes(4, 1))]
200
		pub fn create_account(
201
			origin: OriginFor<T>,
202
			who: T::AccountId,
203
			signature: Vec<u8>,
204
		) -> DispatchResult {
205
			T::ValidityOrigin::ensure_origin(origin)?;
206
			// Account is already being tracked by the pallet.
207
			ensure!(!Accounts::<T>::contains_key(&who), Error::<T>::ExistingAccount);
208
			// Account should not have a vesting schedule.
209
			ensure!(
210
				T::VestingSchedule::vesting_balance(&who).is_none(),
211
				Error::<T>::VestingScheduleExists
212
			);
213

            
214
			// Verify the signature provided is valid for the statement.
215
			Self::verify_signature(&who, &signature)?;
216

            
217
			// Create a new pending account.
218
			let status = AccountStatus {
219
				validity: AccountValidity::Initiated,
220
				signature,
221
				free_balance: Zero::zero(),
222
				locked_balance: Zero::zero(),
223
				vat: Permill::zero(),
224
			};
225
			Accounts::<T>::insert(&who, status);
226
			Self::deposit_event(Event::<T>::AccountCreated { who });
227
			Ok(())
228
		}
229

            
230
		/// Update the validity status of an existing account. If set to completed, the account
231
		/// will no longer be able to continue through the crowdfund process.
232
		///
233
		/// We check that the account exists at this stage, but has not completed the process.
234
		///
235
		/// Origin must match the `ValidityOrigin`.
236
		#[pallet::call_index(1)]
237
		#[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
238
		pub fn update_validity_status(
239
			origin: OriginFor<T>,
240
			who: T::AccountId,
241
			validity: AccountValidity,
242
		) -> DispatchResult {
243
			T::ValidityOrigin::ensure_origin(origin)?;
244
			ensure!(Accounts::<T>::contains_key(&who), Error::<T>::InvalidAccount);
245
			Accounts::<T>::try_mutate(
246
				&who,
247
				|status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult {
248
					ensure!(
249
						status.validity != AccountValidity::Completed,
250
						Error::<T>::AlreadyCompleted
251
					);
252
					status.validity = validity;
253
					Ok(())
254
				},
255
			)?;
256
			Self::deposit_event(Event::<T>::ValidityUpdated { who, validity });
257
			Ok(())
258
		}
259

            
260
		/// Update the balance of a valid account.
261
		///
262
		/// We check that the account is valid for a balance transfer at this point.
263
		///
264
		/// Origin must match the `ValidityOrigin`.
265
		#[pallet::call_index(2)]
266
		#[pallet::weight(T::DbWeight::get().reads_writes(2, 1))]
267
		pub fn update_balance(
268
			origin: OriginFor<T>,
269
			who: T::AccountId,
270
			free_balance: BalanceOf<T>,
271
			locked_balance: BalanceOf<T>,
272
			vat: Permill,
273
		) -> DispatchResult {
274
			T::ValidityOrigin::ensure_origin(origin)?;
275

            
276
			Accounts::<T>::try_mutate(
277
				&who,
278
				|status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult {
279
					// Account has a valid status (not Invalid, Pending, or Completed)...
280
					ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount);
281

            
282
					free_balance.checked_add(&locked_balance).ok_or(Error::<T>::Overflow)?;
283
					status.free_balance = free_balance;
284
					status.locked_balance = locked_balance;
285
					status.vat = vat;
286
					Ok(())
287
				},
288
			)?;
289
			Self::deposit_event(Event::<T>::BalanceUpdated {
290
				who,
291
				free: free_balance,
292
				locked: locked_balance,
293
			});
294
			Ok(())
295
		}
296

            
297
		/// Pay the user and complete the purchase process.
298
		///
299
		/// We reverify all assumptions about the state of an account, and complete the process.
300
		///
301
		/// Origin must match the configured `PaymentAccount` (if it is not configured then this
302
		/// will always fail with `BadOrigin`).
303
		#[pallet::call_index(3)]
304
		#[pallet::weight(T::DbWeight::get().reads_writes(4, 2))]
305
		pub fn payout(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
306
			// Payments must be made directly by the `PaymentAccount`.
307
			let payment_account = ensure_signed(origin)?;
308
			let test_against = PaymentAccount::<T>::get().ok_or(DispatchError::BadOrigin)?;
309
			ensure!(payment_account == test_against, DispatchError::BadOrigin);
310

            
311
			// Account should not have a vesting schedule.
312
			ensure!(
313
				T::VestingSchedule::vesting_balance(&who).is_none(),
314
				Error::<T>::VestingScheduleExists
315
			);
316

            
317
			Accounts::<T>::try_mutate(
318
				&who,
319
				|status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult {
320
					// Account has a valid status (not Invalid, Pending, or Completed)...
321
					ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount);
322

            
323
					// Transfer funds from the payment account into the purchasing user.
324
					let total_balance = status
325
						.free_balance
326
						.checked_add(&status.locked_balance)
327
						.ok_or(Error::<T>::Overflow)?;
328
					T::Currency::transfer(
329
						&payment_account,
330
						&who,
331
						total_balance,
332
						ExistenceRequirement::AllowDeath,
333
					)?;
334

            
335
					if !status.locked_balance.is_zero() {
336
						let unlock_block = UnlockBlock::<T>::get();
337
						// We allow some configurable portion of the purchased locked DOTs to be
338
						// unlocked for basic usage.
339
						let unlocked = (T::UnlockedProportion::get() * status.locked_balance)
340
							.min(T::MaxUnlocked::get());
341
						let locked = status.locked_balance.saturating_sub(unlocked);
342
						// We checked that this account has no existing vesting schedule. So this
343
						// function should never fail, however if it does, not much we can do about
344
						// it at this point.
345
						let _ = T::VestingSchedule::add_vesting_schedule(
346
							// Apply vesting schedule to this user
347
							&who,
348
							// For this much amount
349
							locked,
350
							// Unlocking the full amount after one block
351
							locked,
352
							// When everything unlocks
353
							unlock_block,
354
						);
355
					}
356

            
357
					// Setting the user account to `Completed` ends the purchase process for this
358
					// user.
359
					status.validity = AccountValidity::Completed;
360
					Self::deposit_event(Event::<T>::PaymentComplete {
361
						who: who.clone(),
362
						free: status.free_balance,
363
						locked: status.locked_balance,
364
					});
365
					Ok(())
366
				},
367
			)?;
368
			Ok(())
369
		}
370

            
371
		/* Configuration Operations */
372

            
373
		/// Set the account that will be used to payout users in the DOT purchase process.
374
		///
375
		/// Origin must match the `ConfigurationOrigin`
376
		#[pallet::call_index(4)]
377
		#[pallet::weight(T::DbWeight::get().writes(1))]
378
		pub fn set_payment_account(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
379
			T::ConfigurationOrigin::ensure_origin(origin)?;
380
			// Possibly this is worse than having the caller account be the payment account?
381
			PaymentAccount::<T>::put(who.clone());
382
			Self::deposit_event(Event::<T>::PaymentAccountSet { who });
383
			Ok(())
384
		}
385

            
386
		/// Set the statement that must be signed for a user to participate on the DOT sale.
387
		///
388
		/// Origin must match the `ConfigurationOrigin`
389
		#[pallet::call_index(5)]
390
		#[pallet::weight(T::DbWeight::get().writes(1))]
391
		pub fn set_statement(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult {
392
			T::ConfigurationOrigin::ensure_origin(origin)?;
393
			ensure!(
394
				(statement.len() as u32) < T::MaxStatementLength::get(),
395
				Error::<T>::InvalidStatement
396
			);
397
			// Possibly this is worse than having the caller account be the payment account?
398
			Statement::<T>::set(statement);
399
			Self::deposit_event(Event::<T>::StatementUpdated);
400
			Ok(())
401
		}
402

            
403
		/// Set the block where locked DOTs will become unlocked.
404
		///
405
		/// Origin must match the `ConfigurationOrigin`
406
		#[pallet::call_index(6)]
407
		#[pallet::weight(T::DbWeight::get().writes(1))]
408
		pub fn set_unlock_block(
409
			origin: OriginFor<T>,
410
			unlock_block: BlockNumberFor<T>,
411
		) -> DispatchResult {
412
			T::ConfigurationOrigin::ensure_origin(origin)?;
413
			ensure!(
414
				unlock_block > frame_system::Pallet::<T>::block_number(),
415
				Error::<T>::InvalidUnlockBlock
416
			);
417
			// Possibly this is worse than having the caller account be the payment account?
418
			UnlockBlock::<T>::set(unlock_block);
419
			Self::deposit_event(Event::<T>::UnlockBlockUpdated { block_number: unlock_block });
420
			Ok(())
421
		}
422
	}
423
}
424

            
425
impl<T: Config> Pallet<T> {
426
	fn verify_signature(who: &T::AccountId, signature: &[u8]) -> Result<(), DispatchError> {
427
		// sr25519 always expects a 64 byte signature.
428
		let signature: AnySignature = sr25519::Signature::try_from(signature)
429
			.map_err(|_| Error::<T>::InvalidSignature)?
430
			.into();
431

            
432
		// In Polkadot, the AccountId is always the same as the 32 byte public key.
433
		let account_bytes: [u8; 32] = account_to_bytes(who)?;
434
		let public_key = sr25519::Public::from_raw(account_bytes);
435

            
436
		let message = Statement::<T>::get();
437

            
438
		// Check if everything is good or not.
439
		match signature.verify(message.as_slice(), &public_key) {
440
			true => Ok(()),
441
			false => Err(Error::<T>::InvalidSignature)?,
442
		}
443
	}
444
}
445

            
446
// This function converts a 32 byte AccountId to its byte-array equivalent form.
447
fn account_to_bytes<AccountId>(account: &AccountId) -> Result<[u8; 32], DispatchError>
448
where
449
	AccountId: Encode,
450
{
451
	let account_vec = account.encode();
452
	ensure!(account_vec.len() == 32, "AccountId must be 32 bytes.");
453
	let mut bytes = [0u8; 32];
454
	bytes.copy_from_slice(&account_vec);
455
	Ok(bytes)
456
}
457

            
458
/// WARNING: Executing this function will clear all storage used by this pallet.
459
/// Be sure this is what you want...
460
pub fn remove_pallet<T>() -> frame_support::weights::Weight
461
where
462
	T: frame_system::Config,
463
{
464
	#[allow(deprecated)]
465
	use frame_support::migration::remove_storage_prefix;
466
	#[allow(deprecated)]
467
	remove_storage_prefix(b"Purchase", b"Accounts", b"");
468
	#[allow(deprecated)]
469
	remove_storage_prefix(b"Purchase", b"PaymentAccount", b"");
470
	#[allow(deprecated)]
471
	remove_storage_prefix(b"Purchase", b"Statement", b"");
472
	#[allow(deprecated)]
473
	remove_storage_prefix(b"Purchase", b"UnlockBlock", b"");
474

            
475
	<T as frame_system::Config>::BlockWeights::get().max_block
476
}
477

            
478
#[cfg(test)]
479
mod tests {
480
	use super::*;
481

            
482
	use sp_core::{crypto::AccountId32, ed25519, Pair, Public, H256};
483
	// The testing primitives are very useful for avoiding having to work with signatures
484
	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
485
	use crate::purchase;
486
	use frame_support::{
487
		assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types,
488
		traits::{Currency, WithdrawReasons},
489
	};
490
	use sp_runtime::{
491
		traits::{BlakeTwo256, Dispatchable, IdentifyAccount, Identity, IdentityLookup, Verify},
492
		ArithmeticError, BuildStorage,
493
		DispatchError::BadOrigin,
494
		MultiSignature,
495
	};
496

            
497
	type Block = frame_system::mocking::MockBlock<Test>;
498

            
499
	frame_support::construct_runtime!(
500
		pub enum Test
501
		{
502
			System: frame_system,
503
			Balances: pallet_balances,
504
			Vesting: pallet_vesting,
505
			Purchase: purchase,
506
		}
507
	);
508

            
509
	type AccountId = AccountId32;
510

            
511
	#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
512
	impl frame_system::Config for Test {
513
		type BaseCallFilter = frame_support::traits::Everything;
514
		type BlockWeights = ();
515
		type BlockLength = ();
516
		type DbWeight = ();
517
		type RuntimeOrigin = RuntimeOrigin;
518
		type RuntimeCall = RuntimeCall;
519
		type Nonce = u64;
520
		type Hash = H256;
521
		type Hashing = BlakeTwo256;
522
		type AccountId = AccountId;
523
		type Lookup = IdentityLookup<AccountId>;
524
		type Block = Block;
525
		type RuntimeEvent = RuntimeEvent;
526
		type Version = ();
527
		type PalletInfo = PalletInfo;
528
		type AccountData = pallet_balances::AccountData<u64>;
529
		type OnNewAccount = ();
530
		type OnKilledAccount = ();
531
		type SystemWeightInfo = ();
532
		type SS58Prefix = ();
533
		type OnSetCode = ();
534
		type MaxConsumers = frame_support::traits::ConstU32<16>;
535
	}
536

            
537
	#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
538
	impl pallet_balances::Config for Test {
539
		type AccountStore = System;
540
	}
541

            
542
	parameter_types! {
543
		pub const MinVestedTransfer: u64 = 1;
544
		pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons =
545
			WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE);
546
	}
547

            
548
	impl pallet_vesting::Config for Test {
549
		type RuntimeEvent = RuntimeEvent;
550
		type Currency = Balances;
551
		type BlockNumberToBalance = Identity;
552
		type MinVestedTransfer = MinVestedTransfer;
553
		type WeightInfo = ();
554
		type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons;
555
		type BlockNumberProvider = System;
556
		const MAX_VESTING_SCHEDULES: u32 = 28;
557
	}
558

            
559
	parameter_types! {
560
		pub const MaxStatementLength: u32 =  1_000;
561
		pub const UnlockedProportion: Permill = Permill::from_percent(10);
562
		pub const MaxUnlocked: u64 = 10;
563
	}
564

            
565
	ord_parameter_types! {
566
		pub const ValidityOrigin: AccountId = AccountId32::from([0u8; 32]);
567
		pub const PaymentOrigin: AccountId = AccountId32::from([1u8; 32]);
568
		pub const ConfigurationOrigin: AccountId = AccountId32::from([2u8; 32]);
569
	}
570

            
571
	impl Config for Test {
572
		type RuntimeEvent = RuntimeEvent;
573
		type Currency = Balances;
574
		type VestingSchedule = Vesting;
575
		type ValidityOrigin = frame_system::EnsureSignedBy<ValidityOrigin, AccountId>;
576
		type ConfigurationOrigin = frame_system::EnsureSignedBy<ConfigurationOrigin, AccountId>;
577
		type MaxStatementLength = MaxStatementLength;
578
		type UnlockedProportion = UnlockedProportion;
579
		type MaxUnlocked = MaxUnlocked;
580
	}
581

            
582
	// This function basically just builds a genesis storage key/value store according to
583
	// our desired mockup. It also executes our `setup` function which sets up this pallet for use.
584
	pub fn new_test_ext() -> sp_io::TestExternalities {
585
		let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
586
		let mut ext = sp_io::TestExternalities::new(t);
587
		ext.execute_with(|| setup());
588
		ext
589
	}
590

            
591
	fn setup() {
592
		let statement = b"Hello, World".to_vec();
593
		let unlock_block = 100;
594
		Purchase::set_statement(RuntimeOrigin::signed(configuration_origin()), statement).unwrap();
595
		Purchase::set_unlock_block(RuntimeOrigin::signed(configuration_origin()), unlock_block)
596
			.unwrap();
597
		Purchase::set_payment_account(
598
			RuntimeOrigin::signed(configuration_origin()),
599
			payment_account(),
600
		)
601
		.unwrap();
602
		Balances::make_free_balance_be(&payment_account(), 100_000);
603
	}
604

            
605
	type AccountPublic = <MultiSignature as Verify>::Signer;
606

            
607
	/// Helper function to generate a crypto pair from seed
608
	fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
609
		TPublic::Pair::from_string(&format!("//{}", seed), None)
610
			.expect("static values are valid; qed")
611
			.public()
612
	}
613

            
614
	/// Helper function to generate an account ID from seed
615
	fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
616
	where
617
		AccountPublic: From<<TPublic::Pair as Pair>::Public>,
618
	{
619
		AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
620
	}
621

            
622
	fn alice() -> AccountId {
623
		get_account_id_from_seed::<sr25519::Public>("Alice")
624
	}
625

            
626
	fn alice_ed25519() -> AccountId {
627
		get_account_id_from_seed::<ed25519::Public>("Alice")
628
	}
629

            
630
	fn bob() -> AccountId {
631
		get_account_id_from_seed::<sr25519::Public>("Bob")
632
	}
633

            
634
	fn alice_signature() -> [u8; 64] {
635
		// echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold
636
		// race lonely fit walk//Alice"
637
		hex_literal::hex!("20e0faffdf4dfe939f2faa560f73b1d01cde8472e2b690b7b40606a374244c3a2e9eb9c8107c10b605138374003af8819bd4387d7c24a66ee9253c2e688ab881")
638
	}
639

            
640
	fn bob_signature() -> [u8; 64] {
641
		// echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold
642
		// race lonely fit walk//Bob"
643
		hex_literal::hex!("d6d460187ecf530f3ec2d6e3ac91b9d083c8fbd8f1112d92a82e4d84df552d18d338e6da8944eba6e84afaacf8a9850f54e7b53a84530d649be2e0119c7ce889")
644
	}
645

            
646
	fn alice_signature_ed25519() -> [u8; 64] {
647
		// echo -n "Hello, World" | subkey -e sign "bottom drive obey lake curtain smoke basket hold
648
		// race lonely fit walk//Alice"
649
		hex_literal::hex!("ee3f5a6cbfc12a8f00c18b811dc921b550ddf272354cda4b9a57b1d06213fcd8509f5af18425d39a279d13622f14806c3e978e2163981f2ec1c06e9628460b0e")
650
	}
651

            
652
	fn validity_origin() -> AccountId {
653
		ValidityOrigin::get()
654
	}
655

            
656
	fn configuration_origin() -> AccountId {
657
		ConfigurationOrigin::get()
658
	}
659

            
660
	fn payment_account() -> AccountId {
661
		[42u8; 32].into()
662
	}
663

            
664
	#[test]
665
	fn set_statement_works_and_handles_basic_errors() {
666
		new_test_ext().execute_with(|| {
667
			let statement = b"Test Set Statement".to_vec();
668
			// Invalid origin
669
			assert_noop!(
670
				Purchase::set_statement(RuntimeOrigin::signed(alice()), statement.clone()),
671
				BadOrigin,
672
			);
673
			// Too Long
674
			let long_statement = [0u8; 10_000].to_vec();
675
			assert_noop!(
676
				Purchase::set_statement(
677
					RuntimeOrigin::signed(configuration_origin()),
678
					long_statement
679
				),
680
				Error::<Test>::InvalidStatement,
681
			);
682
			// Just right...
683
			assert_ok!(Purchase::set_statement(
684
				RuntimeOrigin::signed(configuration_origin()),
685
				statement.clone()
686
			));
687
			assert_eq!(Statement::<Test>::get(), statement);
688
		});
689
	}
690

            
691
	#[test]
692
	fn set_unlock_block_works_and_handles_basic_errors() {
693
		new_test_ext().execute_with(|| {
694
			let unlock_block = 69;
695
			// Invalid origin
696
			assert_noop!(
697
				Purchase::set_unlock_block(RuntimeOrigin::signed(alice()), unlock_block),
698
				BadOrigin,
699
			);
700
			// Block Number in Past
701
			let bad_unlock_block = 50;
702
			System::set_block_number(bad_unlock_block);
703
			assert_noop!(
704
				Purchase::set_unlock_block(
705
					RuntimeOrigin::signed(configuration_origin()),
706
					bad_unlock_block
707
				),
708
				Error::<Test>::InvalidUnlockBlock,
709
			);
710
			// Just right...
711
			assert_ok!(Purchase::set_unlock_block(
712
				RuntimeOrigin::signed(configuration_origin()),
713
				unlock_block
714
			));
715
			assert_eq!(UnlockBlock::<Test>::get(), unlock_block);
716
		});
717
	}
718

            
719
	#[test]
720
	fn set_payment_account_works_and_handles_basic_errors() {
721
		new_test_ext().execute_with(|| {
722
			let payment_account: AccountId = [69u8; 32].into();
723
			// Invalid Origin
724
			assert_noop!(
725
				Purchase::set_payment_account(
726
					RuntimeOrigin::signed(alice()),
727
					payment_account.clone()
728
				),
729
				BadOrigin,
730
			);
731
			// Just right...
732
			assert_ok!(Purchase::set_payment_account(
733
				RuntimeOrigin::signed(configuration_origin()),
734
				payment_account.clone()
735
			));
736
			assert_eq!(PaymentAccount::<Test>::get(), Some(payment_account));
737
		});
738
	}
739

            
740
	#[test]
741
	fn signature_verification_works() {
742
		new_test_ext().execute_with(|| {
743
			assert_ok!(Purchase::verify_signature(&alice(), &alice_signature()));
744
			assert_ok!(Purchase::verify_signature(&alice_ed25519(), &alice_signature_ed25519()));
745
			assert_ok!(Purchase::verify_signature(&bob(), &bob_signature()));
746

            
747
			// Mixing and matching fails
748
			assert_noop!(
749
				Purchase::verify_signature(&alice(), &bob_signature()),
750
				Error::<Test>::InvalidSignature
751
			);
752
			assert_noop!(
753
				Purchase::verify_signature(&bob(), &alice_signature()),
754
				Error::<Test>::InvalidSignature
755
			);
756
		});
757
	}
758

            
759
	#[test]
760
	fn account_creation_works() {
761
		new_test_ext().execute_with(|| {
762
			assert!(!Accounts::<Test>::contains_key(alice()));
763
			assert_ok!(Purchase::create_account(
764
				RuntimeOrigin::signed(validity_origin()),
765
				alice(),
766
				alice_signature().to_vec(),
767
			));
768
			assert_eq!(
769
				Accounts::<Test>::get(alice()),
770
				AccountStatus {
771
					validity: AccountValidity::Initiated,
772
					free_balance: Zero::zero(),
773
					locked_balance: Zero::zero(),
774
					signature: alice_signature().to_vec(),
775
					vat: Permill::zero(),
776
				}
777
			);
778
		});
779
	}
780

            
781
	#[test]
782
	fn account_creation_handles_basic_errors() {
783
		new_test_ext().execute_with(|| {
784
			// Wrong Origin
785
			assert_noop!(
786
				Purchase::create_account(
787
					RuntimeOrigin::signed(alice()),
788
					alice(),
789
					alice_signature().to_vec()
790
				),
791
				BadOrigin,
792
			);
793

            
794
			// Wrong Account/Signature
795
			assert_noop!(
796
				Purchase::create_account(
797
					RuntimeOrigin::signed(validity_origin()),
798
					alice(),
799
					bob_signature().to_vec()
800
				),
801
				Error::<Test>::InvalidSignature,
802
			);
803

            
804
			// Account with vesting
805
			Balances::make_free_balance_be(&alice(), 100);
806
			assert_ok!(<Test as Config>::VestingSchedule::add_vesting_schedule(
807
				&alice(),
808
				100,
809
				1,
810
				50
811
			));
812
			assert_noop!(
813
				Purchase::create_account(
814
					RuntimeOrigin::signed(validity_origin()),
815
					alice(),
816
					alice_signature().to_vec()
817
				),
818
				Error::<Test>::VestingScheduleExists,
819
			);
820

            
821
			// Duplicate Purchasing Account
822
			assert_ok!(Purchase::create_account(
823
				RuntimeOrigin::signed(validity_origin()),
824
				bob(),
825
				bob_signature().to_vec()
826
			));
827
			assert_noop!(
828
				Purchase::create_account(
829
					RuntimeOrigin::signed(validity_origin()),
830
					bob(),
831
					bob_signature().to_vec()
832
				),
833
				Error::<Test>::ExistingAccount,
834
			);
835
		});
836
	}
837

            
838
	#[test]
839
	fn update_validity_status_works() {
840
		new_test_ext().execute_with(|| {
841
			// Alice account is created.
842
			assert_ok!(Purchase::create_account(
843
				RuntimeOrigin::signed(validity_origin()),
844
				alice(),
845
				alice_signature().to_vec(),
846
			));
847
			// She submits KYC, and we update the status to `Pending`.
848
			assert_ok!(Purchase::update_validity_status(
849
				RuntimeOrigin::signed(validity_origin()),
850
				alice(),
851
				AccountValidity::Pending,
852
			));
853
			// KYC comes back negative, so we mark the account invalid.
854
			assert_ok!(Purchase::update_validity_status(
855
				RuntimeOrigin::signed(validity_origin()),
856
				alice(),
857
				AccountValidity::Invalid,
858
			));
859
			assert_eq!(
860
				Accounts::<Test>::get(alice()),
861
				AccountStatus {
862
					validity: AccountValidity::Invalid,
863
					free_balance: Zero::zero(),
864
					locked_balance: Zero::zero(),
865
					signature: alice_signature().to_vec(),
866
					vat: Permill::zero(),
867
				}
868
			);
869
			// She fixes it, we mark her account valid.
870
			assert_ok!(Purchase::update_validity_status(
871
				RuntimeOrigin::signed(validity_origin()),
872
				alice(),
873
				AccountValidity::ValidLow,
874
			));
875
			assert_eq!(
876
				Accounts::<Test>::get(alice()),
877
				AccountStatus {
878
					validity: AccountValidity::ValidLow,
879
					free_balance: Zero::zero(),
880
					locked_balance: Zero::zero(),
881
					signature: alice_signature().to_vec(),
882
					vat: Permill::zero(),
883
				}
884
			);
885
		});
886
	}
887

            
888
	#[test]
889
	fn update_validity_status_handles_basic_errors() {
890
		new_test_ext().execute_with(|| {
891
			// Wrong Origin
892
			assert_noop!(
893
				Purchase::update_validity_status(
894
					RuntimeOrigin::signed(alice()),
895
					alice(),
896
					AccountValidity::Pending,
897
				),
898
				BadOrigin
899
			);
900
			// Inactive Account
901
			assert_noop!(
902
				Purchase::update_validity_status(
903
					RuntimeOrigin::signed(validity_origin()),
904
					alice(),
905
					AccountValidity::Pending,
906
				),
907
				Error::<Test>::InvalidAccount
908
			);
909
			// Already Completed
910
			assert_ok!(Purchase::create_account(
911
				RuntimeOrigin::signed(validity_origin()),
912
				alice(),
913
				alice_signature().to_vec(),
914
			));
915
			assert_ok!(Purchase::update_validity_status(
916
				RuntimeOrigin::signed(validity_origin()),
917
				alice(),
918
				AccountValidity::Completed,
919
			));
920
			assert_noop!(
921
				Purchase::update_validity_status(
922
					RuntimeOrigin::signed(validity_origin()),
923
					alice(),
924
					AccountValidity::Pending,
925
				),
926
				Error::<Test>::AlreadyCompleted
927
			);
928
		});
929
	}
930

            
931
	#[test]
932
	fn update_balance_works() {
933
		new_test_ext().execute_with(|| {
934
			// Alice account is created
935
			assert_ok!(Purchase::create_account(
936
				RuntimeOrigin::signed(validity_origin()),
937
				alice(),
938
				alice_signature().to_vec()
939
			));
940
			// And approved for basic contribution
941
			assert_ok!(Purchase::update_validity_status(
942
				RuntimeOrigin::signed(validity_origin()),
943
				alice(),
944
				AccountValidity::ValidLow,
945
			));
946
			// We set a balance on the user based on the payment they made. 50 locked, 50 free.
947
			assert_ok!(Purchase::update_balance(
948
				RuntimeOrigin::signed(validity_origin()),
949
				alice(),
950
				50,
951
				50,
952
				Permill::from_rational(77u32, 1000u32),
953
			));
954
			assert_eq!(
955
				Accounts::<Test>::get(alice()),
956
				AccountStatus {
957
					validity: AccountValidity::ValidLow,
958
					free_balance: 50,
959
					locked_balance: 50,
960
					signature: alice_signature().to_vec(),
961
					vat: Permill::from_parts(77000),
962
				}
963
			);
964
			// We can update the balance based on new information.
965
			assert_ok!(Purchase::update_balance(
966
				RuntimeOrigin::signed(validity_origin()),
967
				alice(),
968
				25,
969
				50,
970
				Permill::zero(),
971
			));
972
			assert_eq!(
973
				Accounts::<Test>::get(alice()),
974
				AccountStatus {
975
					validity: AccountValidity::ValidLow,
976
					free_balance: 25,
977
					locked_balance: 50,
978
					signature: alice_signature().to_vec(),
979
					vat: Permill::zero(),
980
				}
981
			);
982
		});
983
	}
984

            
985
	#[test]
986
	fn update_balance_handles_basic_errors() {
987
		new_test_ext().execute_with(|| {
988
			// Wrong Origin
989
			assert_noop!(
990
				Purchase::update_balance(
991
					RuntimeOrigin::signed(alice()),
992
					alice(),
993
					50,
994
					50,
995
					Permill::zero(),
996
				),
997
				BadOrigin
998
			);
999
			// Inactive Account
			assert_noop!(
				Purchase::update_balance(
					RuntimeOrigin::signed(validity_origin()),
					alice(),
					50,
					50,
					Permill::zero(),
				),
				Error::<Test>::InvalidAccount
			);
			// Overflow
			assert_noop!(
				Purchase::update_balance(
					RuntimeOrigin::signed(validity_origin()),
					alice(),
					u64::MAX,
					u64::MAX,
					Permill::zero(),
				),
				Error::<Test>::InvalidAccount
			);
		});
	}
	#[test]
	fn payout_works() {
		new_test_ext().execute_with(|| {
			// Alice and Bob accounts are created
			assert_ok!(Purchase::create_account(
				RuntimeOrigin::signed(validity_origin()),
				alice(),
				alice_signature().to_vec()
			));
			assert_ok!(Purchase::create_account(
				RuntimeOrigin::signed(validity_origin()),
				bob(),
				bob_signature().to_vec()
			));
			// Alice is approved for basic contribution
			assert_ok!(Purchase::update_validity_status(
				RuntimeOrigin::signed(validity_origin()),
				alice(),
				AccountValidity::ValidLow,
			));
			// Bob is approved for high contribution
			assert_ok!(Purchase::update_validity_status(
				RuntimeOrigin::signed(validity_origin()),
				bob(),
				AccountValidity::ValidHigh,
			));
			// We set a balance on the users based on the payment they made. 50 locked, 50 free.
			assert_ok!(Purchase::update_balance(
				RuntimeOrigin::signed(validity_origin()),
				alice(),
				50,
				50,
				Permill::zero(),
			));
			assert_ok!(Purchase::update_balance(
				RuntimeOrigin::signed(validity_origin()),
				bob(),
				100,
				150,
				Permill::zero(),
			));
			// Now we call payout for Alice and Bob.
			assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),));
			assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),));
			// Payment is made.
			assert_eq!(<Test as Config>::Currency::free_balance(&payment_account()), 99_650);
			assert_eq!(<Test as Config>::Currency::free_balance(&alice()), 100);
			// 10% of the 50 units is unlocked automatically for Alice
			assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), Some(45));
			assert_eq!(<Test as Config>::Currency::free_balance(&bob()), 250);
			// A max of 10 units is unlocked automatically for Bob
			assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), Some(140));
			// Status is completed.
			assert_eq!(
				Accounts::<Test>::get(alice()),
				AccountStatus {
					validity: AccountValidity::Completed,
					free_balance: 50,
					locked_balance: 50,
					signature: alice_signature().to_vec(),
					vat: Permill::zero(),
				}
			);
			assert_eq!(
				Accounts::<Test>::get(bob()),
				AccountStatus {
					validity: AccountValidity::Completed,
					free_balance: 100,
					locked_balance: 150,
					signature: bob_signature().to_vec(),
					vat: Permill::zero(),
				}
			);
			// Vesting lock is removed in whole on block 101 (100 blocks after block 1)
			System::set_block_number(100);
			let vest_call = RuntimeCall::Vesting(pallet_vesting::Call::<Test>::vest {});
			assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice())));
			assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob())));
			assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), Some(45));
			assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), Some(140));
			System::set_block_number(101);
			assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice())));
			assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob())));
			assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), None);
			assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), None);
		});
	}
	#[test]
	fn payout_handles_basic_errors() {
		new_test_ext().execute_with(|| {
			// Wrong Origin
			assert_noop!(Purchase::payout(RuntimeOrigin::signed(alice()), alice(),), BadOrigin);
			// Account with Existing Vesting Schedule
			Balances::make_free_balance_be(&bob(), 100);
			assert_ok!(
				<Test as Config>::VestingSchedule::add_vesting_schedule(&bob(), 100, 1, 50,)
			);
			assert_noop!(
				Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),),
				Error::<Test>::VestingScheduleExists
			);
			// Invalid Account (never created)
			assert_noop!(
				Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),),
				Error::<Test>::InvalidAccount
			);
			// Invalid Account (created, but not valid)
			assert_ok!(Purchase::create_account(
				RuntimeOrigin::signed(validity_origin()),
				alice(),
				alice_signature().to_vec()
			));
			assert_noop!(
				Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),),
				Error::<Test>::InvalidAccount
			);
			// Not enough funds in payment account
			assert_ok!(Purchase::update_validity_status(
				RuntimeOrigin::signed(validity_origin()),
				alice(),
				AccountValidity::ValidHigh,
			));
			assert_ok!(Purchase::update_balance(
				RuntimeOrigin::signed(validity_origin()),
				alice(),
				100_000,
				100_000,
				Permill::zero(),
			));
			assert_noop!(
				Purchase::payout(RuntimeOrigin::signed(payment_account()), alice()),
				ArithmeticError::Underflow
			);
		});
	}
	#[test]
	fn remove_pallet_works() {
		new_test_ext().execute_with(|| {
			let account_status = AccountStatus {
				validity: AccountValidity::Completed,
				free_balance: 1234,
				locked_balance: 4321,
				signature: b"my signature".to_vec(),
				vat: Permill::from_percent(50),
			};
			// Add some storage.
			Accounts::<Test>::insert(alice(), account_status.clone());
			Accounts::<Test>::insert(bob(), account_status);
			PaymentAccount::<Test>::put(alice());
			Statement::<Test>::put(b"hello, world!".to_vec());
			UnlockBlock::<Test>::put(4);
			// Verify storage exists.
			assert_eq!(Accounts::<Test>::iter().count(), 2);
			assert!(PaymentAccount::<Test>::exists());
			assert!(Statement::<Test>::exists());
			assert!(UnlockBlock::<Test>::exists());
			// Remove storage.
			remove_pallet::<Test>();
			// Verify storage is gone.
			assert_eq!(Accounts::<Test>::iter().count(), 0);
			assert!(!PaymentAccount::<Test>::exists());
			assert!(!Statement::<Test>::exists());
			assert!(!UnlockBlock::<Test>::exists());
		});
	}
}