1use 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 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 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 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 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 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}