1
// Copyright 2025 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
// #![cfg(feature = "runtime-benchmarks")]
17

            
18
use crate::{foreign_asset::ForeignAssetMigrationStatus, Call, Config, Pallet};
19
use frame_benchmarking::{account, benchmarks};
20
use frame_support::traits::Currency;
21
use frame_support::BoundedVec;
22
use frame_system::RawOrigin;
23
use pallet_asset_manager::AssetRegistrar;
24
use sp_core::H160;
25
use sp_core::{Get, U256};
26
use sp_runtime::traits::StaticLookup;
27
use sp_runtime::Saturating;
28
use sp_std::vec;
29
use sp_std::vec::Vec;
30
use xcm::latest::prelude::*;
31

            
32
fn setup_foreign_asset<T: Config>(n_accounts: u32) -> T::AssetIdParameter {
33
	let asset_type = T::ForeignAssetType::default();
34
	let metadata = T::AssetRegistrarMetadata::default();
35
	let asset_id = asset_type.clone().into();
36

            
37
	let caller: T::AccountId = pallet_asset_manager::Pallet::<T>::account_id();
38
	let caller_lookup = T::Lookup::unlookup(caller.clone());
39
	let root: T::RuntimeOrigin = RawOrigin::Root.into();
40

            
41
	// Register in asset manager
42
	let _ = pallet_asset_manager::Pallet::<T>::register_foreign_asset(
43
		root.clone(),
44
		asset_type,
45
		metadata,
46
		<T as pallet_asset_manager::Config>::Balance::from(1u32),
47
		true,
48
	)
49
	.expect("registering foreign asset should succeed during benchmark setup");
50

            
51
	let _ = <T as pallet_assets::Config>::Currency::deposit_creating(
52
		&caller,
53
		<T as pallet_assets::Config>::MetadataDepositBase::get()
54
			.saturating_add(
55
				<T as pallet_assets::Config>::MetadataDepositPerByte::get()
56
					.saturating_mul((T::StringLimit::get() as u32).into()),
57
			)
58
			.saturating_mul(2u32.into()),
59
	);
60

            
61
	let dummy = Vec::from_iter((0..T::StringLimit::get() as usize).map(|_| 0u8));
62
	let _ = pallet_assets::Pallet::<T>::set_metadata(
63
		RawOrigin::Signed(caller.clone()).into(),
64
		asset_id.clone().into(),
65
		dummy.clone(),
66
		dummy,
67
		18,
68
	)
69
	.expect("setting asset metadata should succeed during benchmark setup");
70

            
71
	// Create approval
72
	pallet_assets::Pallet::<T>::mint(
73
		RawOrigin::Signed(caller.clone()).into(),
74
		asset_id.clone().into(),
75
		caller_lookup,
76
		(100 * (n_accounts + 1)).into(),
77
	)
78
	.expect("minting assets should succeed during benchmark setup");
79

            
80
	// Setup n accounts with balances and approvals
81
	for i in 0..n_accounts {
82
		let user: T::AccountId = account("user", i, 0);
83
		let user_lookup = T::Lookup::unlookup(user.clone());
84

            
85
		// Mint assets
86
		let _ = pallet_assets::Pallet::<T>::mint(
87
			RawOrigin::Signed(caller.clone()).into(),
88
			asset_id.clone().into(),
89
			user_lookup,
90
			100u32.into(),
91
		)
92
		.expect("minting tokens to user accounts should succeed during benchmark setup");
93

            
94
		let spender: T::AccountId = account("spender", i, 0);
95
		let spender_lookup = T::Lookup::unlookup(spender.clone());
96
		let enough = <T as pallet_assets::Config>::Currency::minimum_balance();
97
		<T as pallet_assets::Config>::Currency::make_free_balance_be(&spender, enough);
98

            
99
		let _ = pallet_assets::Pallet::<T>::approve_transfer(
100
			RawOrigin::Signed(caller.clone()).into(),
101
			asset_id.clone().into(),
102
			spender_lookup,
103
			5u32.into(),
104
		)
105
		.expect("approving transfer allowance should succeed during benchmark setup");
106
	}
107

            
108
	asset_id.into()
109
}
110

            
111
benchmarks! {
112
	where_clause {
113
		where
114
		<T as pallet_assets::Config>::Balance: Into<U256>,
115
		T::ForeignAssetType: Into<Option<Location>>,
116
		<T as frame_system::Config>::AccountId: Into<H160> + From<H160>,
117
	}
118
	approve_assets_to_migrate {
119
		let n in 1 .. 100u32;
120
		let assets: Vec<u128> = (0..n).map(|i| {
121
			let metadata = T::AssetRegistrarMetadata::default();
122
			let asset_id: u128 = i.into();
123
			T::AssetRegistrar::create_foreign_asset(
124
				asset_id,
125
				1u32.into(),
126
				metadata.clone(),
127
				true,
128
			).expect("creating foreign assets should succeed during benchmark preparation");
129
			asset_id
130
		}).collect();
131
	}: _(RawOrigin::Root, BoundedVec::try_from(assets.clone()).expect("asset vector should fit within BoundedVec size limit during benchmark"))
132
	verify {
133
		for asset_id in assets {
134
			assert!(crate::pallet::ApprovedForeignAssets::<T>::contains_key(asset_id));
135
		}
136
	}
137

            
138
	start_foreign_assets_migration {
139
		let asset_id = setup_foreign_asset::<T>(1);
140

            
141
		Pallet::<T>::approve_assets_to_migrate(
142
			RawOrigin::Root.into(),
143
			BoundedVec::try_from(vec![asset_id.clone().into()]).expect("single asset ID should fit within BoundedVec capacity during benchmark")
144
		)?;
145
	}: _(RawOrigin::Signed(account("caller", 0, 0)), asset_id.into())
146
	verify {
147
		assert!(matches!(
148
			crate::pallet::ForeignAssetMigrationStatusValue::<T>::get(),
149
			ForeignAssetMigrationStatus::Migrating(_)
150
		));
151
	}
152

            
153
	migrate_foreign_asset_balances {
154
		let n in 1 .. 1000u32;
155
		let asset_id = setup_foreign_asset::<T>(n);
156

            
157
		Pallet::<T>::approve_assets_to_migrate(
158
			RawOrigin::Root.into(),
159
			BoundedVec::try_from(vec![asset_id.clone().into()]).expect("single asset ID should fit within BoundedVec capacity during local assets benchmark")
160
		)?;
161

            
162
		Pallet::<T>::start_foreign_assets_migration(
163
			RawOrigin::Signed(account("caller", 0, 0)).into(),
164
			asset_id.into()
165
		)?;
166
	}: _(RawOrigin::Signed(account("caller", 0, 0)), n + 1)
167
	verify {
168
		match crate::pallet::ForeignAssetMigrationStatusValue::<T>::get() {
169
			ForeignAssetMigrationStatus::Migrating(info) => {
170
				assert_eq!(info.remaining_balances, 0);
171
			},
172
			_ => panic!("Expected Migrating status"),
173
		}
174
	}
175

            
176
	migrate_foreign_asset_approvals {
177
		let n in 1 .. 1000u32;
178
		let asset_id = setup_foreign_asset::<T>(n);
179

            
180
		Pallet::<T>::approve_assets_to_migrate(
181
			RawOrigin::Root.into(),
182
			BoundedVec::try_from(vec![asset_id.clone().into()]).expect("single asset ID should fit within BoundedVec capacity during local assets benchmark")
183
		)?;
184

            
185
		Pallet::<T>::start_foreign_assets_migration(
186
			RawOrigin::Signed(account("caller", 0, 0)).into(),
187
			asset_id.into()
188
		)?;
189

            
190
		Pallet::<T>::migrate_foreign_asset_balances(
191
			RawOrigin::Signed(account("caller", 0, 0)).into(),
192
			n + 1
193
		)?;
194
	}: _(RawOrigin::Signed(account("caller", 0, 0)), n)
195
	verify {
196
		match crate::pallet::ForeignAssetMigrationStatusValue::<T>::get() {
197
			ForeignAssetMigrationStatus::Migrating(info) => {
198
				assert_eq!(info.remaining_approvals, 0);
199
			},
200
			_ => panic!("Expected Migrating status"),
201
		}
202
	}
203

            
204
	finish_foreign_assets_migration {
205
		let n = 100u32;
206
		let asset_id = setup_foreign_asset::<T>(n);
207

            
208
		Pallet::<T>::approve_assets_to_migrate(
209
			RawOrigin::Root.into(),
210
			BoundedVec::try_from(vec![asset_id.clone().into()]).expect("single asset ID should fit within BoundedVec capacity during local assets benchmark")
211
		)?;
212

            
213
		Pallet::<T>::start_foreign_assets_migration(
214
			RawOrigin::Signed(account("caller", 0, 0)).into(),
215
			asset_id.into()
216
		)?;
217

            
218
		Pallet::<T>::migrate_foreign_asset_balances(
219
			RawOrigin::Signed(account("caller", 0, 0)).into(),
220
			n + 1
221
		)?;
222

            
223
		Pallet::<T>::migrate_foreign_asset_approvals(
224
			RawOrigin::Signed(account("caller", 0, 0)).into(),
225
			n + 1
226
		)?;
227
	}: _(RawOrigin::Signed(account("caller", 0, 0)))
228
	verify {
229
		assert_eq!(
230
			crate::pallet::ForeignAssetMigrationStatusValue::<T>::get(),
231
			ForeignAssetMigrationStatus::Idle
232
		);
233
	}
234
}