1
// Copyright 2024 Moonbeam foundation
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
use crate::{
19
	foreign_asset::ForeignAssetMigrationStatus,
20
	mock::{AssetId, Balances},
21
};
22
use frame_support::traits::fungibles::approvals::Inspect;
23
use sp_runtime::{BoundedVec, DispatchError};
24
use xcm::latest::Location;
25
use {
26
	crate::{
27
		mock::{
28
			AccountId, AssetManager, Assets, ExtBuilder, LazyMigrations, MockAssetType,
29
			RuntimeOrigin, Test, ALITH, BOB,
30
		},
31
		Error,
32
	},
33
	frame_support::{assert_noop, assert_ok},
34
	sp_core::{H160, H256},
35
	sp_io::hashing::keccak_256,
36
};
37

            
38
2
fn address_build(seed: u8) -> H160 {
39
2
	let address = H160::from(H256::from(keccak_256(&[seed; 32])));
40
2
	address
41
2
}
42

            
43
1
fn create_dummy_contract_without_metadata(seed: u8) -> H160 {
44
1
	let address = address_build(seed);
45
1
	let dummy_code = vec![1, 2, 3];
46
1
	pallet_evm::AccountCodes::<Test>::insert(address, dummy_code);
47
1
	address
48
1
}
49

            
50
#[test]
51
1
fn test_create_contract_metadata_contract_not_exist() {
52
1
	ExtBuilder::default().build().execute_with(|| {
53
1
		assert_noop!(
54
1
			LazyMigrations::create_contract_metadata(
55
1
				RuntimeOrigin::signed(AccountId::from([45; 20])),
56
1
				address_build(1),
57
1
			),
58
1
			Error::<Test>::ContractNotExist
59
1
		);
60
1
	});
61
1
}
62

            
63
#[test]
64
1
fn test_create_contract_metadata_success_path() {
65
1
	ExtBuilder::default().build().execute_with(|| {
66
1
		// Setup: create a dummy contract
67
1
		let address = create_dummy_contract_without_metadata(1);
68
1

            
69
1
		assert_ok!(LazyMigrations::create_contract_metadata(
70
1
			RuntimeOrigin::signed(AccountId::from([45; 20])),
71
1
			address,
72
1
		));
73

            
74
1
		assert!(pallet_evm::AccountCodesMetadata::<Test>::get(address).is_some());
75

            
76
		// Should not be able to set metadata again
77
1
		assert_noop!(
78
1
			LazyMigrations::create_contract_metadata(
79
1
				RuntimeOrigin::signed(AccountId::from([45; 20])),
80
1
				address,
81
1
			),
82
1
			Error::<Test>::ContractMetadataAlreadySet
83
1
		);
84
1
	});
85
1
}
86

            
87
// Helper function to create a foreign asset with basic metadata
88
17
fn create_old_foreign_asset(location: Location) -> AssetId {
89
17
	let asset = MockAssetType::Xcm(location.clone().into());
90
17
	let asset_id: AssetId = asset.clone().into();
91
17
	// First register asset in asset manager with a Location
92
17
	assert_ok!(AssetManager::register_foreign_asset(
93
17
		RuntimeOrigin::root(),
94
17
		asset,
95
17
		1,
96
17
		1,
97
17
		true,
98
17
	));
99

            
100
	// Set metadata for the asset
101
17
	assert_ok!(Assets::set_metadata(
102
17
		RuntimeOrigin::signed(AssetManager::account_id()),
103
17
		asset_id,
104
17
		b"Test".to_vec(),
105
17
		b"TEST".to_vec(),
106
17
		12,
107
17
	));
108

            
109
17
	asset_id
110
17
}
111

            
112
#[test]
113
1
fn test_approve_foreign_asset_migration_unauthorized() {
114
1
	ExtBuilder::default().build().execute_with(|| {
115
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
116
1
		let asset_id = create_old_foreign_asset(location);
117
1

            
118
1
		// Try to approve migration with non-root origin
119
1
		assert_noop!(
120
1
			LazyMigrations::approve_assets_to_migrate(
121
1
				RuntimeOrigin::signed(BOB),
122
1
				BoundedVec::try_from(vec![asset_id]).unwrap()
123
1
			),
124
1
			DispatchError::BadOrigin
125
1
		);
126
1
	});
127
1
}
128

            
129
#[test]
130
1
fn test_approve_foreign_asset_migration_success() {
131
1
	ExtBuilder::default().build().execute_with(|| {
132
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
133
1
		let asset_id = create_old_foreign_asset(location);
134
1

            
135
1
		// Try to approve migration with root origin
136
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
137
1
			RuntimeOrigin::root(),
138
1
			BoundedVec::try_from(vec![asset_id]).unwrap()
139
1
		));
140

            
141
		// Verify the asset is approved for migration
142
1
		assert!(crate::pallet::ApprovedForeignAssets::<Test>::contains_key(
143
1
			asset_id
144
1
		));
145
1
	});
146
1
}
147

            
148
#[test]
149
1
fn test_start_foreign_asset_migration_success() {
150
1
	ExtBuilder::default().build().execute_with(|| {
151
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
152
1
		let asset_id = create_old_foreign_asset(location);
153
1

            
154
1
		assert_ok!(Assets::mint(
155
1
			RuntimeOrigin::signed(AssetManager::account_id()),
156
1
			asset_id.into(),
157
1
			ALITH.into(),
158
1
			100,
159
1
		));
160

            
161
		// Verify asset is live by calling transfer in pallet assets
162
1
		assert_ok!(Assets::transfer(
163
1
			RuntimeOrigin::signed(ALITH),
164
1
			asset_id.into(),
165
1
			BOB.into(),
166
1
			100,
167
1
		));
168

            
169
		// Approve the migration of the asset
170
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
171
1
			RuntimeOrigin::root(),
172
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
173
1
		));
174

            
175
		// Try to migrate the asset
176
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
177
1
			RuntimeOrigin::signed(ALITH),
178
1
			asset_id,
179
1
		));
180

            
181
		// Verify asset is frozen by calling transfer in pallet assets
182
1
		assert_noop!(
183
1
			Assets::transfer(
184
1
				RuntimeOrigin::signed(ALITH),
185
1
				asset_id.into(),
186
1
				BOB.into(),
187
1
				100,
188
1
			),
189
1
			pallet_assets::Error::<Test>::AssetNotLive
190
1
		);
191

            
192
		// Verify migration status
193
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
194
1
			ForeignAssetMigrationStatus::Migrating(info) => {
195
1
				assert_eq!(info.asset_id, asset_id);
196
1
				assert_eq!(info.remaining_balances, 1);
197
1
				assert_eq!(info.remaining_approvals, 0);
198
			}
199
			_ => panic!("Expected migration status to be Migrating"),
200
		}
201
1
	});
202
1
}
203

            
204
#[test]
205
1
fn test_start_foreign_asset_migration_already_migrating() {
206
1
	ExtBuilder::default().build().execute_with(|| {
207
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
208
1
		let asset_id = create_old_foreign_asset(location);
209
1

            
210
1
		// Approve the migration of the asset
211
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
212
1
			RuntimeOrigin::root(),
213
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
214
1
		));
215

            
216
		// Start first migrationJunction::Parachain(1000)
217
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
218
1
			RuntimeOrigin::signed(ALITH),
219
1
			asset_id,
220
1
		));
221

            
222
		// Try to start another migration while one is in progress
223
1
		assert_noop!(
224
1
			LazyMigrations::start_foreign_assets_migration(RuntimeOrigin::signed(ALITH), 2u128),
225
1
			Error::<Test>::MigrationNotFinished
226
1
		);
227
1
	});
228
1
}
229

            
230
#[test]
231
1
fn test_start_foreign_asset_migration_asset_not_found() {
232
1
	ExtBuilder::default().build().execute_with(|| {
233
1
		// Try to migrate non-existent asset
234
1
		assert_noop!(
235
1
			LazyMigrations::start_foreign_assets_migration(RuntimeOrigin::signed(ALITH), 1u128),
236
1
			Error::<Test>::AssetNotFound
237
1
		);
238
1
	});
239
1
}
240

            
241
#[test]
242
1
fn test_start_foreign_asset_migration_asset_type_not_found() {
243
1
	ExtBuilder::default().build().execute_with(|| {
244
1
		let asset_id = 1u128;
245
1

            
246
1
		// Create asset without registering in asset manager
247
1
		assert_ok!(Assets::create(
248
1
			RuntimeOrigin::signed(ALITH),
249
1
			asset_id.into(),
250
1
			ALITH.into(),
251
1
			1,
252
1
		));
253

            
254
		// Approve the migration of the asset
255
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
256
1
			RuntimeOrigin::root(),
257
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
258
1
		));
259

            
260
1
		assert_noop!(
261
1
			LazyMigrations::start_foreign_assets_migration(RuntimeOrigin::signed(ALITH), asset_id),
262
1
			Error::<Test>::AssetTypeNotFound
263
1
		);
264
1
	});
265
1
}
266

            
267
#[test]
268
1
fn test_start_foreign_asset_migration_with_balances_and_approvals() {
269
1
	ExtBuilder::default().build().execute_with(|| {
270
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
271
1
		let asset_id = create_old_foreign_asset(location);
272
1

            
273
1
		// Add some balances
274
1
		assert_ok!(Assets::mint(
275
1
			RuntimeOrigin::signed(AssetManager::account_id()),
276
1
			asset_id.into(),
277
1
			BOB.into(),
278
1
			100,
279
1
		));
280

            
281
		// Add some approvals
282
1
		assert_ok!(Assets::approve_transfer(
283
1
			RuntimeOrigin::signed(BOB),
284
1
			asset_id.into(),
285
1
			AccountId::from([3; 20]).into(),
286
1
			50,
287
1
		));
288

            
289
		// Approve the migration of the asset
290
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
291
1
			RuntimeOrigin::root(),
292
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
293
1
		));
294

            
295
		// Start migration
296
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
297
1
			RuntimeOrigin::signed(ALITH),
298
1
			asset_id,
299
1
		));
300

            
301
		// Verify migration status includes the balances and approvals
302
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
303
1
			ForeignAssetMigrationStatus::Migrating(info) => {
304
1
				assert_eq!(info.asset_id, asset_id);
305
1
				assert_eq!(info.remaining_balances, 1);
306
1
				assert_eq!(info.remaining_approvals, 1);
307
			}
308
			_ => panic!("Expected migration status to be Migrating"),
309
		}
310
1
	});
311
1
}
312

            
313
#[test]
314
1
fn test_migrate_foreign_asset_balances_without_migration_started() {
315
1
	ExtBuilder::default().build().execute_with(|| {
316
1
		assert_noop!(
317
1
			LazyMigrations::migrate_foreign_asset_balances(RuntimeOrigin::signed(ALITH), 100),
318
1
			Error::<Test>::NoMigrationInProgress
319
1
		);
320
1
	});
321
1
}
322

            
323
#[test]
324
1
fn test_migrate_foreign_asset_balances_zero_limit() {
325
1
	ExtBuilder::default().build().execute_with(|| {
326
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
327
1
		let asset_id = create_old_foreign_asset(location);
328
1

            
329
1
		// Approve the migration of the asset
330
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
331
1
			RuntimeOrigin::root(),
332
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
333
1
		));
334

            
335
		// Start migration
336
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
337
1
			RuntimeOrigin::signed(ALITH),
338
1
			asset_id,
339
1
		));
340

            
341
1
		assert_noop!(
342
1
			LazyMigrations::migrate_foreign_asset_balances(RuntimeOrigin::signed(ALITH), 0),
343
1
			Error::<Test>::LimitCannotBeZero
344
1
		);
345
1
	});
346
1
}
347

            
348
#[test]
349
1
fn test_migrate_foreign_asset_balances_single_account() {
350
1
	ExtBuilder::default().build().execute_with(|| {
351
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
352
1
		let asset_id = create_old_foreign_asset(location);
353
1

            
354
1
		// Mint some tokens to an account
355
1
		assert_ok!(Assets::mint(
356
1
			RuntimeOrigin::signed(AssetManager::account_id()),
357
1
			asset_id.into(),
358
1
			BOB.into(),
359
1
			100,
360
1
		));
361

            
362
		// Approve the migration of the asset
363
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
364
1
			RuntimeOrigin::root(),
365
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
366
1
		));
367

            
368
		// Start migration
369
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
370
1
			RuntimeOrigin::signed(ALITH),
371
1
			asset_id,
372
1
		));
373

            
374
		// Migrate balances
375
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_balances(
376
1
			RuntimeOrigin::signed(ALITH),
377
1
			10
378
1
		));
379

            
380
		// Verify migration status
381
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
382
1
			ForeignAssetMigrationStatus::Migrating(info) => {
383
1
				assert_eq!(info.asset_id, asset_id);
384
1
				assert_eq!(info.remaining_balances, 0);
385
1
				assert_eq!(info.remaining_approvals, 0);
386
			}
387
			_ => panic!("Expected migration status to be Migrating"),
388
		}
389

            
390
		// Verify the balance was migrated by
391
1
		assert_eq!(Assets::balance(asset_id, BOB), 0);
392
1
	});
393
1
}
394

            
395
#[test]
396
1
fn test_migrate_foreign_asset_balances_multiple_accounts() {
397
1
	ExtBuilder::default().build().execute_with(|| {
398
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
399
1
		let asset_id = create_old_foreign_asset(location);
400

            
401
		// Mint tokens to multiple accounts
402
6
		for i in 1..=5 {
403
5
			assert_ok!(Assets::mint(
404
5
				RuntimeOrigin::signed(AssetManager::account_id()),
405
5
				asset_id.into(),
406
5
				AccountId::from([i as u8; 20]).into(),
407
5
				100 * i as u128,
408
5
			));
409
		}
410

            
411
		// Approve the migration of the asset
412
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
413
1
			RuntimeOrigin::root(),
414
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
415
1
		));
416

            
417
		// Start migration
418
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
419
1
			RuntimeOrigin::signed(ALITH),
420
1
			asset_id,
421
1
		));
422

            
423
		// Migrate balances in batches
424
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_balances(
425
1
			RuntimeOrigin::signed(ALITH),
426
1
			3
427
1
		));
428

            
429
		// Check intermediate state
430
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
431
1
			ForeignAssetMigrationStatus::Migrating(info) => {
432
1
				assert_eq!(info.asset_id, asset_id);
433
1
				assert_eq!(info.remaining_balances, 2);
434
1
				assert_eq!(info.remaining_approvals, 0);
435
			}
436
			_ => panic!("Expected migration status to be Migrating"),
437
		}
438

            
439
		// Migrate remaining balances
440
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_balances(
441
1
			RuntimeOrigin::signed(ALITH),
442
1
			2
443
1
		));
444

            
445
		// Verify final state
446
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
447
1
			ForeignAssetMigrationStatus::Migrating(info) => {
448
1
				assert_eq!(info.asset_id, asset_id);
449
1
				assert_eq!(info.remaining_balances, 0);
450
1
				assert_eq!(info.remaining_approvals, 0);
451
			}
452
			_ => panic!("Expected migration status to be Migrating"),
453
		}
454

            
455
		// Verify all balances were migrated correctly
456
6
		for i in 1..=5 {
457
5
			assert_eq!(Assets::balance(asset_id, AccountId::from([i as u8; 20])), 0);
458
		}
459
1
	});
460
1
}
461

            
462
#[test]
463
1
fn test_migrate_foreign_asset_balances_with_reserved_deposits() {
464
1
	ExtBuilder::default().build().execute_with(|| {
465
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
466
1
		let asset_id = create_old_foreign_asset(location);
467
1

            
468
1
		// Create account with reserved deposit
469
1
		let account = BOB;
470
1
		assert_ok!(Assets::touch(
471
1
			RuntimeOrigin::signed(account.clone()),
472
1
			asset_id.into(),
473
1
		));
474

            
475
		// Mint some tokens
476
1
		assert_ok!(Assets::mint(
477
1
			RuntimeOrigin::signed(AssetManager::account_id()),
478
1
			asset_id.into(),
479
1
			account.clone().into(),
480
1
			100,
481
1
		));
482

            
483
		// Check initial reserved balance
484
1
		let initial_reserved = Balances::reserved_balance(&account);
485
1
		assert!(initial_reserved > 0);
486

            
487
		// Approve the migration of the asset
488
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
489
1
			RuntimeOrigin::root(),
490
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
491
1
		));
492

            
493
		// Start migration
494
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
495
1
			RuntimeOrigin::signed(ALITH),
496
1
			asset_id,
497
1
		));
498

            
499
		// Migrate balances
500
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_balances(
501
1
			RuntimeOrigin::signed(ALITH),
502
1
			10
503
1
		));
504

            
505
		// Verify the balance was migrated
506
1
		assert_eq!(Assets::balance(asset_id, account.clone()), 0);
507

            
508
		// Verify the deposit was unreserved
509
1
		assert_eq!(Balances::reserved_balance(&account), 0);
510
1
	});
511
1
}
512

            
513
#[test]
514
1
fn test_migrate_foreign_asset_approvals_without_migration_started() {
515
1
	ExtBuilder::default().build().execute_with(|| {
516
1
		assert_noop!(
517
1
			LazyMigrations::migrate_foreign_asset_approvals(
518
1
				RuntimeOrigin::signed(AccountId::from([45; 20])),
519
1
				100
520
1
			),
521
1
			Error::<Test>::NoMigrationInProgress
522
1
		);
523
1
	});
524
1
}
525

            
526
#[test]
527
1
fn test_migrate_foreign_asset_approvals_zero_limit() {
528
1
	ExtBuilder::default().build().execute_with(|| {
529
1
		// Create and start migration for an asset
530
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
531
1
		let asset_id = create_old_foreign_asset(location);
532
1

            
533
1
		// Approve the migration of the asset
534
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
535
1
			RuntimeOrigin::root(),
536
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
537
1
		));
538

            
539
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
540
1
			RuntimeOrigin::signed(ALITH),
541
1
			asset_id,
542
1
		));
543

            
544
1
		assert_noop!(
545
1
			LazyMigrations::migrate_foreign_asset_approvals(
546
1
				RuntimeOrigin::signed(AccountId::from([45; 20])),
547
1
				0
548
1
			),
549
1
			Error::<Test>::LimitCannotBeZero
550
1
		);
551
1
	});
552
1
}
553

            
554
#[test]
555
1
fn test_migrate_foreign_asset_approvals_single_approval() {
556
1
	ExtBuilder::default().build().execute_with(|| {
557
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
558
1
		let asset_id = create_old_foreign_asset(location);
559
1

            
560
1
		// Create an approval
561
1
		assert_ok!(Assets::approve_transfer(
562
1
			RuntimeOrigin::signed(BOB),
563
1
			asset_id.into(),
564
1
			AccountId::from([3; 20]).into(),
565
1
			50,
566
1
		));
567

            
568
		// Approve the migration of the asset
569
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
570
1
			RuntimeOrigin::root(),
571
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
572
1
		));
573

            
574
		// Start migration
575
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
576
1
			RuntimeOrigin::signed(ALITH),
577
1
			asset_id,
578
1
		));
579

            
580
		// Check initial migration status
581
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
582
1
			ForeignAssetMigrationStatus::Migrating(info) => {
583
1
				assert_eq!(info.remaining_approvals, 1);
584
			}
585
			_ => panic!("Expected migration status to be Migrating"),
586
		}
587

            
588
		// Initial reserved balance for approval
589
1
		let initial_reserved = Balances::reserved_balance(&BOB);
590
1
		assert!(initial_reserved > 0);
591

            
592
		// Migrate approvals
593
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_approvals(
594
1
			RuntimeOrigin::signed(ALITH),
595
1
			10
596
1
		));
597

            
598
		// Check final migration status
599
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
600
1
			ForeignAssetMigrationStatus::Migrating(info) => {
601
1
				assert_eq!(info.remaining_approvals, 0);
602
			}
603
			_ => panic!("Expected migration status to be Migrating"),
604
		}
605

            
606
		// Verify the deposit was unreserved
607
1
		assert_eq!(Balances::reserved_balance(&BOB), 0);
608
1
	});
609
1
}
610

            
611
#[test]
612
1
fn test_migrate_foreign_asset_approvals_multiple_approvals() {
613
1
	ExtBuilder::default().build().execute_with(|| {
614
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
615
1
		let asset_id = create_old_foreign_asset(location);
616

            
617
		// Create multiple approvals from different accounts
618
6
		for i in 1..=5 {
619
5
			let owner = AccountId::from([i as u8; 20]);
620
5
			// Add balance for each account
621
5
			assert_ok!(Balances::force_set_balance(
622
5
				RuntimeOrigin::root(),
623
5
				owner.clone(),
624
5
				100 * i as u128,
625
5
			));
626

            
627
5
			let spender = AccountId::from([i as u8 + 1; 20]);
628
5
			assert_ok!(Assets::approve_transfer(
629
5
				RuntimeOrigin::signed(owner.clone()),
630
5
				asset_id.into(),
631
5
				spender.into(),
632
5
				50 * i as u128,
633
5
			));
634
		}
635

            
636
		// Approve the migration of the asset
637
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
638
1
			RuntimeOrigin::root(),
639
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
640
1
		));
641

            
642
		// Start migration
643
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
644
1
			RuntimeOrigin::signed(ALITH),
645
1
			asset_id,
646
1
		));
647

            
648
		// Check initial migration status
649
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
650
1
			ForeignAssetMigrationStatus::Migrating(info) => {
651
1
				assert_eq!(info.remaining_approvals, 5);
652
			}
653
			_ => panic!("Expected migration status to be Migrating"),
654
		}
655

            
656
		// Migrate approvals in batches
657
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_approvals(
658
1
			RuntimeOrigin::signed(ALITH),
659
1
			3
660
1
		));
661

            
662
		// Check intermediate state
663
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
664
1
			ForeignAssetMigrationStatus::Migrating(info) => {
665
1
				assert_eq!(info.remaining_approvals, 2);
666
			}
667
			_ => panic!("Expected migration status to be Migrating"),
668
		}
669

            
670
		// Migrate remaining approvals
671
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_approvals(
672
1
			RuntimeOrigin::signed(ALITH),
673
1
			2
674
1
		));
675

            
676
		// Check final migration status
677
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
678
1
			ForeignAssetMigrationStatus::Migrating(info) => {
679
1
				assert_eq!(info.remaining_approvals, 0);
680
			}
681
			_ => panic!("Expected migration status to be Migrating"),
682
		}
683

            
684
		// Verify all deposits were unreserved
685
6
		for i in 1..=5 {
686
5
			let owner = AccountId::from([i as u8; 20]);
687
5
			assert_eq!(Balances::reserved_balance(&owner), 0);
688
		}
689
1
	});
690
1
}
691

            
692
#[test]
693
1
fn test_migrate_foreign_asset_approvals_with_balances() {
694
1
	ExtBuilder::default().build().execute_with(|| {
695
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
696
1
		let asset_id = create_old_foreign_asset(location);
697
1

            
698
1
		// Create balance and approval for account
699
1
		assert_ok!(Assets::mint(
700
1
			RuntimeOrigin::signed(AssetManager::account_id()),
701
1
			asset_id.into(),
702
1
			BOB.into(),
703
1
			100,
704
1
		));
705

            
706
1
		assert_ok!(Assets::approve_transfer(
707
1
			RuntimeOrigin::signed(BOB),
708
1
			asset_id.into(),
709
1
			AccountId::from([3; 20]).into(),
710
1
			50,
711
1
		));
712

            
713
		// Approve the migration of the asset
714
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
715
1
			RuntimeOrigin::root(),
716
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
717
1
		));
718

            
719
		// Start migration
720
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
721
1
			RuntimeOrigin::signed(ALITH),
722
1
			asset_id,
723
1
		));
724

            
725
		// Migrate approvals first
726
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_approvals(
727
1
			RuntimeOrigin::signed(ALITH),
728
1
			10
729
1
		));
730

            
731
		// Check that approvals were migrated but balances remain
732
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
733
1
			ForeignAssetMigrationStatus::Migrating(info) => {
734
1
				assert_eq!(info.remaining_approvals, 0);
735
1
				assert_eq!(info.remaining_balances, 1);
736
			}
737
			_ => panic!("Expected migration status to be Migrating"),
738
		}
739

            
740
		// Migrate balances
741
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_balances(
742
1
			RuntimeOrigin::signed(ALITH),
743
1
			10
744
1
		));
745

            
746
		// Verify final state
747
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
748
1
			ForeignAssetMigrationStatus::Migrating(info) => {
749
1
				assert_eq!(info.remaining_approvals, 0);
750
1
				assert_eq!(info.remaining_balances, 0);
751
			}
752
			_ => panic!("Expected migration status to be Migrating"),
753
		}
754
1
	});
755
1
}
756

            
757
#[test]
758
1
fn test_migrate_foreign_asset_approvals_exceed_limit() {
759
1
	ExtBuilder::default().build().execute_with(|| {
760
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
761
1
		let asset_id = create_old_foreign_asset(location);
762

            
763
11
		for i in 1..=10 {
764
10
			let owner = AccountId::from([i as u8; 20]);
765
10
			// Add balance and approval for each account
766
10
			assert_ok!(Balances::force_set_balance(
767
10
				RuntimeOrigin::root(),
768
10
				owner.clone(),
769
10
				100 * i as u128,
770
10
			));
771

            
772
10
			let spender = AccountId::from([i as u8 + 1; 20]);
773
10
			assert_ok!(Assets::approve_transfer(
774
10
				RuntimeOrigin::signed(owner.clone()),
775
10
				asset_id.into(),
776
10
				spender.into(),
777
10
				50 * i as u128,
778
10
			));
779
		}
780

            
781
		// Approve the migration of the asset
782
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
783
1
			RuntimeOrigin::root(),
784
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
785
1
		));
786

            
787
		// Start migration
788
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
789
1
			RuntimeOrigin::signed(ALITH),
790
1
			asset_id,
791
1
		));
792

            
793
		// Migrate with limit less than total approvals
794
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_approvals(
795
1
			RuntimeOrigin::signed(ALITH),
796
1
			5
797
1
		));
798

            
799
		// Verify partial migration
800
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
801
1
			ForeignAssetMigrationStatus::Migrating(info) => {
802
1
				assert_eq!(info.remaining_approvals, 5);
803
			}
804
			_ => panic!("Expected migration status to be Migrating"),
805
		}
806
1
	});
807
1
}
808

            
809
#[test]
810
1
fn test_finish_foreign_assets_migration_success() {
811
1
	ExtBuilder::default().build().execute_with(|| {
812
1
		// Setup: Create and start migration for an asset
813
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
814
1
		let asset_id = create_old_foreign_asset(location);
815
1

            
816
1
		// Create balance and approval for account
817
1
		assert_ok!(Assets::mint(
818
1
			RuntimeOrigin::signed(AssetManager::account_id()),
819
1
			asset_id.into(),
820
1
			BOB.into(),
821
1
			100,
822
1
		));
823

            
824
1
		assert_ok!(Assets::approve_transfer(
825
1
			RuntimeOrigin::signed(BOB),
826
1
			asset_id.into(),
827
1
			AccountId::from([3; 20]).into(),
828
1
			50,
829
1
		));
830

            
831
		// Initial balances
832
1
		assert_eq!(Assets::balance(asset_id, BOB), 100);
833
1
		assert!(Balances::reserved_balance(&BOB) > 0);
834
1
		assert_eq!(
835
1
			Assets::allowance(asset_id, &BOB, &AccountId::from([3; 20])),
836
1
			50
837
1
		);
838
1
		assert!(Balances::reserved_balance(&AssetManager::account_id()) > 0);
839

            
840
		// Approve the migration of the asset
841
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
842
1
			RuntimeOrigin::root(),
843
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
844
1
		));
845

            
846
		// Start and complete migration
847
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
848
1
			RuntimeOrigin::signed(ALITH),
849
1
			asset_id,
850
1
		));
851

            
852
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_approvals(
853
1
			RuntimeOrigin::signed(ALITH),
854
1
			10
855
1
		));
856
1
		assert_ok!(LazyMigrations::migrate_foreign_asset_balances(
857
1
			RuntimeOrigin::signed(ALITH),
858
1
			10
859
1
		));
860
1
		assert_ok!(LazyMigrations::finish_foreign_assets_migration(
861
1
			RuntimeOrigin::signed(ALITH)
862
1
		));
863

            
864
		// Verify status is reset to Idle
865
1
		assert_eq!(
866
1
			crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get(),
867
1
			ForeignAssetMigrationStatus::Idle
868
1
		);
869

            
870
		// Verify all deposits are unreserved
871
1
		assert_eq!(Balances::reserved_balance(&BOB), 0);
872
1
		assert_eq!(Balances::reserved_balance(&AssetManager::account_id()), 0);
873

            
874
		// Verify the old asset is completely removed
875
1
		assert!(pallet_assets::Asset::<Test>::get(asset_id).is_none());
876
1
		assert_eq!(pallet_assets::Metadata::<Test>::get(asset_id).deposit, 0);
877
1
	});
878
1
}
879

            
880
#[test]
881
1
fn test_finish_foreign_assets_migration_no_migration_in_progress() {
882
1
	ExtBuilder::default().build().execute_with(|| {
883
1
		assert_noop!(
884
1
			LazyMigrations::finish_foreign_assets_migration(RuntimeOrigin::signed(ALITH)),
885
1
			Error::<Test>::NoMigrationInProgress
886
1
		);
887
1
	});
888
1
}
889

            
890
#[test]
891
1
fn test_finish_foreign_assets_migration_with_remaining_balances() {
892
1
	ExtBuilder::default().build().execute_with(|| {
893
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
894
1
		let asset_id = create_old_foreign_asset(location);
895
1

            
896
1
		// Create balance but no approvals
897
1
		assert_ok!(Assets::mint(
898
1
			RuntimeOrigin::signed(AssetManager::account_id()),
899
1
			asset_id.into(),
900
1
			BOB.into(),
901
1
			100,
902
1
		));
903

            
904
		// Approve the migration of the asset
905
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
906
1
			RuntimeOrigin::root(),
907
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
908
1
		));
909

            
910
		// Start migration but don't migrate balances
911
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
912
1
			RuntimeOrigin::signed(ALITH),
913
1
			asset_id,
914
1
		));
915

            
916
1
		assert_noop!(
917
1
			LazyMigrations::finish_foreign_assets_migration(RuntimeOrigin::signed(ALITH)),
918
1
			Error::<Test>::MigrationNotFinished
919
1
		);
920

            
921
		// Verify we're still in migrating state
922
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
923
1
			ForeignAssetMigrationStatus::Migrating(info) => {
924
1
				assert_eq!(info.remaining_balances, 1);
925
			}
926
			_ => panic!("Expected migration status to be Migrating"),
927
		}
928
1
	});
929
1
}
930

            
931
#[test]
932
1
fn test_finish_foreign_assets_migration_with_remaining_approvals() {
933
1
	ExtBuilder::default().build().execute_with(|| {
934
1
		let location = xcm::latest::Location::new(1, [xcm::latest::Junction::Parachain(1000)]);
935
1
		let asset_id = create_old_foreign_asset(location);
936
1

            
937
1
		// Create approval but no balances
938
1
		assert_ok!(Assets::approve_transfer(
939
1
			RuntimeOrigin::signed(BOB),
940
1
			asset_id.into(),
941
1
			AccountId::from([3; 20]).into(),
942
1
			50,
943
1
		));
944

            
945
		// Approve the migration of the asset
946
1
		assert_ok!(LazyMigrations::approve_assets_to_migrate(
947
1
			RuntimeOrigin::root(),
948
1
			BoundedVec::try_from(vec![asset_id]).unwrap(),
949
1
		));
950

            
951
		// Start migration but don't migrate approvals
952
1
		assert_ok!(LazyMigrations::start_foreign_assets_migration(
953
1
			RuntimeOrigin::signed(ALITH),
954
1
			asset_id,
955
1
		));
956

            
957
1
		assert_noop!(
958
1
			LazyMigrations::finish_foreign_assets_migration(RuntimeOrigin::signed(ALITH)),
959
1
			Error::<Test>::MigrationNotFinished
960
1
		);
961

            
962
		// Verify we're still in migrating state
963
1
		match crate::pallet::ForeignAssetMigrationStatusValue::<Test>::get() {
964
1
			ForeignAssetMigrationStatus::Migrating(info) => {
965
1
				assert_eq!(info.remaining_approvals, 1);
966
			}
967
			_ => panic!("Expected migration status to be Migrating"),
968
		}
969
1
	});
970
1
}