referrerpolicy=no-referrer-when-downgrade

pallet_contracts/migration/
v15.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 contracts' _reserved_ balance from the `deposit_account` to be _held_ in the contract's
19//! account instead. 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 instead. For this transfer the
22//! balance from the deposit account to the contract's account and hold it in there.
23//! Then the deposit account is not needed anymore and we can get rid of it.
24
25use crate::{
26	migration::{IsFinished, MigrationStep},
27	weights::WeightInfo,
28	AccountIdOf, BalanceOf, CodeHash, Config, HoldReason, Pallet, TrieId, Weight, LOG_TARGET,
29};
30#[cfg(feature = "try-runtime")]
31use alloc::vec::Vec;
32#[cfg(feature = "try-runtime")]
33use frame_support::traits::fungible::InspectHold;
34use frame_support::{
35	pallet_prelude::*,
36	storage_alias,
37	traits::{
38		fungible::{Mutate, MutateHold},
39		tokens::{fungible::Inspect, Fortitude, Preservation},
40	},
41	weights::WeightMeter,
42	BoundedBTreeMap, DefaultNoBound,
43};
44use frame_system::Pallet as System;
45use sp_core::hexdisplay::HexDisplay;
46#[cfg(feature = "try-runtime")]
47use sp_runtime::TryRuntimeError;
48use sp_runtime::{traits::Zero, Saturating};
49
50mod v14 {
51	use super::*;
52
53	#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
54	#[scale_info(skip_type_params(T))]
55	pub struct ContractInfo<T: Config> {
56		pub trie_id: TrieId,
57		pub deposit_account: AccountIdOf<T>,
58		pub code_hash: CodeHash<T>,
59		pub storage_bytes: u32,
60		pub storage_items: u32,
61		pub storage_byte_deposit: BalanceOf<T>,
62		pub storage_item_deposit: BalanceOf<T>,
63		pub storage_base_deposit: BalanceOf<T>,
64		pub delegate_dependencies:
65			BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
66	}
67
68	#[storage_alias]
69	pub type ContractInfoOf<T: Config> = StorageMap<
70		Pallet<T>,
71		Twox64Concat,
72		<T as frame_system::Config>::AccountId,
73		ContractInfo<T>,
74	>;
75}
76
77#[cfg(feature = "runtime-benchmarks")]
78pub fn store_old_contract_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) {
79	use sp_runtime::traits::{Hash, TrailingZeroInput};
80	let entropy = (b"contract_depo_v1", account.clone()).using_encoded(T::Hashing::hash);
81	let deposit_account = Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
82		.expect("infinite length input; no invalid inputs for type; qed");
83	let info = v14::ContractInfo {
84		trie_id: info.trie_id.clone(),
85		deposit_account,
86		code_hash: info.code_hash,
87		storage_bytes: Default::default(),
88		storage_items: Default::default(),
89		storage_byte_deposit: info.storage_byte_deposit,
90		storage_item_deposit: Default::default(),
91		storage_base_deposit: info.storage_base_deposit(),
92		delegate_dependencies: info.delegate_dependencies().clone(),
93	};
94	v14::ContractInfoOf::<T>::insert(account, info);
95}
96
97#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
98#[scale_info(skip_type_params(T))]
99struct ContractInfo<T: Config> {
100	pub trie_id: TrieId,
101	pub code_hash: CodeHash<T>,
102	pub storage_bytes: u32,
103	pub storage_items: u32,
104	pub storage_byte_deposit: BalanceOf<T>,
105	pub storage_item_deposit: BalanceOf<T>,
106	pub storage_base_deposit: BalanceOf<T>,
107	pub delegate_dependencies:
108		BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
109}
110
111#[storage_alias]
112type ContractInfoOf<T: Config> =
113	StorageMap<Pallet<T>, Twox64Concat, <T as frame_system::Config>::AccountId, ContractInfo<T>>;
114
115#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
116pub struct Migration<T: Config> {
117	last_account: Option<T::AccountId>,
118}
119
120impl<T: Config> MigrationStep for Migration<T> {
121	const VERSION: u16 = 15;
122
123	fn max_step_weight() -> Weight {
124		T::WeightInfo::v15_migration_step()
125	}
126
127	fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
128		let mut iter = if let Some(last_account) = self.last_account.take() {
129			v14::ContractInfoOf::<T>::iter_from(v14::ContractInfoOf::<T>::hashed_key_for(
130				last_account,
131			))
132		} else {
133			v14::ContractInfoOf::<T>::iter()
134		};
135
136		if let Some((account, old_contract)) = iter.next() {
137			let deposit_account = &old_contract.deposit_account;
138			System::<T>::dec_consumers(deposit_account);
139
140			// Get the deposit balance to transfer.
141			let total_deposit_balance = T::Currency::total_balance(deposit_account);
142			let reducible_deposit_balance = T::Currency::reducible_balance(
143				deposit_account,
144				Preservation::Expendable,
145				Fortitude::Force,
146			);
147
148			if total_deposit_balance > reducible_deposit_balance {
149				// This should never happen, as by design all balance in the deposit account is
150				// storage deposit and therefore reducible after decrementing the consumer
151				// reference.
152				log::warn!(
153					target: LOG_TARGET,
154					"Deposit account 0x{:?} for contract 0x{:?} has some non-reducible balance {:?} from a total of {:?} that will remain in there.",
155					HexDisplay::from(&deposit_account.encode()),
156					HexDisplay::from(&account.encode()),
157					total_deposit_balance.saturating_sub(reducible_deposit_balance),
158					total_deposit_balance
159				);
160			}
161
162			// Move balance reserved from the deposit account back to the contract account.
163			// Let the deposit account die.
164			log::debug!(
165				target: LOG_TARGET,
166				"Transferring {:?} from the deposit account 0x{:?} to the contract 0x{:?}.",
167				reducible_deposit_balance,
168				HexDisplay::from(&deposit_account.encode()),
169				HexDisplay::from(&account.encode())
170			);
171			let transferred_deposit_balance = T::Currency::transfer(
172				deposit_account,
173				&account,
174				reducible_deposit_balance,
175				Preservation::Expendable,
176			)
177			.unwrap_or_else(|err| {
178				log::error!(
179					target: LOG_TARGET,
180					"Failed to transfer {:?} from the deposit account 0x{:?} to the contract 0x{:?}, reason: {:?}.",
181					reducible_deposit_balance,
182					HexDisplay::from(&deposit_account.encode()),
183					HexDisplay::from(&account.encode()),
184					err
185				);
186				Zero::zero()
187			});
188
189			// Hold the reserved balance.
190			if transferred_deposit_balance == Zero::zero() {
191				log::warn!(
192					target: LOG_TARGET,
193					"No balance to hold as storage deposit on the contract 0x{:?}.",
194					HexDisplay::from(&account.encode())
195				);
196			} else {
197				log::debug!(
198					target: LOG_TARGET,
199					"Holding {:?} as storage deposit on the contract 0x{:?}.",
200					transferred_deposit_balance,
201					HexDisplay::from(&account.encode())
202				);
203
204				T::Currency::hold(
205					&HoldReason::StorageDepositReserve.into(),
206					&account,
207					transferred_deposit_balance,
208				)
209				.unwrap_or_else(|err| {
210					log::error!(
211						target: LOG_TARGET,
212						"Failed to hold {:?} as storage deposit on the contract 0x{:?}, reason: {:?}.",
213						transferred_deposit_balance,
214						HexDisplay::from(&account.encode()),
215						err
216					);
217				});
218			}
219
220			log::debug!(target: LOG_TARGET, "===");
221			let info = ContractInfo {
222				trie_id: old_contract.trie_id,
223				code_hash: old_contract.code_hash,
224				storage_bytes: old_contract.storage_bytes,
225				storage_items: old_contract.storage_items,
226				storage_byte_deposit: old_contract.storage_byte_deposit,
227				storage_item_deposit: old_contract.storage_item_deposit,
228				storage_base_deposit: old_contract.storage_base_deposit,
229				delegate_dependencies: old_contract.delegate_dependencies,
230			};
231			ContractInfoOf::<T>::insert(account.clone(), info);
232
233			// Store last key for next migration step
234			self.last_account = Some(account);
235
236			meter.consume(T::WeightInfo::v15_migration_step());
237			IsFinished::No
238		} else {
239			log::info!(target: LOG_TARGET, "Done Migrating Storage Deposits.");
240			meter.consume(T::WeightInfo::v15_migration_step());
241			IsFinished::Yes
242		}
243	}
244
245	#[cfg(feature = "try-runtime")]
246	fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
247		let sample: Vec<_> = v14::ContractInfoOf::<T>::iter().take(100).collect();
248
249		log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len());
250
251		let state: Vec<(T::AccountId, v14::ContractInfo<T>, BalanceOf<T>, BalanceOf<T>)> = sample
252			.iter()
253			.map(|(account, contract)| {
254				(
255					account.clone(),
256					contract.clone(),
257					T::Currency::total_balance(&account),
258					T::Currency::total_balance(&contract.deposit_account),
259				)
260			})
261			.collect();
262
263		Ok(state.encode())
264	}
265
266	#[cfg(feature = "try-runtime")]
267	fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
268		let sample =
269			<Vec<(T::AccountId, v14::ContractInfo<T>, BalanceOf<T>, BalanceOf<T>)> as Decode>::decode(
270				&mut &state[..],
271			)
272			.expect("pre_upgrade_step provides a valid state; qed");
273
274		log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len());
275		for (account, old_contract, old_account_balance, old_deposit_balance) in sample {
276			log::debug!(target: LOG_TARGET, "===");
277			log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
278
279			let on_hold =
280				T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account);
281			let account_balance = T::Currency::total_balance(&account);
282
283			log::debug!(
284				target: LOG_TARGET,
285				"Validating balances match. Old deposit account's balance: {:?}. Contract's on hold: {:?}. Old contract's total balance: {:?}, Contract's total balance: {:?}.",
286				old_deposit_balance,
287				on_hold,
288				old_account_balance,
289				account_balance
290			);
291			ensure!(
292				old_account_balance.saturating_add(old_deposit_balance) == account_balance,
293				"total balance mismatch"
294			);
295			ensure!(old_deposit_balance == on_hold, "deposit mismatch");
296			ensure!(
297				!System::<T>::account_exists(&old_contract.deposit_account),
298				"deposit account still exists"
299			);
300
301			let migration_contract_info = ContractInfoOf::<T>::try_get(&account).unwrap();
302			let crate_contract_info = crate::ContractInfoOf::<T>::try_get(&account).unwrap();
303			ensure!(
304				migration_contract_info.trie_id == crate_contract_info.trie_id,
305				"trie_id mismatch"
306			);
307			ensure!(
308				migration_contract_info.code_hash == crate_contract_info.code_hash,
309				"code_hash mismatch"
310			);
311			ensure!(
312				migration_contract_info.storage_byte_deposit ==
313					crate_contract_info.storage_byte_deposit,
314				"storage_byte_deposit mismatch"
315			);
316			ensure!(
317				migration_contract_info.storage_base_deposit ==
318					crate_contract_info.storage_base_deposit(),
319				"storage_base_deposit mismatch"
320			);
321			ensure!(
322				&migration_contract_info.delegate_dependencies ==
323					crate_contract_info.delegate_dependencies(),
324				"delegate_dependencies mismatch"
325			);
326		}
327
328		Ok(())
329	}
330}