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

            
17
// #![cfg(feature = "runtime-benchmarks")]
18

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

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

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

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

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

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

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

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

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

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

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

            
109
	asset_id.into()
110
}
111

            
112
#[benchmarks(
113
	where <T as pallet_assets::Config>::Balance: Into<U256>,
114
	T::ForeignAssetType: Into<Option<Location>>,
115
	<T as frame_system::Config>::AccountId: Into<H160> + From<H160>,
116
)]
117
mod benchmarks {
118
	use super::*;
119

            
120
	#[benchmark]
121
	fn approve_assets_to_migrate(n: Linear<1, 100>) -> Result<(), BenchmarkError> {
122
		let assets: Vec<u128> = (0..n)
123
			.map(|i| {
124
				let metadata = T::AssetRegistrarMetadata::default();
125
				let asset_id: u128 = i.into();
126
				T::AssetRegistrar::create_foreign_asset(
127
					asset_id,
128
					1u32.into(),
129
					metadata.clone(),
130
					true,
131
				)
132
				.expect("creating foreign assets should succeed during benchmark preparation");
133
				asset_id
134
			})
135
			.collect();
136

            
137
		#[extrinsic_call]
138
		_(
139
			RawOrigin::Root,
140
			BoundedVec::try_from(assets.clone())
141
				.expect("asset vector should fit within BoundedVec size limit during benchmark"),
142
		);
143

            
144
		for asset_id in assets {
145
			assert!(crate::pallet::ApprovedForeignAssets::<T>::contains_key(
146
				asset_id
147
			));
148
		}
149
		Ok(())
150
	}
151

            
152
	#[benchmark]
153
	fn start_foreign_assets_migration() -> Result<(), BenchmarkError> {
154
		let asset_id = setup_foreign_asset::<T>(1);
155

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

            
162
		#[extrinsic_call]
163
		_(RawOrigin::Signed(account("caller", 0, 0)), asset_id.into());
164

            
165
		assert!(matches!(
166
			crate::pallet::ForeignAssetMigrationStatusValue::<T>::get(),
167
			ForeignAssetMigrationStatus::Migrating(_)
168
		));
169
		Ok(())
170
	}
171

            
172
	#[benchmark]
173
	fn migrate_foreign_asset_balances(n: Linear<1, 1000>) -> Result<(), BenchmarkError> {
174
		let asset_id = setup_foreign_asset::<T>(n);
175

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

            
181
		Pallet::<T>::start_foreign_assets_migration(
182
			RawOrigin::Signed(account("caller", 0, 0)).into(),
183
			asset_id.into(),
184
		)?;
185

            
186
		#[extrinsic_call]
187
		_(RawOrigin::Signed(account("caller", 0, 0)), n + 1);
188

            
189
		match crate::pallet::ForeignAssetMigrationStatusValue::<T>::get() {
190
			ForeignAssetMigrationStatus::Migrating(info) => {
191
				assert_eq!(info.remaining_balances, 0);
192
			}
193
			_ => panic!("Expected Migrating status"),
194
		}
195
		Ok(())
196
	}
197

            
198
	#[benchmark]
199
	fn migrate_foreign_asset_approvals(n: Linear<1, 1000>) -> Result<(), BenchmarkError> {
200
		let asset_id = setup_foreign_asset::<T>(n);
201

            
202
		Pallet::<T>::approve_assets_to_migrate(
203
			RawOrigin::Root.into(),
204
			BoundedVec::try_from(vec![asset_id.clone().into()]).expect("single asset ID should fit within BoundedVec capacity during local assets benchmark")
205
		)?;
206

            
207
		Pallet::<T>::start_foreign_assets_migration(
208
			RawOrigin::Signed(account("caller", 0, 0)).into(),
209
			asset_id.into(),
210
		)?;
211

            
212
		Pallet::<T>::migrate_foreign_asset_balances(
213
			RawOrigin::Signed(account("caller", 0, 0)).into(),
214
			n + 1,
215
		)?;
216

            
217
		#[extrinsic_call]
218
		_(RawOrigin::Signed(account("caller", 0, 0)), n);
219

            
220
		match crate::pallet::ForeignAssetMigrationStatusValue::<T>::get() {
221
			ForeignAssetMigrationStatus::Migrating(info) => {
222
				assert_eq!(info.remaining_approvals, 0);
223
			}
224
			_ => panic!("Expected Migrating status"),
225
		}
226
		Ok(())
227
	}
228

            
229
	#[benchmark]
230
	fn finish_foreign_assets_migration() -> Result<(), BenchmarkError> {
231
		let n = 100u32;
232
		let asset_id = setup_foreign_asset::<T>(n);
233

            
234
		Pallet::<T>::approve_assets_to_migrate(
235
			RawOrigin::Root.into(),
236
			BoundedVec::try_from(vec![asset_id.clone().into()]).expect("single asset ID should fit within BoundedVec capacity during local assets benchmark")
237
		)?;
238

            
239
		Pallet::<T>::start_foreign_assets_migration(
240
			RawOrigin::Signed(account("caller", 0, 0)).into(),
241
			asset_id.into(),
242
		)?;
243

            
244
		Pallet::<T>::migrate_foreign_asset_balances(
245
			RawOrigin::Signed(account("caller", 0, 0)).into(),
246
			n + 1,
247
		)?;
248

            
249
		Pallet::<T>::migrate_foreign_asset_approvals(
250
			RawOrigin::Signed(account("caller", 0, 0)).into(),
251
			n + 1,
252
		)?;
253

            
254
		#[extrinsic_call]
255
		_(RawOrigin::Signed(account("caller", 0, 0)));
256

            
257
		assert_eq!(
258
			crate::pallet::ForeignAssetMigrationStatusValue::<T>::get(),
259
			ForeignAssetMigrationStatus::Idle
260
		);
261
		Ok(())
262
	}
263
}