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
		<T as frame_system::Config>::AccountId: Into<H160> + From<H160>,
134
	{
135
		#[pallet::call_index(2)]
136
		#[pallet::weight(Pallet::<T>::create_contract_metadata_weight(MAX_CONTRACT_CODE_SIZE))]
137
		pub fn create_contract_metadata(
138
			origin: OriginFor<T>,
139
			address: H160,
140
3
		) -> DispatchResultWithPostInfo {
141
3
			ensure_signed(origin)?;
142

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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