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