referrerpolicy=no-referrer-when-downgrade

pallet_contracts/migration/
v14.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//! Update the code owner balance, make the code upload deposit balance to be held instead of
19//! reserved. Since [`Currency`](frame_support::traits::Currency) has been
20//! [deprecated](https://github.com/paritytech/substrate/pull/12951), we need the deposits to be
21//! handled by the [`frame_support::traits::fungible`] traits.
22
23use crate::{
24	exec::AccountIdOf,
25	migration::{IsFinished, MigrationStep},
26	weights::WeightInfo,
27	BalanceOf, CodeHash, Config, Determinism, HoldReason, Pallet, Weight, LOG_TARGET,
28};
29#[cfg(feature = "try-runtime")]
30use alloc::collections::btree_map::BTreeMap;
31use codec::{Decode, Encode};
32#[cfg(feature = "try-runtime")]
33use environmental::Vec;
34#[cfg(feature = "try-runtime")]
35use frame_support::traits::fungible::{Inspect, InspectHold};
36use frame_support::{
37	pallet_prelude::*,
38	storage_alias,
39	traits::{fungible::MutateHold, ReservableCurrency},
40	weights::WeightMeter,
41	DefaultNoBound,
42};
43use sp_core::hexdisplay::HexDisplay;
44#[cfg(feature = "try-runtime")]
45use sp_runtime::TryRuntimeError;
46use sp_runtime::{traits::Zero, Saturating};
47
48mod v13 {
49	use super::*;
50
51	pub type BalanceOf<T, OldCurrency> = <OldCurrency as frame_support::traits::Currency<
52		<T as frame_system::Config>::AccountId,
53	>>::Balance;
54
55	#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
56	#[codec(mel_bound())]
57	#[scale_info(skip_type_params(T, OldCurrency))]
58	pub struct CodeInfo<T, OldCurrency>
59	where
60		T: Config,
61		OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
62	{
63		pub owner: AccountIdOf<T>,
64		#[codec(compact)]
65		pub deposit: v13::BalanceOf<T, OldCurrency>,
66		#[codec(compact)]
67		pub refcount: u64,
68		pub determinism: Determinism,
69		pub code_len: u32,
70	}
71
72	#[storage_alias]
73	pub type CodeInfoOf<T: Config, OldCurrency> =
74		StorageMap<Pallet<T>, Identity, CodeHash<T>, CodeInfo<T, OldCurrency>>;
75}
76
77#[cfg(feature = "runtime-benchmarks")]
78pub fn store_dummy_code<T: Config, OldCurrency>(account: T::AccountId)
79where
80	T: Config,
81	OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId> + 'static,
82{
83	use alloc::vec;
84	use sp_runtime::traits::Hash;
85
86	let len = T::MaxCodeLen::get();
87	let code = vec![42u8; len as usize];
88	let hash = T::Hashing::hash(&code);
89
90	let info = v13::CodeInfo {
91		owner: account,
92		deposit: 10_000u32.into(),
93		refcount: u64::MAX,
94		determinism: Determinism::Enforced,
95		code_len: len,
96	};
97	v13::CodeInfoOf::<T, OldCurrency>::insert(hash, info);
98}
99
100#[cfg(feature = "try-runtime")]
101#[derive(Encode, Decode)]
102/// Accounts for the balance allocation of a code owner.
103struct BalanceAllocation<T, OldCurrency>
104where
105	T: Config,
106	OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
107{
108	/// Total reserved balance as code upload deposit for the owner.
109	reserved: v13::BalanceOf<T, OldCurrency>,
110	/// Total balance of the owner.
111	total: v13::BalanceOf<T, OldCurrency>,
112}
113
114#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
115pub struct Migration<T, OldCurrency>
116where
117	T: Config,
118	OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
119{
120	last_code_hash: Option<CodeHash<T>>,
121	_phantom: PhantomData<(T, OldCurrency)>,
122}
123
124impl<T, OldCurrency> MigrationStep for Migration<T, OldCurrency>
125where
126	T: Config,
127	OldCurrency: 'static + ReservableCurrency<<T as frame_system::Config>::AccountId>,
128	BalanceOf<T>: From<OldCurrency::Balance>,
129{
130	const VERSION: u16 = 14;
131
132	fn max_step_weight() -> Weight {
133		T::WeightInfo::v14_migration_step()
134	}
135
136	fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
137		let mut iter = if let Some(last_hash) = self.last_code_hash.take() {
138			v13::CodeInfoOf::<T, OldCurrency>::iter_from(
139				v13::CodeInfoOf::<T, OldCurrency>::hashed_key_for(last_hash),
140			)
141		} else {
142			v13::CodeInfoOf::<T, OldCurrency>::iter()
143		};
144
145		if let Some((hash, code_info)) = iter.next() {
146			log::debug!(target: LOG_TARGET, "Migrating code upload deposit for 0x{:?}", HexDisplay::from(&code_info.owner.encode()));
147
148			let remaining = OldCurrency::unreserve(&code_info.owner, code_info.deposit);
149
150			if remaining > Zero::zero() {
151				log::warn!(
152					target: LOG_TARGET,
153					"Code owner's account 0x{:?} for code {:?} has some non-unreservable deposit {:?} from a total of {:?} that will remain in reserved.",
154					HexDisplay::from(&code_info.owner.encode()),
155					hash,
156					remaining,
157					code_info.deposit
158				);
159			}
160
161			let unreserved = code_info.deposit.saturating_sub(remaining);
162			let amount = BalanceOf::<T>::from(unreserved);
163
164			log::debug!(
165				target: LOG_TARGET,
166				"Holding {:?} on the code owner's account 0x{:?} for code {:?}.",
167				amount,
168				HexDisplay::from(&code_info.owner.encode()),
169				hash,
170			);
171
172			T::Currency::hold(
173				&HoldReason::CodeUploadDepositReserve.into(),
174				&code_info.owner,
175				amount,
176			)
177			.unwrap_or_else(|err| {
178				log::error!(
179					target: LOG_TARGET,
180					"Failed to hold {:?} from the code owner's account 0x{:?} for code {:?}, reason: {:?}.",
181					amount,
182					HexDisplay::from(&code_info.owner.encode()),
183					hash,
184					err
185				);
186			});
187
188			self.last_code_hash = Some(hash);
189			meter.consume(T::WeightInfo::v14_migration_step());
190			IsFinished::No
191		} else {
192			log::debug!(target: LOG_TARGET, "No more code upload deposit to migrate");
193			meter.consume(T::WeightInfo::v14_migration_step());
194			IsFinished::Yes
195		}
196	}
197
198	#[cfg(feature = "try-runtime")]
199	fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
200		let info: Vec<_> = v13::CodeInfoOf::<T, OldCurrency>::iter().collect();
201
202		let mut owner_balance_allocation =
203			BTreeMap::<AccountIdOf<T>, BalanceAllocation<T, OldCurrency>>::new();
204
205		// Calculates the balance allocation by accumulating the code upload deposits of all codes
206		// owned by an owner.
207		for (_, code_info) in info {
208			owner_balance_allocation
209				.entry(code_info.owner.clone())
210				.and_modify(|alloc| {
211					alloc.reserved = alloc.reserved.saturating_add(code_info.deposit);
212				})
213				.or_insert(BalanceAllocation {
214					reserved: code_info.deposit,
215					total: OldCurrency::total_balance(&code_info.owner),
216				});
217		}
218
219		Ok(owner_balance_allocation.encode())
220	}
221
222	#[cfg(feature = "try-runtime")]
223	fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
224		let owner_balance_allocation =
225			<BTreeMap<AccountIdOf<T>, BalanceAllocation<T, OldCurrency>> as Decode>::decode(
226				&mut &state[..],
227			)
228			.expect("pre_upgrade_step provides a valid state; qed");
229
230		let mut total_held: BalanceOf<T> = Zero::zero();
231		let count = owner_balance_allocation.len();
232		for (owner, old_balance_allocation) in owner_balance_allocation {
233			let held =
234				T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &owner);
235			log::debug!(
236				target: LOG_TARGET,
237				"Validating code upload deposit for owner 0x{:?}, reserved: {:?}, held: {:?}",
238				HexDisplay::from(&owner.encode()),
239				old_balance_allocation.reserved,
240				held
241			);
242			ensure!(held == old_balance_allocation.reserved.into(), "Held amount mismatch");
243
244			log::debug!(
245				target: LOG_TARGET,
246				"Validating total balance for owner 0x{:?}, new: {:?}, old: {:?}",
247				HexDisplay::from(&owner.encode()),
248				T::Currency::total_balance(&owner),
249				old_balance_allocation.total
250			);
251			ensure!(
252				T::Currency::total_balance(&owner) ==
253					BalanceOf::<T>::decode(&mut &old_balance_allocation.total.encode()[..])
254						.unwrap(),
255				"Balance mismatch "
256			);
257			total_held += held;
258		}
259
260		log::info!(
261			target: LOG_TARGET,
262			"Code owners processed: {:?}.",
263			count
264		);
265
266		log::info!(
267			target: LOG_TARGET,
268			"Total held amount for code upload deposit: {:?}",
269			total_held
270		);
271
272		Ok(())
273	}
274}