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
//! # Functions for handling foreign asset migrations
18

            
19
use super::*;
20
use frame_support::sp_runtime::Saturating;
21
use frame_support::traits::{fungibles::metadata::Inspect, ReservableCurrency};
22
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
23
use sp_core::{H160, U256};
24

            
25
120
#[derive(Debug, Encode, Decode, scale_info::TypeInfo, PartialEq, MaxEncodedLen)]
26
pub enum ForeignAssetMigrationStatus {
27
1
	/// No migration in progress
28
	Idle,
29
31
	/// Migrating a foreign asset in progress
30
	Migrating(ForeignAssetMigrationInfo),
31
}
32

            
33
impl Default for ForeignAssetMigrationStatus {
34
68
	fn default() -> Self {
35
68
		ForeignAssetMigrationStatus::Idle
36
68
	}
37
}
38

            
39
180
#[derive(Debug, Encode, Decode, scale_info::TypeInfo, PartialEq, MaxEncodedLen)]
40
pub(super) struct ForeignAssetMigrationInfo {
41
	pub(super) asset_id: u128,
42
	pub(super) remaining_balances: u32,
43
	pub(super) remaining_approvals: u32,
44
}
45

            
46
impl<T: Config> Pallet<T>
47
where
48
	<T as pallet_assets::Config>::Balance: Into<U256>,
49
	<T as pallet_asset_manager::Config>::ForeignAssetType: Into<Option<Location>>,
50
	<T as frame_system::Config>::AccountId: Into<H160> + From<H160>,
51
{
52
	/// Start a foreign asset migration by freezing the asset and creating the SC with the moonbeam
53
	/// foreign assets pallet.
54
18
	pub(super) fn do_start_foreign_asset_migration(asset_id: u128) -> DispatchResult {
55
18
		ForeignAssetMigrationStatusValue::<T>::try_mutate(|status| -> DispatchResult {
56
18
			ensure!(
57
18
				*status == ForeignAssetMigrationStatus::Idle,
58
1
				Error::<T>::MigrationNotFinished
59
			);
60

            
61
			// ensure asset_id is in the approved list
62
17
			ensure!(
63
17
				ApprovedForeignAssets::<T>::contains_key(asset_id),
64
1
				Error::<T>::AssetNotFound
65
			);
66

            
67
			// Freeze the asset
68
16
			pallet_assets::Asset::<T>::try_mutate_exists(asset_id, |maybe_details| {
69
16
				let details = maybe_details.as_mut().ok_or(Error::<T>::AssetNotFound)?;
70

            
71
16
				details.status = pallet_assets::AssetStatus::Frozen;
72
16

            
73
16
				let decimals = pallet_assets::Pallet::<T>::decimals(asset_id);
74
16
				let symbol = pallet_assets::Pallet::<T>::symbol(asset_id)
75
16
					.try_into()
76
16
					.map_err(|_| Error::<T>::SymbolTooLong)?;
77
16
				let name = <pallet_assets::Pallet<T> as Inspect<_>>::name(asset_id)
78
16
					.try_into()
79
16
					.map_err(|_| Error::<T>::NameTooLong)?;
80
16
				let asset_type = pallet_asset_manager::AssetIdType::<T>::take(asset_id)
81
16
					.ok_or(Error::<T>::AssetTypeNotFound)?;
82
15
				let xcm_location: Location =
83
15
					asset_type.into().ok_or(Error::<T>::LocationNotFound)?;
84

            
85
				// Remove the precompile for the old foreign asset.
86
				// Cleaning the precompile is done by removing the code and metadata
87
15
				let contract_addr =
88
15
					pallet_moonbeam_foreign_assets::Pallet::<T>::contract_address_from_asset_id(
89
15
						asset_id,
90
15
					);
91
15
				pallet_evm::AccountCodes::<T>::remove(contract_addr);
92
15
				pallet_evm::AccountCodesMetadata::<T>::remove(contract_addr);
93
15

            
94
15
				// Create the SC for the asset with moonbeam foreign assets pallet
95
15
				pallet_moonbeam_foreign_assets::Pallet::<T>::register_foreign_asset(
96
15
					asset_id,
97
15
					xcm_location,
98
15
					decimals,
99
15
					symbol,
100
15
					name,
101
15
				)?;
102

            
103
15
				*status = ForeignAssetMigrationStatus::Migrating(ForeignAssetMigrationInfo {
104
15
					asset_id,
105
15
					remaining_balances: details.accounts,
106
15
					remaining_approvals: details.approvals,
107
15
				});
108
15

            
109
15
				Ok(())
110
16
			})
111
18
		})
112
18
	}
113

            
114
8
	pub(super) fn do_migrate_foreign_asset_balances(limit: u32) -> DispatchResult {
115
		use pallet_assets::ExistenceReason::*;
116

            
117
8
		ensure!(limit != 0, Error::<T>::LimitCannotBeZero);
118

            
119
7
		ForeignAssetMigrationStatusValue::<T>::try_mutate(|status| -> DispatchResult {
120
7
			let info = match status {
121
6
				ForeignAssetMigrationStatus::Migrating(info) => info,
122
1
				_ => return Err(Error::<T>::NoMigrationInProgress.into()),
123
			};
124

            
125
6
			pallet_assets::Account::<T>::drain_prefix(info.asset_id)
126
6
				.take(limit as usize)
127
9
				.try_for_each(|(who, mut asset)| {
128
					// Unreserve the deposit
129
9
					if let Some((depositor, deposit)) = asset.reason.take_deposit_from() {
130
						<T as pallet_assets::Config>::Currency::unreserve(&depositor, deposit);
131
9
					} else if let Some(deposit) = asset.reason.take_deposit() {
132
1
						<T as pallet_assets::Config>::Currency::unreserve(&who, deposit);
133
8
					}
134

            
135
9
					match asset.reason {
136
						Consumer => frame_system::Pallet::<T>::dec_consumers(&who),
137
8
						Sufficient => {
138
8
							frame_system::Pallet::<T>::dec_sufficients(&who);
139
8
						}
140
1
						_ => {}
141
					};
142

            
143
9
					let zero_address = T::AccountId::from(H160::zero());
144
9
					if who.clone() != zero_address {
145
9
						MIGRATING_FOREIGN_ASSETS::using_once(&mut true, || {
146
9
							pallet_moonbeam_foreign_assets::Pallet::<T>::mint_into(
147
9
								info.asset_id,
148
9
								who.clone(),
149
9
								asset.balance.into(),
150
9
							)
151
9
						})
152
9
						.map_err(|err| {
153
							log::debug!("Error: {err:?}");
154
							Error::<T>::MintFailed
155
9
						})?;
156
					}
157

            
158
9
					info.remaining_balances = info.remaining_balances.saturating_sub(1);
159
9
					Ok::<(), Error<T>>(())
160
9
				})?;
161

            
162
6
			Ok(())
163
7
		})
164
8
	}
165

            
166
8
	pub(super) fn do_migrate_foreign_asset_approvals(limit: u32) -> DispatchResult {
167
8
		ensure!(limit != 0, Error::<T>::LimitCannotBeZero);
168

            
169
7
		ForeignAssetMigrationStatusValue::<T>::try_mutate(|status| -> DispatchResult {
170
7
			let info = match status {
171
6
				ForeignAssetMigrationStatus::Migrating(info) => info,
172
1
				_ => return Err(Error::<T>::NoMigrationInProgress.into()),
173
			};
174

            
175
6
			pallet_assets::Approvals::<T>::drain_prefix((info.asset_id,))
176
6
				.take(limit as usize)
177
13
				.try_for_each(|((owner, beneficiary), approval)| {
178
13
					<T as pallet_assets::Config>::Currency::unreserve(&owner, approval.deposit);
179
13

            
180
13
					MIGRATING_FOREIGN_ASSETS::using_once(&mut true, || {
181
13
						let address: H160 = owner.clone().into();
182
13

            
183
13
						// Temporarily remove metadata
184
13
						let meta = pallet_evm::AccountCodesMetadata::<T>::take(address.clone());
185
13

            
186
13
						let result = pallet_moonbeam_foreign_assets::Pallet::<T>::approve(
187
13
							info.asset_id,
188
13
							owner.clone(),
189
13
							beneficiary,
190
13
							approval.amount.into(),
191
13
						);
192

            
193
						// Re-add metadata
194
13
						if let Some(metadata) = meta {
195
							pallet_evm::AccountCodesMetadata::<T>::insert(address, metadata);
196
13
						}
197

            
198
13
						result
199
13
					})
200
13
					.map_err(|err| {
201
						log::debug!("Error: {err:?}");
202
						Error::<T>::ApprovalFailed
203
13
					})?;
204

            
205
13
					info.remaining_approvals = info.remaining_approvals.saturating_sub(1);
206
13
					Ok::<(), Error<T>>(())
207
13
				})?;
208

            
209
6
			Ok(())
210
7
		})
211
8
	}
212

            
213
	/// Finish Migration
214
4
	pub(super) fn do_finish_foreign_asset_migration() -> DispatchResult {
215
4
		ForeignAssetMigrationStatusValue::<T>::try_mutate(|status| -> DispatchResult {
216
4
			let migration_info = match status {
217
3
				ForeignAssetMigrationStatus::Migrating(info) => info,
218
1
				_ => return Err(Error::<T>::NoMigrationInProgress.into()),
219
			};
220

            
221
3
			ensure!(
222
3
				migration_info.remaining_balances == 0 && migration_info.remaining_approvals == 0,
223
2
				Error::<T>::MigrationNotFinished
224
			);
225

            
226
1
			pallet_assets::Asset::<T>::try_mutate_exists(
227
1
				migration_info.asset_id,
228
1
				|maybe_details| {
229
1
					let details = maybe_details.take().ok_or(Error::<T>::AssetNotFound)?;
230

            
231
1
					let metadata = pallet_assets::Metadata::<T>::take(migration_info.asset_id);
232
1
					<T as pallet_assets::Config>::Currency::unreserve(
233
1
						&details.owner,
234
1
						details.deposit.saturating_add(metadata.deposit),
235
1
					);
236
1

            
237
1
					Ok::<(), Error<T>>(())
238
1
				},
239
1
			)?;
240

            
241
1
			ApprovedForeignAssets::<T>::remove(migration_info.asset_id);
242
1
			*status = ForeignAssetMigrationStatus::Idle;
243
1
			Ok(())
244
4
		})
245
4
	}
246
}