referrerpolicy=no-referrer-when-downgrade

pallet_contracts/migration/
v12.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and
19//! repay deposits.
20
21use 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			// We print this to measure the impact of the migration.
166			// Storage removed: deleted PrefabWasmModule's encoded len.
167			// Storage added: determinism field encoded len (as all other CodeInfo fields are the
168			// same as in the deleted OwnerInfo).
169			log::debug!(target: LOG_TARGET, "Storage removed: 1 item, {} bytes", &code_len,);
170
171			// Storage usage prices could change over time, and accounts who uploaded their
172			// contracts code before the storage deposits where introduced, had not been ever
173			// charged with any deposit for that (see migration v6).
174			//
175			// This is why deposit to be refunded here is calculated as follows:
176			//
177			// 1. Calculate the deposit amount for storage before the migration, given current
178			// prices.
179			// 2. Given current reserved deposit amount, calculate the correction factor.
180			// 3. Calculate the deposit amount for storage after the migration, given current
181			// prices.
182			// 4. Calculate real deposit amount to be reserved after the migration.
183			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		// CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1
322		// I.e. code info adds up 1 byte per record.
323		let info_bytes_added = items;
324		// We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code.
325		let storage_removed = storage_module.saturating_sub(info_bytes_added);
326		// module+code+info - bytes
327		let storage_was = storage_module
328			.saturating_add(storage_code)
329			.saturating_add(storage_info)
330			.saturating_sub(info_bytes_added);
331		// We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3
332		// items per code).
333		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}