1use crate::{
22 migration::{IsFinished, MigrationStep},
23 weights::WeightInfo,
24 AccountIdOf, BalanceOf, CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET,
25};
26use alloc::vec::Vec;
27use codec::{Decode, Encode};
28use frame_support::{
29 pallet_prelude::*, storage_alias, traits::ReservableCurrency, weights::WeightMeter,
30 DefaultNoBound, Identity,
31};
32use scale_info::prelude::format;
33use sp_core::hexdisplay::HexDisplay;
34#[cfg(feature = "try-runtime")]
35use sp_runtime::TryRuntimeError;
36use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating};
37
38mod v11 {
39 use super::*;
40
41 pub type BalanceOf<T, OldCurrency> = <OldCurrency as frame_support::traits::Currency<
42 <T as frame_system::Config>::AccountId,
43 >>::Balance;
44
45 #[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
46 #[codec(mel_bound())]
47 #[scale_info(skip_type_params(T, OldCurrency))]
48 pub struct OwnerInfo<T: Config, OldCurrency>
49 where
50 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
51 {
52 pub owner: AccountIdOf<T>,
53 #[codec(compact)]
54 pub deposit: BalanceOf<T, OldCurrency>,
55 #[codec(compact)]
56 pub refcount: u64,
57 }
58
59 #[derive(Encode, Decode, scale_info::TypeInfo)]
60 #[codec(mel_bound())]
61 #[scale_info(skip_type_params(T))]
62 pub struct PrefabWasmModule {
63 #[codec(compact)]
64 pub instruction_weights_version: u32,
65 #[codec(compact)]
66 pub initial: u32,
67 #[codec(compact)]
68 pub maximum: u32,
69 pub code: Vec<u8>,
70 pub determinism: Determinism,
71 }
72
73 #[storage_alias]
74 pub type OwnerInfoOf<T: Config, OldCurrency> =
75 StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T, OldCurrency>>;
76
77 #[storage_alias]
78 pub type CodeStorage<T: Config> =
79 StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
80}
81
82#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
83#[codec(mel_bound())]
84#[scale_info(skip_type_params(T, OldCurrency))]
85pub struct CodeInfo<T: Config, OldCurrency>
86where
87 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
88{
89 owner: AccountIdOf<T>,
90 #[codec(compact)]
91 deposit: v11::BalanceOf<T, OldCurrency>,
92 #[codec(compact)]
93 refcount: u64,
94 determinism: Determinism,
95 code_len: u32,
96}
97
98#[storage_alias]
99pub type CodeInfoOf<T: Config, OldCurrency> =
100 StorageMap<Pallet<T>, Identity, CodeHash<T>, CodeInfo<T, OldCurrency>>;
101
102#[storage_alias]
103pub type PristineCode<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, Vec<u8>>;
104
105#[cfg(feature = "runtime-benchmarks")]
106pub fn store_old_dummy_code<T: Config, OldCurrency>(len: usize, account: T::AccountId)
107where
108 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId> + 'static,
109{
110 use sp_runtime::traits::Hash;
111
112 let code = alloc::vec![42u8; len];
113 let hash = T::Hashing::hash(&code);
114 PristineCode::<T>::insert(hash, code.clone());
115
116 let module = v11::PrefabWasmModule {
117 instruction_weights_version: Default::default(),
118 initial: Default::default(),
119 maximum: Default::default(),
120 code,
121 determinism: Determinism::Enforced,
122 };
123 v11::CodeStorage::<T>::insert(hash, module);
124
125 let info = v11::OwnerInfo { owner: account, deposit: u32::MAX.into(), refcount: u64::MAX };
126 v11::OwnerInfoOf::<T, OldCurrency>::insert(hash, info);
127}
128
129#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
130pub struct Migration<T: Config, OldCurrency>
131where
132 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
133 OldCurrency::Balance: From<BalanceOf<T>>,
134{
135 last_code_hash: Option<CodeHash<T>>,
136 _phantom: PhantomData<OldCurrency>,
137}
138
139impl<T: Config, OldCurrency> MigrationStep for Migration<T, OldCurrency>
140where
141 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId> + 'static,
142 OldCurrency::Balance: From<BalanceOf<T>>,
143{
144 const VERSION: u16 = 12;
145
146 fn max_step_weight() -> Weight {
147 T::WeightInfo::v12_migration_step(T::MaxCodeLen::get())
148 }
149
150 fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
151 let mut iter = if let Some(last_key) = self.last_code_hash.take() {
152 v11::OwnerInfoOf::<T, OldCurrency>::iter_from(
153 v11::OwnerInfoOf::<T, OldCurrency>::hashed_key_for(last_key),
154 )
155 } else {
156 v11::OwnerInfoOf::<T, OldCurrency>::iter()
157 };
158 if let Some((hash, old_info)) = iter.next() {
159 log::debug!(target: LOG_TARGET, "Migrating OwnerInfo for code_hash {:?}", hash);
160
161 let module = v11::CodeStorage::<T>::take(hash)
162 .expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str());
163
164 let code_len = module.code.len();
165 log::debug!(target: LOG_TARGET, "Storage removed: 1 item, {} bytes", &code_len,);
170
171 let price_per_byte = T::DepositPerByte::get();
184 let price_per_item = T::DepositPerItem::get();
185 let bytes_before = module
186 .encoded_size()
187 .saturating_add(code_len)
188 .saturating_add(v11::OwnerInfo::<T, OldCurrency>::max_encoded_len())
189 as u32;
190 let items_before = 3u32;
191 let deposit_expected_before = price_per_byte
192 .saturating_mul(bytes_before.into())
193 .saturating_add(price_per_item.saturating_mul(items_before.into()));
194 let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before)
195 .unwrap_or_default()
196 .min(FixedU128::from_u32(1));
197 let bytes_after =
198 code_len.saturating_add(CodeInfo::<T, OldCurrency>::max_encoded_len()) as u32;
199 let items_after = 2u32;
200 let deposit_expected_after = price_per_byte
201 .saturating_mul(bytes_after.into())
202 .saturating_add(price_per_item.saturating_mul(items_after.into()));
203 let deposit = ratio.saturating_mul_int(deposit_expected_after);
204
205 let info = CodeInfo::<T, OldCurrency> {
206 determinism: module.determinism,
207 owner: old_info.owner,
208 deposit: deposit.into(),
209 refcount: old_info.refcount,
210 code_len: code_len as u32,
211 };
212
213 let amount = old_info.deposit.saturating_sub(info.deposit);
214 if !amount.is_zero() {
215 OldCurrency::unreserve(&info.owner, amount);
216 log::debug!(
217 target: LOG_TARGET,
218 "Deposit refunded: {:?} Balance, to: {:?}",
219 &amount,
220 HexDisplay::from(&info.owner.encode())
221 );
222 } else {
223 log::warn!(
224 target: LOG_TARGET,
225 "new deposit: {:?} >= old deposit: {:?}",
226 &info.deposit,
227 &old_info.deposit
228 );
229 }
230 CodeInfoOf::<T, OldCurrency>::insert(hash, info);
231
232 self.last_code_hash = Some(hash);
233
234 meter.consume(T::WeightInfo::v12_migration_step(code_len as u32));
235 IsFinished::No
236 } else {
237 log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate");
238 meter.consume(T::WeightInfo::v12_migration_step(0));
239 IsFinished::Yes
240 }
241 }
242
243 #[cfg(feature = "try-runtime")]
244 fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
245 let len = 100;
246 log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len);
247 let sample: Vec<_> = v11::OwnerInfoOf::<T, OldCurrency>::iter()
248 .take(len)
249 .map(|(k, v)| {
250 let module = v11::CodeStorage::<T>::get(k)
251 .expect("No PrefabWasmModule found for code_hash: {:?}");
252 let info: CodeInfo<T, OldCurrency> = CodeInfo {
253 determinism: module.determinism,
254 deposit: v.deposit,
255 refcount: v.refcount,
256 owner: v.owner,
257 code_len: module.code.len() as u32,
258 };
259 (k, info)
260 })
261 .collect();
262
263 let storage: u32 =
264 v11::CodeStorage::<T>::iter().map(|(_k, v)| v.encoded_size() as u32).sum();
265 let mut deposit: v11::BalanceOf<T, OldCurrency> = Default::default();
266 v11::OwnerInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| deposit += v.deposit);
267
268 Ok((sample, deposit, storage).encode())
269 }
270
271 #[cfg(feature = "try-runtime")]
272 fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
273 let state = <(
274 Vec<(CodeHash<T>, CodeInfo<T, OldCurrency>)>,
275 v11::BalanceOf<T, OldCurrency>,
276 u32,
277 ) as Decode>::decode(&mut &state[..])
278 .unwrap();
279
280 log::debug!(target: LOG_TARGET, "Validating state of {} Codeinfo(s)", state.0.len());
281 for (hash, old) in state.0 {
282 let info = CodeInfoOf::<T, OldCurrency>::get(&hash)
283 .expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str());
284 ensure!(info.determinism == old.determinism, "invalid determinism");
285 ensure!(info.owner == old.owner, "invalid owner");
286 ensure!(info.refcount == old.refcount, "invalid refcount");
287 }
288
289 if let Some((k, _)) = v11::CodeStorage::<T>::iter().next() {
290 log::warn!(
291 target: LOG_TARGET,
292 "CodeStorage is still NOT empty, found code_hash: {:?}",
293 k
294 );
295 } else {
296 log::debug!(target: LOG_TARGET, "CodeStorage is empty.");
297 }
298 if let Some((k, _)) = v11::OwnerInfoOf::<T, OldCurrency>::iter().next() {
299 log::warn!(
300 target: LOG_TARGET,
301 "OwnerInfoOf is still NOT empty, found code_hash: {:?}",
302 k
303 );
304 } else {
305 log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty.");
306 }
307
308 let mut deposit: v11::BalanceOf<T, OldCurrency> = Default::default();
309 let mut items = 0u32;
310 let mut storage_info = 0u32;
311 CodeInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| {
312 deposit += v.deposit;
313 items += 1;
314 storage_info += v.encoded_size() as u32;
315 });
316 let mut storage_code = 0u32;
317 PristineCode::<T>::iter().for_each(|(_k, v)| {
318 storage_code += v.len() as u32;
319 });
320 let (_, old_deposit, storage_module) = state;
321 let info_bytes_added = items;
324 let storage_removed = storage_module.saturating_sub(info_bytes_added);
326 let storage_was = storage_module
328 .saturating_add(storage_code)
329 .saturating_add(storage_info)
330 .saturating_sub(info_bytes_added);
331 let items_removed = items;
334 log::info!(
335 target: LOG_TARGET,
336 "Storage freed, bytes: {} (of {}), items: {} (of {})",
337 storage_removed,
338 storage_was,
339 items_removed,
340 items_removed * 3,
341 );
342 log::info!(
343 target: LOG_TARGET,
344 "Deposits returned, total: {:?} Balance (of {:?} Balance)",
345 old_deposit.saturating_sub(deposit),
346 old_deposit,
347 );
348
349 Ok(())
350 }
351}