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
//! # Lazy Migration Pallet
18

            
19
#![allow(non_camel_case_types)]
20
#![cfg_attr(not(feature = "std"), no_std)]
21

            
22
#[cfg(test)]
23
pub mod mock;
24
#[cfg(test)]
25
mod tests;
26

            
27
#[cfg(any(test, feature = "runtime-benchmarks"))]
28
mod benchmarks;
29

            
30
mod foreign_asset;
31
pub mod weights;
32
pub use weights::WeightInfo;
33

            
34
use frame_support::pallet;
35
use frame_support::pallet_prelude::*;
36
use frame_system::pallet_prelude::*;
37
pub use pallet::*;
38
use xcm::latest::Location;
39

            
40
const MAX_CONTRACT_CODE_SIZE: u64 = 25 * 1024;
41

            
42
743
environmental::environmental!(MIGRATING_FOREIGN_ASSETS: bool);
43

            
44
521
#[pallet]
45
pub mod pallet {
46
	use super::*;
47
	use crate::foreign_asset::ForeignAssetMigrationStatus;
48
	use sp_core::{H160, U256};
49

            
50
	pub const ARRAY_LIMIT: u32 = 1000;
51
	pub type GetArrayLimit = ConstU32<ARRAY_LIMIT>;
52

            
53
	/// Pallet for multi block migrations
54
70
	#[pallet::pallet]
55
	pub struct Pallet<T>(PhantomData<T>);
56

            
57
	#[pallet::storage]
58
	pub(crate) type StateMigrationStatusValue<T: Config> =
59
		StorageValue<_, (StateMigrationStatus, u64), ValueQuery>;
60

            
61
160
	#[pallet::storage]
62
	pub(crate) type ForeignAssetMigrationStatusValue<T: Config> =
63
		StorageValue<_, ForeignAssetMigrationStatus, ValueQuery>;
64

            
65
	// List of approved foreign assets to be migrated
66
36
	#[pallet::storage]
67
	pub(crate) type ApprovedForeignAssets<T: Config> =
68
		StorageMap<_, Twox64Concat, u128, (), OptionQuery>;
69

            
70
	pub(crate) type StorageKey = BoundedVec<u8, ConstU32<1_024>>;
71

            
72
252
	#[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, MaxEncodedLen, Debug)]
73
	pub enum StateMigrationStatus {
74
		NotStarted,
75
		Started(StorageKey),
76
		Error(BoundedVec<u8, ConstU32<1024>>),
77
		Complete,
78
	}
79

            
80
	impl Default for StateMigrationStatus {
81
51
		fn default() -> Self {
82
51
			StateMigrationStatus::NotStarted
83
51
		}
84
	}
85
	/// Configuration trait of this pallet.
86
	#[pallet::config]
87
	pub trait Config:
88
		frame_system::Config
89
		+ pallet_evm::Config
90
		+ pallet_balances::Config
91
		+ pallet_assets::Config<AssetId = u128>
92
		+ pallet_asset_manager::Config<AssetId = u128>
93
		+ pallet_moonbeam_foreign_assets::Config
94
	{
95
		// Origin that is allowed to start foreign assets migration
96
		type ForeignAssetMigratorOrigin: EnsureOrigin<Self::RuntimeOrigin>;
97
		type WeightInfo: WeightInfo;
98
	}
99

            
100
24
	#[pallet::error]
101
	pub enum Error<T> {
102
		/// The limit cannot be zero
103
		LimitCannotBeZero,
104
		/// The contract already have metadata
105
		ContractMetadataAlreadySet,
106
		/// Contract not exist
107
		ContractNotExist,
108
		/// The symbol length exceeds the maximum allowed
109
		SymbolTooLong,
110
		/// The name length exceeds the maximum allowed
111
		NameTooLong,
112
		/// The asset type was not found
113
		AssetTypeNotFound,
114
		/// Asset not found
115
		AssetNotFound,
116
		/// The location of the asset was not found
117
		LocationNotFound,
118
		/// Migration is not finished yet
119
		MigrationNotFinished,
120
		/// No migration in progress
121
		NoMigrationInProgress,
122
		/// Fail to mint the foreign asset
123
		MintFailed,
124
		/// Fail to add an approval
125
		ApprovalFailed,
126
	}
127

            
128
	#[pallet::call]
129
	impl<T: Config> Pallet<T>
130
	where
131
		<T as pallet_assets::Config>::Balance: Into<U256>,
132
		<T as pallet_asset_manager::Config>::ForeignAssetType: Into<Option<Location>>,
133
	{
134
		#[pallet::call_index(2)]
135
		#[pallet::weight(Pallet::<T>::create_contract_metadata_weight(MAX_CONTRACT_CODE_SIZE))]
136
		pub fn create_contract_metadata(
137
			origin: OriginFor<T>,
138
			address: H160,
139
3
		) -> DispatchResultWithPostInfo {
140
3
			ensure_signed(origin)?;
141

            
142
3
			ensure!(
143
3
				pallet_evm::AccountCodesMetadata::<T>::get(address).is_none(),
144
1
				Error::<T>::ContractMetadataAlreadySet
145
			);
146

            
147
			// Ensure contract exist
148
2
			let code = pallet_evm::AccountCodes::<T>::get(address);
149
2
			ensure!(!code.is_empty(), Error::<T>::ContractNotExist);
150

            
151
			// Construct metadata
152
1
			let code_size = code.len() as u64;
153
1
			let code_hash = sp_core::H256::from(sp_io::hashing::keccak_256(&code));
154
1
			let meta = pallet_evm::CodeMetadata {
155
1
				size: code_size,
156
1
				hash: code_hash,
157
1
			};
158
1

            
159
1
			// Set metadata
160
1
			pallet_evm::AccountCodesMetadata::<T>::insert(address, meta);
161
1

            
162
1
			Ok((
163
1
				Some(Self::create_contract_metadata_weight(code_size)),
164
1
				Pays::No,
165
1
			)
166
1
				.into())
167
		}
168

            
169
		#[pallet::call_index(3)]
170
		#[pallet::weight(
171
			<T as pallet::Config>::WeightInfo::approve_assets_to_migrate(assets.len() as u32)
172
		)]
173
		pub fn approve_assets_to_migrate(
174
			origin: OriginFor<T>,
175
			assets: BoundedVec<u128, GetArrayLimit>,
176
18
		) -> DispatchResultWithPostInfo {
177
18
			T::ForeignAssetMigratorOrigin::ensure_origin(origin.clone())?;
178

            
179
17
			assets.iter().try_for_each(|asset_id| {
180
17
				ensure!(
181
17
					pallet_assets::Asset::<T>::contains_key(*asset_id),
182
					Error::<T>::AssetNotFound
183
				);
184

            
185
17
				ApprovedForeignAssets::<T>::insert(asset_id, ());
186
17
				Ok::<(), Error<T>>(())
187
17
			})?;
188
17
			Ok(Pays::No.into())
189
		}
190

            
191
		#[pallet::call_index(4)]
192
		#[pallet::weight(<T as pallet::Config>::WeightInfo::start_foreign_assets_migration())]
193
		pub fn start_foreign_assets_migration(
194
			origin: OriginFor<T>,
195
			asset_id: u128,
196
18
		) -> DispatchResultWithPostInfo {
197
18
			ensure_signed(origin)?;
198

            
199
18
			Self::do_start_foreign_asset_migration(asset_id)?;
200
15
			Ok(Pays::No.into())
201
		}
202

            
203
		#[pallet::call_index(5)]
204
		#[pallet::weight(<T as pallet::Config>::WeightInfo::migrate_foreign_asset_balances(*limit))]
205
		pub fn migrate_foreign_asset_balances(
206
			origin: OriginFor<T>,
207
			limit: u32,
208
8
		) -> DispatchResultWithPostInfo {
209
8
			ensure_signed(origin)?;
210

            
211
8
			Self::do_migrate_foreign_asset_balances(limit)?;
212
6
			Ok(Pays::No.into())
213
		}
214

            
215
		#[pallet::call_index(6)]
216
		#[pallet::weight(<T as pallet::Config>::WeightInfo::migrate_foreign_asset_approvals(*limit))]
217
		pub fn migrate_foreign_asset_approvals(
218
			origin: OriginFor<T>,
219
			limit: u32,
220
8
		) -> DispatchResultWithPostInfo {
221
8
			ensure_signed(origin)?;
222

            
223
8
			Self::do_migrate_foreign_asset_approvals(limit)?;
224
6
			Ok(Pays::No.into())
225
		}
226

            
227
		#[pallet::call_index(7)]
228
		#[pallet::weight(<T as pallet::Config>::WeightInfo::finish_foreign_assets_migration())]
229
4
		pub fn finish_foreign_assets_migration(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
230
4
			ensure_signed(origin)?;
231

            
232
4
			Self::do_finish_foreign_asset_migration()?;
233
1
			Ok(Pays::No.into())
234
		}
235
	}
236

            
237
	impl<T: Config> Pallet<T> {
238
1
		fn create_contract_metadata_weight(code_size: u64) -> Weight {
239
1
			// max entry size of AccountCodesMetadata (full key + value)
240
1
			const PROOF_SIZE_CODE_METADATA: u64 = 100;
241
1
			// intermediates nodes might be up to 3Kb
242
1
			const PROOF_SIZE_INTERMEDIATES_NODES: u64 = 3 * 1024;
243
1

            
244
1
			// Account for 2 reads, 1 write
245
1
			<T as frame_system::Config>::DbWeight::get()
246
1
				.reads_writes(2, 1)
247
1
				.set_proof_size(
248
1
					code_size + (PROOF_SIZE_INTERMEDIATES_NODES * 2) + PROOF_SIZE_CODE_METADATA,
249
1
				)
250
1
		}
251
	}
252
}
253

            
254
4242
pub fn is_migrating_foreign_assets() -> bool {
255
4242
	MIGRATING_FOREIGN_ASSETS::with(|v| *v).unwrap_or(false)
256
4242
}