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(
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 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 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 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 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 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}