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::U256;
24

            
25
126
#[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
71
	fn default() -> Self {
35
71
		ForeignAssetMigrationStatus::Idle
36
71
	}
37
}
38

            
39
189
#[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
{
51
	/// Start a foreign asset migration by freezing the asset and creating the SC with the moonbeam
52
	/// foreign assets pallet.
53
18
	pub(super) fn do_start_foreign_asset_migration(asset_id: u128) -> DispatchResult {
54
18
		ForeignAssetMigrationStatusValue::<T>::try_mutate(|status| -> DispatchResult {
55
18
			ensure!(
56
18
				*status == ForeignAssetMigrationStatus::Idle,
57
1
				Error::<T>::MigrationNotFinished
58
			);
59

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

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

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

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

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

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

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

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

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

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

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

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

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

            
142
9
					MIGRATING_FOREIGN_ASSETS::using_once(&mut true, || {
143
9
						pallet_moonbeam_foreign_assets::Pallet::<T>::mint_into(
144
9
							info.asset_id,
145
9
							who.clone(),
146
9
							asset.balance.into(),
147
9
						)
148
9
					})
149
9
					.map_err(|_| Error::<T>::MintFailed)?;
150

            
151
9
					info.remaining_balances = info.remaining_balances.saturating_sub(1);
152
9
					Ok::<(), Error<T>>(())
153
9
				})?;
154

            
155
6
			Ok(())
156
7
		})
157
8
	}
158

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

            
162
7
		ForeignAssetMigrationStatusValue::<T>::try_mutate(|status| -> DispatchResult {
163
7
			let info = match status {
164
6
				ForeignAssetMigrationStatus::Migrating(info) => info,
165
1
				_ => return Err(Error::<T>::NoMigrationInProgress.into()),
166
			};
167

            
168
6
			pallet_assets::Approvals::<T>::drain_prefix((info.asset_id,))
169
6
				.take(limit as usize)
170
13
				.try_for_each(|((owner, beneficiary), approval)| {
171
13
					<T as pallet_assets::Config>::Currency::unreserve(&owner, approval.deposit);
172
13

            
173
13
					MIGRATING_FOREIGN_ASSETS::using_once(&mut true, || {
174
13
						pallet_moonbeam_foreign_assets::Pallet::<T>::approve(
175
13
							info.asset_id,
176
13
							owner.clone(),
177
13
							beneficiary,
178
13
							approval.amount.into(),
179
13
						)
180
13
					})
181
13
					.map_err(|_| Error::<T>::ApprovalFailed)?;
182

            
183
13
					info.remaining_approvals = info.remaining_approvals.saturating_sub(1);
184
13
					Ok::<(), Error<T>>(())
185
13
				})?;
186

            
187
6
			Ok(())
188
7
		})
189
8
	}
190

            
191
	/// Finish Migration
192
4
	pub(super) fn do_finish_foreign_asset_migration() -> DispatchResult {
193
4
		ForeignAssetMigrationStatusValue::<T>::try_mutate(|status| -> DispatchResult {
194
4
			let migration_info = match status {
195
3
				ForeignAssetMigrationStatus::Migrating(info) => info,
196
1
				_ => return Err(Error::<T>::NoMigrationInProgress.into()),
197
			};
198

            
199
3
			ensure!(
200
3
				migration_info.remaining_balances == 0 && migration_info.remaining_approvals == 0,
201
2
				Error::<T>::MigrationNotFinished
202
			);
203

            
204
1
			pallet_assets::Asset::<T>::try_mutate_exists(
205
1
				migration_info.asset_id,
206
1
				|maybe_details| {
207
1
					let details = maybe_details.take().ok_or(Error::<T>::AssetNotFound)?;
208

            
209
1
					let metadata = pallet_assets::Metadata::<T>::take(migration_info.asset_id);
210
1
					<T as pallet_assets::Config>::Currency::unreserve(
211
1
						&details.owner,
212
1
						details.deposit.saturating_add(metadata.deposit),
213
1
					);
214
1

            
215
1
					Ok::<(), Error<T>>(())
216
1
				},
217
1
			)?;
218

            
219
1
			ApprovedForeignAssets::<T>::remove(migration_info.asset_id);
220
1
			*status = ForeignAssetMigrationStatus::Idle;
221
1
			Ok(())
222
4
		})
223
4
	}
224
}