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
//! Unit testing
18

            
19
use super::*;
20
use crate::mock::*;
21
use crate::pallet::{EndVestingBlock, InitVestingBlock, Initialized};
22
use frame_support::{assert_noop, assert_ok};
23
use parity_scale_codec::Encode;
24
use sp_core::{crypto::AccountId32, Pair};
25
use sp_runtime::traits::AccountIdConversion;
26
use sp_runtime::MultiSignature;
27

            
28
// Constant that reflects the desired vesting period for the tests
29
// Most tests complete initialization passing initRelayBlock + VESTING as the endRelayBlock
30
const VESTING: u32 = 8;
31

            
32
#[test]
33
1
fn geneses() {
34
1
	ExtBuilder::empty().execute_with(|| {
35
1
		// Fund pallet with exact amount needed (total rewards + small dust)
36
1
		assert!(System::events().is_empty());
37
		// Insert contributors
38
1
		let pairs = get_ed25519_pairs(3);
39
1
		let init_block = CrowdloanRewards::init_vesting_block();
40
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
41
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
42
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
43
1
			(pairs[0].public().into(), None, 500u32.into()),
44
1
			(pairs[1].public().into(), None, 500u32.into()),
45
1
			(pairs[2].public().into(), None, 500u32.into())
46
1
		]));
47
1
		assert_ok!(CrowdloanRewards::complete_initialization(
48
1
			init_block + VESTING
49
1
		));
50
1
		assert_eq!(CrowdloanRewards::total_contributors(), 5);
51

            
52
		// accounts_payable
53
1
		assert!(CrowdloanRewards::accounts_payable(&account(1)).is_some());
54
1
		assert!(CrowdloanRewards::accounts_payable(&account(2)).is_some());
55
1
		assert!(CrowdloanRewards::accounts_payable(&account(3)).is_none());
56
1
		assert!(CrowdloanRewards::accounts_payable(&account(4)).is_none());
57
1
		assert!(CrowdloanRewards::accounts_payable(&account(5)).is_none());
58

            
59
		// claimed address existence
60
1
		assert!(CrowdloanRewards::claimed_relay_chain_ids(account(1)).is_some());
61
1
		assert!(CrowdloanRewards::claimed_relay_chain_ids(account(2)).is_some());
62
1
		assert!(
63
1
			CrowdloanRewards::claimed_relay_chain_ids(AccountId32::from(pairs[0].public()))
64
1
				.is_none()
65
1
		);
66
1
		assert!(
67
1
			CrowdloanRewards::claimed_relay_chain_ids(AccountId32::from(pairs[1].public()))
68
1
				.is_none()
69
1
		);
70
1
		assert!(
71
1
			CrowdloanRewards::claimed_relay_chain_ids(AccountId32::from(pairs[2].public()))
72
1
				.is_none()
73
1
		);
74

            
75
		// unassociated_contributions
76
1
		assert!(CrowdloanRewards::unassociated_contributions(account(1)).is_none());
77
1
		assert!(CrowdloanRewards::unassociated_contributions(account(2)).is_none());
78
1
		assert!(
79
1
			CrowdloanRewards::unassociated_contributions(AccountId32::from(pairs[0].public()))
80
1
				.is_some()
81
1
		);
82
1
		assert!(
83
1
			CrowdloanRewards::unassociated_contributions(AccountId32::from(pairs[1].public()))
84
1
				.is_some()
85
1
		);
86
1
		assert!(
87
1
			CrowdloanRewards::unassociated_contributions(AccountId32::from(pairs[2].public()))
88
1
				.is_some()
89
1
		);
90
1
	});
91
1
}
92

            
93
#[test]
94
1
fn proving_assignation_works() {
95
1
	let pairs = get_ed25519_pairs(3);
96
1
	let mut payload = WRAPPED_BYTES_PREFIX.to_vec();
97
1
	payload.append(&mut SignatureNetworkIdentifier::get().to_vec());
98
1
	payload.append(&mut account(3).encode());
99
1
	payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec());
100
1
	let signature: MultiSignature = pairs[0].sign(&payload).into();
101
1

            
102
1
	let mut already_associated_payload = WRAPPED_BYTES_PREFIX.to_vec();
103
1
	already_associated_payload.append(&mut SignatureNetworkIdentifier::get().to_vec());
104
1
	already_associated_payload.append(&mut account(1).encode());
105
1
	already_associated_payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec());
106
1
	let alread_associated_signature: MultiSignature =
107
1
		pairs[0].sign(&already_associated_payload).into();
108
1
	ExtBuilder::empty().execute_with(|| {
109
1
		// Insert contributors
110
1
		let pairs = get_ed25519_pairs(3);
111
1
		let init_block = CrowdloanRewards::init_vesting_block();
112
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
113
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
114
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
115
1
			(pairs[0].public().into(), None, 500u32.into()),
116
1
			(pairs[1].public().into(), None, 500u32.into()),
117
1
			(pairs[2].public().into(), None, 500u32.into())
118
1
		],));
119
1
		assert_ok!(CrowdloanRewards::complete_initialization(
120
1
			init_block + VESTING
121
1
		));
122
		// 4 is not payable first
123
1
		assert!(CrowdloanRewards::accounts_payable(account(3)).is_none());
124
1
		assert_eq!(
125
1
			CrowdloanRewards::accounts_payable(account(1))
126
1
				.unwrap()
127
1
				.contributed_relay_addresses,
128
1
			vec![account(1)]
129
1
		);
130

            
131
1
		roll_to(4);
132
1
		// Signature is wrong, prove fails
133
1
		assert_noop!(
134
1
			CrowdloanRewards::associate_native_identity(
135
1
				RuntimeOrigin::signed(account(4)),
136
1
				account(4),
137
1
				pairs[0].public().into(),
138
1
				signature.clone()
139
1
			),
140
1
			Error::<Test>::InvalidClaimSignature
141
1
		);
142

            
143
		// Signature is right, but address already claimed
144
1
		assert_noop!(
145
1
			CrowdloanRewards::associate_native_identity(
146
1
				RuntimeOrigin::signed(account(4)),
147
1
				account(1),
148
1
				pairs[0].public().into(),
149
1
				alread_associated_signature
150
1
			),
151
1
			Error::<Test>::AlreadyAssociated
152
1
		);
153

            
154
		// Signature is right, prove passes
155
1
		assert_ok!(CrowdloanRewards::associate_native_identity(
156
1
			RuntimeOrigin::signed(account(4)),
157
1
			account(3),
158
1
			pairs[0].public().into(),
159
1
			signature.clone()
160
1
		));
161

            
162
		// Signature is right, but relay address is no longer on unassociated
163
1
		assert_noop!(
164
1
			CrowdloanRewards::associate_native_identity(
165
1
				RuntimeOrigin::signed(account(4)),
166
1
				account(3),
167
1
				pairs[0].public().into(),
168
1
				signature
169
1
			),
170
1
			Error::<Test>::NoAssociatedClaim
171
1
		);
172

            
173
		// now three is payable
174
1
		assert!(CrowdloanRewards::accounts_payable(account(3)).is_some());
175
1
		assert_eq!(
176
1
			CrowdloanRewards::accounts_payable(account(3))
177
1
				.unwrap()
178
1
				.contributed_relay_addresses,
179
1
			vec![AccountId32::from(*pairs[0].public().as_array_ref())]
180
1
		);
181

            
182
1
		assert!(
183
1
			CrowdloanRewards::unassociated_contributions(AccountId32::from(pairs[0].public()))
184
1
				.is_none()
185
1
		);
186
1
		assert!(
187
1
			CrowdloanRewards::claimed_relay_chain_ids(AccountId32::from(pairs[0].public()))
188
1
				.is_some()
189
1
		);
190

            
191
		// Only events from current block (after roll_to(4)) are preserved
192
1
		let expected = vec![
193
1
			crate::Event::InitialPaymentMade(account(3), 100),
194
1
			crate::Event::NativeIdentityAssociated(pairs[0].public().into(), account(3), 500),
195
1
		];
196
1
		assert_eq!(events(), expected);
197
1
	});
198
1
}
199

            
200
#[test]
201
1
fn initializing_multi_relay_to_single_native_address_works() {
202
1
	ExtBuilder::empty().execute_with(|| {
203
1
		// Insert contributors
204
1
		let pairs = get_ed25519_pairs(3);
205
1
		// The init relay block gets inserted
206
1
		roll_to(2);
207
1
		let init_block = CrowdloanRewards::init_vesting_block();
208
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
209
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
210
1
			([2u8; 32].into(), Some(account(1)), 500u32.into()),
211
1
			(pairs[0].public().into(), None, 500u32.into()),
212
1
			(pairs[1].public().into(), None, 500u32.into()),
213
1
			(pairs[2].public().into(), None, 500u32.into())
214
1
		]));
215
1
		assert_ok!(CrowdloanRewards::complete_initialization(
216
1
			init_block + VESTING
217
1
		));
218
		// 1 is payable
219
1
		assert!(CrowdloanRewards::accounts_payable(account(1)).is_some());
220
1
		assert_eq!(
221
1
			CrowdloanRewards::accounts_payable(account(1))
222
1
				.unwrap()
223
1
				.contributed_relay_addresses,
224
1
			vec![account(1), account(2)]
225
1
		);
226

            
227
1
		roll_to(4);
228
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
229
1
		assert_eq!(
230
1
			CrowdloanRewards::accounts_payable(account(1))
231
1
				.unwrap()
232
1
				.claimed_reward,
233
1
			400
234
1
		);
235
1
		assert_noop!(
236
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))),
237
1
			Error::<Test>::NoAssociatedClaim
238
1
		);
239

            
240
1
		let expected = vec![
241
1
			crate::Event::InitialPaymentMade(account(1), 100),
242
1
			crate::Event::InitialPaymentMade(account(1), 100),
243
1
			crate::Event::RewardsPaid(account(1), 200),
244
1
		];
245
1
		assert_eq!(events(), expected);
246
1
	});
247
1
}
248

            
249
#[test]
250
1
fn paying_works_step_by_step() {
251
1
	ExtBuilder::empty().execute_with(|| {
252
1
		// Insert contributors
253
1
		let pairs = get_ed25519_pairs(3);
254
1
		// The init relay block gets inserted
255
1
		roll_to(2);
256
1
		let init_block = CrowdloanRewards::init_vesting_block();
257
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
258
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
259
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
260
1
			(pairs[0].public().into(), None, 500u32.into()),
261
1
			(pairs[1].public().into(), None, 500u32.into()),
262
1
			(pairs[2].public().into(), None, 500u32.into())
263
1
		]));
264
1
		assert_ok!(CrowdloanRewards::complete_initialization(
265
1
			init_block + VESTING
266
1
		));
267
		// 1 is payable
268
1
		assert!(CrowdloanRewards::accounts_payable(&account(1)).is_some());
269
1
		roll_to(4);
270
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
271
1
		assert_eq!(
272
1
			CrowdloanRewards::accounts_payable(&account(1))
273
1
				.unwrap()
274
1
				.claimed_reward,
275
1
			200
276
1
		);
277
1
		assert_noop!(
278
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))),
279
1
			Error::<Test>::NoAssociatedClaim
280
1
		);
281
1
		roll_to(5);
282
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
283
1
		assert_eq!(
284
1
			CrowdloanRewards::accounts_payable(&account(1))
285
1
				.unwrap()
286
1
				.claimed_reward,
287
1
			250
288
1
		);
289
1
		roll_to(6);
290
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
291
1
		assert_eq!(
292
1
			CrowdloanRewards::accounts_payable(&account(1))
293
1
				.unwrap()
294
1
				.claimed_reward,
295
1
			300
296
1
		);
297
1
		roll_to(7);
298
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
299
1
		assert_eq!(
300
1
			CrowdloanRewards::accounts_payable(&account(1))
301
1
				.unwrap()
302
1
				.claimed_reward,
303
1
			350
304
1
		);
305
1
		roll_to(8);
306
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
307
1
		assert_eq!(
308
1
			CrowdloanRewards::accounts_payable(&account(1))
309
1
				.unwrap()
310
1
				.claimed_reward,
311
1
			400
312
1
		);
313
1
		roll_to(9);
314
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
315
1
		assert_eq!(
316
1
			CrowdloanRewards::accounts_payable(&account(1))
317
1
				.unwrap()
318
1
				.claimed_reward,
319
1
			450
320
1
		);
321
1
		roll_to(10);
322
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
323
1
		assert_eq!(
324
1
			CrowdloanRewards::accounts_payable(&account(1))
325
1
				.unwrap()
326
1
				.claimed_reward,
327
1
			500
328
1
		);
329
1
		roll_to(11);
330
1
		assert_noop!(
331
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))),
332
1
			Error::<Test>::RewardsAlreadyClaimed
333
1
		);
334

            
335
1
		let expected = vec![
336
1
			crate::Event::InitialPaymentMade(account(1), 100),
337
1
			crate::Event::InitialPaymentMade(account(2), 100),
338
1
			crate::Event::RewardsPaid(account(1), 100),
339
1
			crate::Event::RewardsPaid(account(1), 50),
340
1
			crate::Event::RewardsPaid(account(1), 50),
341
1
			crate::Event::RewardsPaid(account(1), 50),
342
1
			crate::Event::RewardsPaid(account(1), 50),
343
1
			crate::Event::RewardsPaid(account(1), 50),
344
1
			crate::Event::RewardsPaid(account(1), 50),
345
1
		];
346
1
		assert_eq!(events(), expected);
347
1
	});
348
1
}
349

            
350
#[test]
351
1
fn paying_works_after_unclaimed_period() {
352
1
	ExtBuilder::empty().execute_with(|| {
353
1
		// Insert contributors
354
1
		let pairs = get_ed25519_pairs(3);
355
1
		// The init relay block gets inserted
356
1
		roll_to(2);
357
1
		let init_block = CrowdloanRewards::init_vesting_block();
358
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
359
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
360
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
361
1
			(pairs[0].public().into(), None, 500u32.into()),
362
1
			(pairs[1].public().into(), None, 500u32.into()),
363
1
			(pairs[2].public().into(), None, 500u32.into())
364
1
		]));
365
1
		assert_ok!(CrowdloanRewards::complete_initialization(
366
1
			init_block + VESTING
367
1
		));
368

            
369
		// 1 is payable
370
1
		assert!(CrowdloanRewards::accounts_payable(&account(1)).is_some());
371
1
		roll_to(4);
372
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
373
1
		assert_eq!(
374
1
			CrowdloanRewards::accounts_payable(&account(1))
375
1
				.unwrap()
376
1
				.claimed_reward,
377
1
			200
378
1
		);
379
1
		assert_noop!(
380
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))),
381
1
			Error::<Test>::NoAssociatedClaim
382
1
		);
383
1
		roll_to(5);
384
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
385
1
		assert_eq!(
386
1
			CrowdloanRewards::accounts_payable(&account(1))
387
1
				.unwrap()
388
1
				.claimed_reward,
389
1
			250
390
1
		);
391
1
		roll_to(6);
392
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
393
1
		assert_eq!(
394
1
			CrowdloanRewards::accounts_payable(&account(1))
395
1
				.unwrap()
396
1
				.claimed_reward,
397
1
			300
398
1
		);
399
1
		roll_to(7);
400
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
401
1
		assert_eq!(
402
1
			CrowdloanRewards::accounts_payable(&account(1))
403
1
				.unwrap()
404
1
				.claimed_reward,
405
1
			350
406
1
		);
407
1
		roll_to(11);
408
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
409
1
		assert_eq!(
410
1
			CrowdloanRewards::accounts_payable(&account(1))
411
1
				.unwrap()
412
1
				.claimed_reward,
413
1
			500
414
1
		);
415
1
		roll_to(330);
416
1
		assert_noop!(
417
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))),
418
1
			Error::<Test>::RewardsAlreadyClaimed
419
1
		);
420

            
421
1
		let expected = vec![
422
1
			crate::Event::InitialPaymentMade(account(1), 100),
423
1
			crate::Event::InitialPaymentMade(account(2), 100),
424
1
			crate::Event::RewardsPaid(account(1), 100),
425
1
			crate::Event::RewardsPaid(account(1), 50),
426
1
			crate::Event::RewardsPaid(account(1), 50),
427
1
			crate::Event::RewardsPaid(account(1), 50),
428
1
			crate::Event::RewardsPaid(account(1), 150),
429
1
		];
430
1
		assert_eq!(events(), expected);
431
1
	});
432
1
}
433

            
434
#[test]
435
1
fn paying_late_joiner_works() {
436
1
	let pairs = get_ed25519_pairs(3);
437
1
	let mut payload = WRAPPED_BYTES_PREFIX.to_vec();
438
1
	payload.append(&mut SignatureNetworkIdentifier::get().to_vec());
439
1
	payload.append(&mut account(3).encode());
440
1
	payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec());
441
1
	let signature: MultiSignature = pairs[0].sign(&payload).into();
442
1
	ExtBuilder::empty().execute_with(|| {
443
1
		// Insert contributors
444
1
		let pairs = get_ed25519_pairs(3);
445
1
		let init_block = CrowdloanRewards::init_vesting_block();
446
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
447
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
448
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
449
1
			(pairs[0].public().into(), None, 500u32.into()),
450
1
			(pairs[1].public().into(), None, 500u32.into()),
451
1
			(pairs[2].public().into(), None, 500u32.into())
452
1
		]));
453
1
		assert_ok!(CrowdloanRewards::complete_initialization(
454
1
			init_block + VESTING
455
1
		));
456

            
457
1
		roll_to(12);
458
1
		assert_ok!(CrowdloanRewards::associate_native_identity(
459
1
			RuntimeOrigin::signed(account(4)),
460
1
			account(3),
461
1
			pairs[0].public().into(),
462
1
			signature.clone()
463
1
		));
464
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
465
1
		assert_eq!(
466
1
			CrowdloanRewards::accounts_payable(&account(3))
467
1
				.unwrap()
468
1
				.claimed_reward,
469
1
			500
470
1
		);
471
		// Note: account(1) and account(2) don't get InitialPaymentMade events during initialization
472
		// because they are already associated with native accounts. Only account(3) gets it
473
		// when associate_native_identity is called.
474
1
		let expected = vec![
475
1
			crate::Event::InitialPaymentMade(account(3), 100),
476
1
			crate::Event::NativeIdentityAssociated(pairs[0].public().into(), account(3), 500),
477
1
			crate::Event::RewardsPaid(account(3), 400),
478
1
		];
479
1
		assert_eq!(events(), expected);
480
1
	});
481
1
}
482

            
483
#[test]
484
1
fn update_address_works() {
485
1
	ExtBuilder::empty().execute_with(|| {
486
1
		// Insert contributors
487
1
		let pairs = get_ed25519_pairs(3);
488
1
		// The init relay block gets inserted
489
1
		roll_to(2);
490
1
		let init_block = CrowdloanRewards::init_vesting_block();
491
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
492
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
493
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
494
1
			(pairs[0].public().into(), None, 500u32.into()),
495
1
			(pairs[1].public().into(), None, 500u32.into()),
496
1
			(pairs[2].public().into(), None, 500u32.into())
497
1
		]));
498
1
		assert_ok!(CrowdloanRewards::complete_initialization(
499
1
			init_block + VESTING
500
1
		));
501

            
502
1
		roll_to(4);
503
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
504
1
		assert_noop!(
505
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(8))),
506
1
			Error::<Test>::NoAssociatedClaim
507
1
		);
508
1
		assert_ok!(CrowdloanRewards::update_reward_address(
509
1
			RuntimeOrigin::signed(account(1)),
510
1
			account(8)
511
1
		));
512
1
		assert_eq!(
513
1
			CrowdloanRewards::accounts_payable(&account(8))
514
1
				.unwrap()
515
1
				.claimed_reward,
516
1
			200
517
1
		);
518
1
		roll_to(6);
519
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(8))));
520
1
		assert_eq!(
521
1
			CrowdloanRewards::accounts_payable(&account(8))
522
1
				.unwrap()
523
1
				.claimed_reward,
524
1
			300
525
1
		);
526
		// The initial payment is not
527
1
		let expected = vec![
528
1
			crate::Event::InitialPaymentMade(account(1), 100),
529
1
			crate::Event::InitialPaymentMade(account(2), 100),
530
1
			crate::Event::RewardsPaid(account(1), 100),
531
1
			crate::Event::RewardAddressUpdated(account(1), account(8)),
532
1
			crate::Event::RewardsPaid(account(8), 100),
533
1
		];
534
1
		assert_eq!(events(), expected);
535
1
	});
536
1
}
537

            
538
#[test]
539
1
fn update_address_with_existing_address_fails() {
540
1
	ExtBuilder::empty().execute_with(|| {
541
1
		// Insert contributors
542
1
		let pairs = get_ed25519_pairs(3);
543
1
		// The init relay block gets inserted
544
1
		roll_to(2);
545
1
		let init_block = CrowdloanRewards::init_vesting_block();
546
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
547
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
548
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
549
1
			(pairs[0].public().into(), None, 500u32.into()),
550
1
			(pairs[1].public().into(), None, 500u32.into()),
551
1
			(pairs[2].public().into(), None, 500u32.into())
552
1
		]));
553
1
		assert_ok!(CrowdloanRewards::complete_initialization(
554
1
			init_block + VESTING
555
1
		));
556

            
557
1
		roll_to(4);
558
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
559
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(2))));
560
1
		assert_noop!(
561
1
			CrowdloanRewards::update_reward_address(RuntimeOrigin::signed(account(1)), account(2)),
562
1
			Error::<Test>::AlreadyAssociated
563
1
		);
564
1
	});
565
1
}
566

            
567
#[test]
568
1
fn update_address_with_existing_with_multi_address_works() {
569
1
	ExtBuilder::empty().execute_with(|| {
570
1
		// Insert contributors
571
1
		let pairs = get_ed25519_pairs(3);
572
1
		// The init relay block gets inserted
573
1
		roll_to(2);
574
1
		let init_block = CrowdloanRewards::init_vesting_block();
575
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
576
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
577
1
			([2u8; 32].into(), Some(account(1)), 500u32.into()),
578
1
			(pairs[0].public().into(), None, 500u32.into()),
579
1
			(pairs[1].public().into(), None, 500u32.into()),
580
1
			(pairs[2].public().into(), None, 500u32.into())
581
1
		]));
582
1
		assert_ok!(CrowdloanRewards::complete_initialization(
583
1
			init_block + VESTING
584
1
		));
585

            
586
1
		roll_to(4);
587
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))));
588

            
589
		// We make sure all rewards go to the new address
590
1
		assert_ok!(CrowdloanRewards::update_reward_address(
591
1
			RuntimeOrigin::signed(account(1)),
592
1
			account(2)
593
1
		));
594
1
		assert_eq!(
595
1
			CrowdloanRewards::accounts_payable(&account(2))
596
1
				.unwrap()
597
1
				.claimed_reward,
598
1
			400
599
1
		);
600
1
		assert_eq!(
601
1
			CrowdloanRewards::accounts_payable(&account(2))
602
1
				.unwrap()
603
1
				.total_reward,
604
1
			1000
605
1
		);
606

            
607
1
		assert_noop!(
608
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))),
609
1
			Error::<Test>::NoAssociatedClaim
610
1
		);
611
1
	});
612
1
}
613

            
614
#[test]
615
1
fn initialize_new_addresses() {
616
1
	ExtBuilder::empty().execute_with(|| {
617
1
		// The init relay block gets inserted
618
1
		roll_to(2);
619
1
		// Insert contributors
620
1
		let pairs = get_ed25519_pairs(3);
621
1
		let init_block = CrowdloanRewards::init_vesting_block();
622
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
623
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
624
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
625
1
			(pairs[0].public().into(), None, 500u32.into()),
626
1
			(pairs[1].public().into(), None, 500u32.into()),
627
1
			(pairs[2].public().into(), None, 500u32.into())
628
1
		]));
629
1
		assert_ok!(CrowdloanRewards::complete_initialization(
630
1
			init_block + VESTING
631
1
		));
632

            
633
1
		assert_eq!(CrowdloanRewards::initialized(), true);
634

            
635
1
		roll_to(4);
636
1
		assert_noop!(
637
1
			CrowdloanRewards::initialize_reward_vec(vec![(
638
1
				[1u8; 32].into(),
639
1
				Some(account(1)),
640
1
				500u32.into()
641
1
			)]),
642
1
			Error::<Test>::RewardVecAlreadyInitialized,
643
1
		);
644

            
645
1
		assert_noop!(
646
1
			CrowdloanRewards::complete_initialization(init_block + VESTING * 2),
647
1
			Error::<Test>::RewardVecAlreadyInitialized,
648
1
		);
649
1
	});
650
1
}
651

            
652
#[test]
653
1
fn initialize_new_addresses_handle_dust() {
654
1
	ExtBuilder::empty().execute_with(|| {
655
1
		// The init relay block gets inserted
656
1
		roll_to(2);
657
1
		// Insert contributors
658
1
		let pairs = get_ed25519_pairs(2);
659
1
		let init_block = CrowdloanRewards::init_vesting_block();
660
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
661
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
662
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
663
1
			(pairs[0].public().into(), None, 500u32.into()),
664
1
			(pairs[1].public().into(), None, 999u32.into()),
665
1
		]));
666

            
667
1
		assert_ok!(CrowdloanRewards::complete_initialization(
668
1
			init_block + VESTING
669
1
		));
670

            
671
1
		assert_eq!(CrowdloanRewards::initialized(), true);
672
1
	});
673
1
}
674

            
675
#[test]
676
1
fn initialize_new_addresses_not_matching_funds() {
677
1
	ExtBuilder::empty().execute_with(|| {
678
1
		// The init relay block gets inserted
679
1
		roll_to(2);
680
1
		// Insert contributors
681
1
		let pairs = get_ed25519_pairs(2);
682
1
		let init_block = CrowdloanRewards::init_vesting_block();
683
1
		// Total supply is 2500. Lets ensure inserting 2495 is not working.
684
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
685
1
			([1u8; 32].into(), Some(account(1)), 500u32.into()),
686
1
			([2u8; 32].into(), Some(account(2)), 500u32.into()),
687
1
			(pairs[0].public().into(), None, 500u32.into()),
688
1
			(pairs[1].public().into(), None, 995u32.into()),
689
1
		]));
690
1
		assert_noop!(
691
1
			CrowdloanRewards::complete_initialization(init_block + VESTING),
692
1
			Error::<Test>::RewardsDoNotMatchFund
693
1
		);
694
1
	});
695
1
}
696

            
697
#[test]
698
1
fn initialize_new_addresses_with_batch() {
699
1
	ExtBuilder::empty().execute_with(|| {
700
1
		// This time should succeed trully
701
1
		roll_to(10);
702
1
		let init_block = CrowdloanRewards::init_vesting_block();
703
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
704
1
			[4u8; 32].into(),
705
1
			Some(account(3)),
706
1
			1250
707
1
		)]));
708
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
709
1
			[5u8; 32].into(),
710
1
			Some(account(1)),
711
1
			1250
712
1
		)]));
713

            
714
1
		assert_ok!(CrowdloanRewards::complete_initialization(
715
1
			init_block + VESTING
716
1
		));
717
1
		assert_eq!(CrowdloanRewards::total_contributors(), 2);
718
		// Verify that the second ending block provider had no effect
719
1
		assert_eq!(CrowdloanRewards::end_vesting_block(), init_block + VESTING);
720

            
721
		// After complete_initialization, initialize_reward_vec should fail
722
1
		assert_noop!(
723
1
			CrowdloanRewards::initialize_reward_vec(vec![(
724
1
				[4u8; 32].into(),
725
1
				Some(account(3)),
726
1
				500
727
1
			)]),
728
1
			Error::<Test>::RewardVecAlreadyInitialized
729
1
		);
730
1
	});
731
1
}
732

            
733
#[test]
734
1
fn floating_point_arithmetic_works() {
735
1
	ExtBuilder::empty().execute_with(|| {
736
1
		// The init relay block gets inserted
737
1
		roll_to(2);
738
1
		let init_block = CrowdloanRewards::init_vesting_block();
739
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
740
1
			[4u8; 32].into(),
741
1
			Some(account(1)),
742
1
			1190
743
1
		)]));
744
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
745
1
			[5u8; 32].into(),
746
1
			Some(account(2)),
747
1
			1185
748
1
		)]));
749
		// We will work with this. This has 100/8=12.5 payable per block
750
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
751
1
			[3u8; 32].into(),
752
1
			Some(account(3)),
753
1
			125
754
1
		)]));
755

            
756
1
		assert_ok!(CrowdloanRewards::complete_initialization(
757
1
			init_block + VESTING
758
1
		));
759
1
		assert_eq!(CrowdloanRewards::total_contributors(), 3);
760

            
761
1
		assert_eq!(
762
1
			CrowdloanRewards::accounts_payable(&account(3))
763
1
				.unwrap()
764
1
				.claimed_reward,
765
1
			25u128
766
1
		);
767

            
768
		// Block relay number is 2 post init initialization
769
		// In this case there is no problem. Here we pay 12.5*2=25
770
		// Total claimed reward: 25+25 = 50
771
1
		roll_to(4);
772
1

            
773
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
774

            
775
1
		assert_eq!(
776
1
			CrowdloanRewards::accounts_payable(&account(3))
777
1
				.unwrap()
778
1
				.claimed_reward,
779
1
			50u128
780
1
		);
781
1
		roll_to(5);
782
1
		// If we claim now we have to pay 12.5. 12 will be paid.
783
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
784

            
785
1
		assert_eq!(
786
1
			CrowdloanRewards::accounts_payable(&account(3))
787
1
				.unwrap()
788
1
				.claimed_reward,
789
1
			62u128
790
1
		);
791
1
		roll_to(6);
792
1
		// Now we should pay 12.5. However the calculus will be:
793
1
		// Account 3 should have claimed 50 + 25 at this block, but
794
1
		// he only claimed 62. The payment is 13
795
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
796
1
		assert_eq!(
797
1
			CrowdloanRewards::accounts_payable(&account(3))
798
1
				.unwrap()
799
1
				.claimed_reward,
800
1
			75u128
801
1
		);
802
1
		let expected = vec![
803
1
			crate::Event::InitialPaymentMade(account(1), 238),
804
1
			crate::Event::InitialPaymentMade(account(2), 237),
805
1
			crate::Event::InitialPaymentMade(account(3), 25),
806
1
			crate::Event::RewardsPaid(account(3), 25),
807
1
			crate::Event::RewardsPaid(account(3), 12),
808
1
			crate::Event::RewardsPaid(account(3), 13),
809
1
		];
810
1
		assert_eq!(events(), expected);
811
1
	});
812
1
}
813

            
814
#[test]
815
1
fn reward_below_vesting_period_works() {
816
1
	ExtBuilder::empty().execute_with(|| {
817
1
		// The init relay block gets inserted
818
1
		roll_to(2);
819
1
		let init_block = CrowdloanRewards::init_vesting_block();
820
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
821
1
			[4u8; 32].into(),
822
1
			Some(account(1)),
823
1
			1247
824
1
		)]));
825
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
826
1
			[5u8; 32].into(),
827
1
			Some(account(2)),
828
1
			1247
829
1
		)]));
830
		// We will work with this. This has 5/8=0.625 payable per block
831
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
832
1
			[3u8; 32].into(),
833
1
			Some(account(3)),
834
1
			6
835
1
		)]));
836

            
837
1
		assert_ok!(CrowdloanRewards::complete_initialization(
838
1
			init_block + VESTING
839
1
		));
840

            
841
1
		assert_eq!(
842
1
			CrowdloanRewards::accounts_payable(&account(3))
843
1
				.unwrap()
844
1
				.claimed_reward,
845
1
			1u128
846
1
		);
847

            
848
		// Block relay number is 2 post init initialization
849
		// Here we should pay floor(0.625*2)=1
850
		// Total claimed reward: 1+1 = 2
851
1
		roll_to(4);
852
1

            
853
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
854

            
855
1
		assert_eq!(
856
1
			CrowdloanRewards::accounts_payable(&account(3))
857
1
				.unwrap()
858
1
				.claimed_reward,
859
1
			2u128
860
1
		);
861
1
		roll_to(5);
862
1
		// If we claim now we have to pay floor(0.625) = 0
863
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
864

            
865
1
		assert_eq!(
866
1
			CrowdloanRewards::accounts_payable(&account(3))
867
1
				.unwrap()
868
1
				.claimed_reward,
869
1
			2u128
870
1
		);
871
1
		roll_to(6);
872
1
		// Now we should pay 1 again. The claimer should have claimed floor(0.625*4) + 1
873
1
		// but he only claimed 2
874
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
875
1
		assert_eq!(
876
1
			CrowdloanRewards::accounts_payable(&account(3))
877
1
				.unwrap()
878
1
				.claimed_reward,
879
1
			3u128
880
1
		);
881
1
		roll_to(10);
882
1
		// We pay the remaining
883
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))));
884
1
		assert_eq!(
885
1
			CrowdloanRewards::accounts_payable(&account(3))
886
1
				.unwrap()
887
1
				.claimed_reward,
888
1
			6u128
889
1
		);
890
1
		roll_to(11);
891
1
		// Nothing more to claim
892
1
		assert_noop!(
893
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(3))),
894
1
			Error::<Test>::RewardsAlreadyClaimed
895
1
		);
896

            
897
1
		let expected = vec![
898
1
			crate::Event::InitialPaymentMade(account(1), 249),
899
1
			crate::Event::InitialPaymentMade(account(2), 249),
900
1
			crate::Event::InitialPaymentMade(account(3), 1),
901
1
			crate::Event::RewardsPaid(account(3), 1),
902
1
			crate::Event::RewardsPaid(account(3), 0),
903
1
			crate::Event::RewardsPaid(account(3), 1),
904
1
			crate::Event::RewardsPaid(account(3), 3),
905
1
		];
906
1
		assert_eq!(events(), expected);
907
1
	});
908
1
}
909

            
910
#[test]
911
1
fn test_initialization_errors() {
912
1
	ExtBuilder::empty().execute_with(|| {
913
1
		// The init relay block gets inserted
914
1
		roll_to(2);
915
1
		let init_block = CrowdloanRewards::init_vesting_block();
916
1

            
917
1
		let pot = CrowdloanRewards::pot();
918
1

            
919
1
		// Too many contributors
920
1
		assert_noop!(
921
1
			CrowdloanRewards::initialize_reward_vec(vec![
922
1
				([1u8; 32].into(), Some(account(1)), 1),
923
1
				([2u8; 32].into(), Some(account(2)), 1),
924
1
				([3u8; 32].into(), Some(account(3)), 1),
925
1
				([4u8; 32].into(), Some(account(4)), 1),
926
1
				([5u8; 32].into(), Some(account(5)), 1),
927
1
				([6u8; 32].into(), Some(account(6)), 1),
928
1
				([7u8; 32].into(), Some(account(7)), 1),
929
1
				([8u8; 32].into(), Some(account(8)), 1),
930
1
				([9u8; 32].into(), Some(account(9)), 1)
931
1
			]),
932
1
			Error::<Test>::TooManyContributors
933
1
		);
934

            
935
		// Go beyond fund pot
936
1
		assert_noop!(
937
1
			CrowdloanRewards::initialize_reward_vec(vec![(
938
1
				[1u8; 32].into(),
939
1
				Some(account(1)),
940
1
				pot + 1
941
1
			)]),
942
1
			Error::<Test>::BatchBeyondFundPot
943
1
		);
944

            
945
		// Dont fill rewards
946
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
947
1
			[1u8; 32].into(),
948
1
			Some(account(1)),
949
1
			pot - 1
950
1
		)]));
951

            
952
		// Fill rewards
953
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![(
954
1
			[2u8; 32].into(),
955
1
			Some(account(2)),
956
1
			1
957
1
		)]));
958

            
959
		// Insert a non-valid vesting period
960
1
		assert_noop!(
961
1
			CrowdloanRewards::complete_initialization(init_block),
962
1
			Error::<Test>::VestingPeriodNonValid
963
1
		);
964

            
965
		// Cannot claim if we dont complete initialization
966
1
		assert_noop!(
967
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(account(1))),
968
1
			Error::<Test>::RewardVecNotFullyInitializedYet
969
1
		);
970
		// Complete
971
1
		assert_ok!(CrowdloanRewards::complete_initialization(
972
1
			init_block + VESTING
973
1
		));
974

            
975
		// Cannot initialize again
976
1
		assert_noop!(
977
1
			CrowdloanRewards::complete_initialization(init_block),
978
1
			Error::<Test>::RewardVecAlreadyInitialized
979
1
		);
980
1
	});
981
1
}
982

            
983
#[test]
984
1
fn test_relay_signatures_can_change_reward_addresses() {
985
1
	ExtBuilder::empty().execute_with(|| {
986
1
		// 5 relay keys
987
1
		let pairs = get_ed25519_pairs(5);
988
1

            
989
1
		// The init relay block gets inserted
990
1
		roll_to(2);
991
1
		let init_block = CrowdloanRewards::init_vesting_block();
992
1

            
993
1
		// We will have all pointint to the same reward account
994
1
		assert_ok!(CrowdloanRewards::initialize_reward_vec(vec![
995
1
			(pairs[0].public().into(), Some(account(1)), 500u32.into()),
996
1
			(pairs[1].public().into(), Some(account(1)), 500u32.into()),
997
1
			(pairs[2].public().into(), Some(account(1)), 500u32.into()),
998
1
			(pairs[3].public().into(), Some(account(1)), 500u32.into()),
999
1
			(pairs[4].public().into(), Some(account(1)), 500u32.into())
1
		],));
		// Complete
1
		assert_ok!(CrowdloanRewards::complete_initialization(
1
			init_block + VESTING
1
		));
1
		let reward_info = CrowdloanRewards::accounts_payable(&account(1)).unwrap();
		// We should have all of them as contributors
5
		for pair in pairs.clone() {
5
			assert!(reward_info
5
				.contributed_relay_addresses
5
				.contains(&pair.public().into()))
		}
		// Threshold is set to 50%, so we need at least 3 votes to pass
		// Let's make sure that we dont pass with 2
1
		let mut payload = WRAPPED_BYTES_PREFIX.to_vec();
1
		payload.append(&mut SignatureNetworkIdentifier::get().to_vec());
1
		payload.append(&mut account(2).encode());
1
		payload.append(&mut account(1).encode());
1
		payload.append(&mut WRAPPED_BYTES_POSTFIX.to_vec());
1

            
1
		let mut insufficient_proofs: Vec<(AccountId, MultiSignature)> = vec![];
3
		for i in 0..2 {
2
			insufficient_proofs.push((pairs[i].public().into(), pairs[i].sign(&payload).into()));
2
		}
		// Not sufficient proofs presented
1
		assert_noop!(
1
			CrowdloanRewards::change_association_with_relay_keys(
1
				RuntimeOrigin::signed(account(1)),
1
				account(2),
1
				account(1),
1
				insufficient_proofs.clone()
1
			),
1
			Error::<Test>::InsufficientNumberOfValidProofs
1
		);
		// With three votes we should passs
1
		let mut sufficient_proofs = insufficient_proofs.clone();
1

            
1
		// We push one more
1
		sufficient_proofs.push((pairs[2].public().into(), pairs[2].sign(&payload).into()));
1

            
1
		// This time should pass
1
		assert_ok!(CrowdloanRewards::change_association_with_relay_keys(
1
			RuntimeOrigin::signed(account(1)),
1
			account(2),
1
			account(1),
1
			sufficient_proofs.clone()
1
		));
		// 1 should no longer be payable
1
		assert!(CrowdloanRewards::accounts_payable(&account(1)).is_none());
		// 2 should be now payable
1
		let reward_info_2 = CrowdloanRewards::accounts_payable(&account(2)).unwrap();
1

            
1
		// The reward info should be identical
1
		assert_eq!(reward_info, reward_info_2);
1
	});
1
}
#[test]
1
fn test_claim_works_with_full_vesting() {
1
	let reward_account = account(1);
1
	let relay_account = account(10);
1
	let total_reward = 10_000u128;
1

            
1
	ExtBuilder::default()
1
		.with_funded_accounts(vec![(
1
			relay_account.clone(),
1
			Some(reward_account.clone()),
1
			total_reward,
1
		)])
1
		.build()
1
		.execute_with(|| {
1
			// Move to end of vesting period
1
			run_to_block(100);
1

            
1
			let initial_balance = Balances::free_balance(&reward_account);
1
			let pallet_initial_balance = Balances::free_balance(&CrowdloanRewards::account_id());
1

            
1
			// Claim rewards
1
			assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(
1
				reward_account.clone()
1
			)));
			// Check that rewards were paid
1
			let reward_info = AccountsPayable::<Test>::get(&reward_account).unwrap();
1
			assert_eq!(reward_info.claimed_reward, total_reward);
			// Check balances
1
			let final_balance = Balances::free_balance(&reward_account);
1
			let pallet_final_balance = Balances::free_balance(&CrowdloanRewards::account_id());
1

            
1
			// Should have received remaining vested amount
1
			let initialization_payment = InitializationPayment::get() * total_reward;
1
			let expected_claim = total_reward - initialization_payment;
1
			assert_eq!(final_balance, initial_balance + expected_claim);
1
			assert_eq!(
1
				pallet_final_balance,
1
				pallet_initial_balance - expected_claim
1
			);
			// Note: Event checking could be added here if needed
1
		});
1
}
#[test]
1
fn test_claim_works_with_partial_vesting() {
1
	let reward_account = account(1);
1
	let relay_account = account(10);
1
	let total_reward = 10_000u128;
1

            
1
	ExtBuilder::default()
1
		.with_funded_accounts(vec![(
1
			relay_account.clone(),
1
			Some(reward_account.clone()),
1
			total_reward,
1
		)])
1
		.build()
1
		.execute_with(|| {
1
			// Move to 50% of vesting period (block 50 out of 100)
1
			run_to_block(50);
1

            
1
			let initial_balance = Balances::free_balance(&reward_account);
1

            
1
			// Claim rewards
1
			assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(
1
				reward_account.clone()
1
			)));
			// Check that partial rewards were paid
1
			let reward_info = AccountsPayable::<Test>::get(&reward_account).unwrap();
1

            
1
			// Calculate expected vested amount
1
			let initialization_payment = InitializationPayment::get() * total_reward;
1
			let remaining_to_vest = total_reward - initialization_payment;
1
			let vesting_period = 100u32 - 1u32; // 99 blocks
1
			let elapsed_period = 50u32 - 1u32; // 49 blocks
1
			let expected_vested =
1
				remaining_to_vest * elapsed_period as u128 / vesting_period as u128;
1
			let expected_total_claimed = initialization_payment + expected_vested;
1

            
1
			assert_eq!(reward_info.claimed_reward, expected_total_claimed);
			// Check balance increased by the vested amount
1
			let final_balance = Balances::free_balance(&reward_account);
1
			assert_eq!(final_balance, initial_balance + expected_vested);
1
		});
1
}
#[test]
1
fn test_claim_fails_when_no_rewards() {
1
	// Use default genesis which initializes with account(1) having rewards
1
	// But we'll try to claim with a different account
1
	ExtBuilder::default().build().execute_with(|| {
1
		let reward_account = account(2);
1

            
1
		// Try to claim without having any rewards
1
		assert_noop!(
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(reward_account)),
1
			Error::<Test>::NoAssociatedClaim
1
		);
1
	});
1
}
#[test]
1
fn test_claim_fails_when_not_initialized() {
1
	// Use empty genesis config which will still set Initialized to true
1
	// We need to manually set it to false after
1
	ExtBuilder::empty().execute_with(|| {
1
		let reward_account = account(1);
1
		let relay_account = account(10);
1
		let total_reward = 10_000u128;
1

            
1
		// Manually insert reward data and mark as not initialized
1
		let reward_info = RewardInfo {
1
			total_reward,
1
			claimed_reward: InitializationPayment::get() * total_reward,
1
			contributed_relay_addresses: vec![relay_account.clone()],
1
		};
1
		AccountsPayable::<Test>::insert(&reward_account, &reward_info);
1
		ClaimedRelayChainIds::<Test>::insert(&relay_account, ());
1
		Initialized::<Test>::put(false);
1

            
1
		// Try to claim
1
		assert_noop!(
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(reward_account)),
1
			Error::<Test>::RewardVecNotFullyInitializedYet
1
		);
1
	});
1
}
#[test]
1
fn test_claim_fails_when_all_rewards_claimed() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		let reward_account = account(1);
1
		let relay_account = account(10);
1
		let total_reward = 10_000u128;
1

            
1
		// Setup reward data with all rewards already claimed
1
		let reward_info = RewardInfo {
1
			total_reward,
1
			claimed_reward: total_reward, // All claimed
1
			contributed_relay_addresses: vec![relay_account.clone()],
1
		};
1

            
1
		AccountsPayable::<Test>::insert(&reward_account, &reward_info);
1
		ClaimedRelayChainIds::<Test>::insert(&relay_account, ());
1
		InitVestingBlock::<Test>::put(1u32);
1
		EndVestingBlock::<Test>::put(100u32);
1
		Initialized::<Test>::put(true);
1

            
1
		// Try to claim
1
		assert_noop!(
1
			CrowdloanRewards::claim(RuntimeOrigin::signed(reward_account)),
1
			Error::<Test>::RewardsAlreadyClaimed
1
		);
1
	});
1
}
#[test]
1
fn test_update_reward_address_works() {
1
	let old_reward_account = account(1);
1
	let new_reward_account = account(2);
1
	let relay_account = account(10);
1
	let total_reward = 10_000u128;
1

            
1
	ExtBuilder::default()
1
		.with_funded_accounts(vec![(
1
			relay_account.clone(),
1
			Some(old_reward_account.clone()),
1
			total_reward,
1
		)])
1
		.build()
1
		.execute_with(|| {
1
			// Update reward address
1
			assert_ok!(CrowdloanRewards::update_reward_address(
1
				RuntimeOrigin::signed(old_reward_account.clone()),
1
				new_reward_account.clone()
1
			));
			// Check that old account no longer has rewards
1
			assert!(AccountsPayable::<Test>::get(&old_reward_account).is_none());
			// Check that new account has the rewards
1
			let reward_info = AccountsPayable::<Test>::get(&new_reward_account).unwrap();
1
			assert_eq!(reward_info.total_reward, total_reward);
			// Note: Event checking could be added here if needed
1
		});
1
}
#[test]
1
fn test_update_reward_address_fails_when_no_rewards() {
1
	ExtBuilder::empty().execute_with(|| {
1
		let old_reward_account = account(1);
1
		let new_reward_account = account(2);
1

            
1
		// Try to update address without having rewards
1
		assert_noop!(
1
			CrowdloanRewards::update_reward_address(
1
				RuntimeOrigin::signed(old_reward_account),
1
				new_reward_account
1
			),
1
			Error::<Test>::NoAssociatedClaim
1
		);
1
	});
1
}
#[test]
1
fn test_update_reward_address_fails_when_new_account_already_has_rewards() {
1
	let old_reward_account = account(1);
1
	let new_reward_account = account(2);
1
	let relay_account1 = account(10);
1
	let relay_account2 = account(11);
1
	let total_reward = 10_000u128;
1

            
1
	ExtBuilder::default()
1
		.with_funded_accounts(vec![
1
			(
1
				relay_account1.clone(),
1
				Some(old_reward_account.clone()),
1
				total_reward,
1
			),
1
			(
1
				relay_account2.clone(),
1
				Some(new_reward_account.clone()),
1
				total_reward,
1
			),
1
		])
1
		.build()
1
		.execute_with(|| {
1
			// Try to update address to an account that already has rewards
1
			assert_noop!(
1
				CrowdloanRewards::update_reward_address(
1
					RuntimeOrigin::signed(old_reward_account),
1
					new_reward_account
1
				),
1
				Error::<Test>::AlreadyAssociated
1
			);
1
		});
1
}
#[test]
1
fn test_pot_returns_correct_balance() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		// Total rewards minus the initial payment to the native accounts
1
		// Default genesis has 10_000 reward with 20% initial payment = 2000
1
		// Pot is set to total_rewards + 1 (dust) = 10_001
1
		let expected_balance = 10_001u128 - 2000u128;
1
		assert_eq!(CrowdloanRewards::pot(), expected_balance);
1
	});
1
}
#[test]
1
fn test_account_id_returns_correct_account() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		let expected_account = CrowdloanPalletId::get().into_account_truncating();
1
		assert_eq!(CrowdloanRewards::account_id(), expected_account);
1
	});
1
}
#[test]
1
fn test_vesting_calculation_with_zero_period() {
1
	ExtBuilder::default().build().execute_with(|| {
1
		let reward_account = account(1);
1
		let relay_account = account(10);
1
		let total_reward = 10_000u128;
1

            
1
		// Setup with zero vesting period
1
		let reward_info = RewardInfo {
1
			total_reward,
1
			claimed_reward: InitializationPayment::get() * total_reward,
1
			contributed_relay_addresses: vec![relay_account.clone()],
1
		};
1

            
1
		AccountsPayable::<Test>::insert(&reward_account, &reward_info);
1
		ClaimedRelayChainIds::<Test>::insert(&relay_account, ());
1
		InitVestingBlock::<Test>::put(1u32);
1
		EndVestingBlock::<Test>::put(1u32); // Same as init = zero period
1
		Initialized::<Test>::put(true);
1

            
1
		run_to_block(10);
1

            
1
		let initial_balance = Balances::free_balance(&reward_account);
1

            
1
		// Claim with zero vesting period should pay everything immediately
1
		assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(
1
			reward_account.clone()
1
		)));
1
		let final_balance = Balances::free_balance(&reward_account);
1
		let initialization_payment = InitializationPayment::get() * total_reward;
1
		let remaining_reward = total_reward - initialization_payment;
1

            
1
		assert_eq!(final_balance, initial_balance + remaining_reward);
1
	});
1
}
#[test]
1
fn test_multiple_claims_during_vesting() {
1
	let reward_account = account(1);
1
	let relay_account = account(10);
1
	let total_reward = 10_000u128;
1

            
1
	ExtBuilder::default()
1
		.with_funded_accounts(vec![(
1
			relay_account.clone(),
1
			Some(reward_account.clone()),
1
			total_reward,
1
		)])
1
		.build()
1
		.execute_with(|| {
1
			let initialization_payment = InitializationPayment::get() * total_reward;
1
			let initial_balance = Balances::free_balance(&reward_account);
1

            
1
			// First claim at 25% vesting
1
			run_to_block(25);
1
			assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(
1
				reward_account.clone()
1
			)));
1
			let balance_after_first = Balances::free_balance(&reward_account);
1

            
1
			// Second claim at 50% vesting
1
			run_to_block(50);
1
			assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(
1
				reward_account.clone()
1
			)));
1
			let balance_after_second = Balances::free_balance(&reward_account);
1

            
1
			// Third claim at 100% vesting
1
			run_to_block(100);
1
			assert_ok!(CrowdloanRewards::claim(RuntimeOrigin::signed(
1
				reward_account.clone()
1
			)));
1
			let final_balance = Balances::free_balance(&reward_account);
1

            
1
			// Should have received all rewards by the end
1
			assert_eq!(
1
				final_balance,
1
				initial_balance + total_reward - initialization_payment
1
			);
			// Each claim should increase balance
1
			assert!(balance_after_first > initial_balance);
1
			assert!(balance_after_second > balance_after_first);
1
			assert!(final_balance > balance_after_second);
			// Final check: all rewards should be claimed
1
			let reward_info = AccountsPayable::<Test>::get(&reward_account).unwrap();
1
			assert_eq!(reward_info.claimed_reward, total_reward);
1
		});
1
}