1use crate::{
22 exec::AccountIdOf,
23 migration::{IsFinished, MigrationStep},
24 weights::WeightInfo,
25 CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET,
26};
27use codec::{Decode, Encode};
28use core::{
29 cmp::{max, min},
30 ops::Deref,
31};
32use frame_support::{
33 pallet_prelude::*,
34 storage_alias,
35 traits::{
36 tokens::{fungible::Inspect, Fortitude::Polite, Preservation::Preserve},
37 ExistenceRequirement, ReservableCurrency,
38 },
39 weights::WeightMeter,
40 DefaultNoBound,
41};
42use sp_core::hexdisplay::HexDisplay;
43use sp_runtime::{
44 traits::{Hash, TrailingZeroInput, Zero},
45 Perbill, Saturating,
46};
47
48#[cfg(feature = "try-runtime")]
49use alloc::vec::Vec;
50#[cfg(feature = "try-runtime")]
51use sp_runtime::TryRuntimeError;
52
53mod v9 {
54 use super::*;
55
56 pub type BalanceOf<T, OldCurrency> = <OldCurrency as frame_support::traits::Currency<
57 <T as frame_system::Config>::AccountId,
58 >>::Balance;
59
60 #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
61 #[scale_info(skip_type_params(T, OldCurrency))]
62 pub struct ContractInfo<T: Config, OldCurrency>
63 where
64 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
65 {
66 pub trie_id: TrieId,
67 pub code_hash: CodeHash<T>,
68 pub storage_bytes: u32,
69 pub storage_items: u32,
70 pub storage_byte_deposit: BalanceOf<T, OldCurrency>,
71 pub storage_item_deposit: BalanceOf<T, OldCurrency>,
72 pub storage_base_deposit: BalanceOf<T, OldCurrency>,
73 }
74
75 #[storage_alias]
76 pub type ContractInfoOf<T: Config, OldCurrency> = StorageMap<
77 Pallet<T>,
78 Twox64Concat,
79 <T as frame_system::Config>::AccountId,
80 ContractInfo<T, OldCurrency>,
81 >;
82}
83
84#[cfg(feature = "runtime-benchmarks")]
85pub fn store_old_contract_info<T: Config, OldCurrency>(
86 account: T::AccountId,
87 info: crate::ContractInfo<T>,
88) where
89 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId> + 'static,
90{
91 let info = v9::ContractInfo {
92 trie_id: info.trie_id,
93 code_hash: info.code_hash,
94 storage_bytes: Default::default(),
95 storage_items: Default::default(),
96 storage_byte_deposit: Default::default(),
97 storage_item_deposit: Default::default(),
98 storage_base_deposit: Default::default(),
99 };
100 v9::ContractInfoOf::<T, OldCurrency>::insert(account, info);
101}
102
103#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
104#[scale_info(skip_type_params(T))]
105pub struct DepositAccount<T: Config>(AccountIdOf<T>);
106
107impl<T: Config> Deref for DepositAccount<T> {
108 type Target = AccountIdOf<T>;
109
110 fn deref(&self) -> &Self::Target {
111 &self.0
112 }
113}
114
115#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
116#[scale_info(skip_type_params(T, OldCurrency))]
117pub struct ContractInfo<T: Config, OldCurrency>
118where
119 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
120{
121 pub trie_id: TrieId,
122 deposit_account: DepositAccount<T>,
123 pub code_hash: CodeHash<T>,
124 storage_bytes: u32,
125 storage_items: u32,
126 pub storage_byte_deposit: v9::BalanceOf<T, OldCurrency>,
127 storage_item_deposit: v9::BalanceOf<T, OldCurrency>,
128 storage_base_deposit: v9::BalanceOf<T, OldCurrency>,
129}
130
131#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
132pub struct Migration<T: Config, OldCurrency = ()> {
133 last_account: Option<T::AccountId>,
134 _phantom: PhantomData<(T, OldCurrency)>,
135}
136
137#[storage_alias]
138type ContractInfoOf<T: Config, OldCurrency> = StorageMap<
139 Pallet<T>,
140 Twox64Concat,
141 <T as frame_system::Config>::AccountId,
142 ContractInfo<T, OldCurrency>,
143>;
144
145fn deposit_address<T: Config>(
147 contract_addr: &<T as frame_system::Config>::AccountId,
148) -> <T as frame_system::Config>::AccountId {
149 let entropy = (b"contract_depo_v1", contract_addr)
150 .using_encoded(<T as frame_system::Config>::Hashing::hash);
151 Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
152 .expect("infinite length input; no invalid inputs for type; qed")
153}
154
155impl<T: Config, OldCurrency: 'static> MigrationStep for Migration<T, OldCurrency>
156where
157 OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>
158 + Inspect<<T as frame_system::Config>::AccountId, Balance = v9::BalanceOf<T, OldCurrency>>,
159{
160 const VERSION: u16 = 10;
161
162 fn max_step_weight() -> Weight {
163 T::WeightInfo::v10_migration_step()
164 }
165
166 fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
167 let mut iter = if let Some(last_account) = self.last_account.take() {
168 v9::ContractInfoOf::<T, OldCurrency>::iter_from(
169 v9::ContractInfoOf::<T, OldCurrency>::hashed_key_for(last_account),
170 )
171 } else {
172 v9::ContractInfoOf::<T, OldCurrency>::iter()
173 };
174
175 if let Some((account, contract)) = iter.next() {
176 let min_balance = <OldCurrency as frame_support::traits::Currency<
177 <T as frame_system::Config>::AccountId,
178 >>::minimum_balance();
179 log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
180
181 let deposit_account: DepositAccount<T> = DepositAccount(deposit_address::<T>(&account));
183
184 let old_deposit = contract
186 .storage_base_deposit
187 .saturating_add(contract.storage_item_deposit)
188 .saturating_add(contract.storage_byte_deposit);
189
190 let remaining = OldCurrency::unreserve(&account, old_deposit);
193 if !remaining.is_zero() {
194 log::warn!(
195 target: LOG_TARGET,
196 "Partially unreserved. Remaining {:?} out of {:?} asked",
197 remaining,
198 old_deposit
199 );
200 }
201
202 let amount = old_deposit
204 .saturating_sub(min_balance)
205 .min(OldCurrency::reducible_balance(&account, Preserve, Polite));
206
207 let new_deposit = OldCurrency::transfer(
208 &account,
209 &deposit_account,
210 amount,
211 ExistenceRequirement::KeepAlive,
212 )
213 .map(|_| {
214 log::debug!(
215 target: LOG_TARGET,
216 "Transferred deposit ({:?}) to deposit account",
217 amount
218 );
219 amount
220 })
221 .unwrap_or_else(|err| {
223 log::error!(
224 target: LOG_TARGET,
225 "Failed to transfer the base deposit, reason: {:?}",
226 err
227 );
228 let _ = OldCurrency::deposit_creating(&deposit_account, min_balance);
229 min_balance
230 });
231
232 let new_base_deposit = min(
237 max(contract.storage_base_deposit, min_balance.saturating_add(min_balance)),
238 new_deposit,
239 );
240
241 let new_deposit_without_base = new_deposit.saturating_sub(new_base_deposit);
243 let old_deposit_without_base =
244 old_deposit.saturating_sub(contract.storage_base_deposit);
245 let ratio = Perbill::from_rational(new_deposit_without_base, old_deposit_without_base);
246
247 let storage_byte_deposit = ratio.mul_ceil(contract.storage_byte_deposit);
249 let storage_item_deposit = ratio.mul_ceil(contract.storage_item_deposit);
250
251 let storage_base_deposit = new_deposit
254 .saturating_sub(storage_byte_deposit)
255 .saturating_sub(storage_item_deposit);
256
257 let new_contract_info = ContractInfo {
258 trie_id: contract.trie_id,
259 deposit_account,
260 code_hash: contract.code_hash,
261 storage_bytes: contract.storage_bytes,
262 storage_items: contract.storage_items,
263 storage_byte_deposit,
264 storage_item_deposit,
265 storage_base_deposit,
266 };
267
268 ContractInfoOf::<T, OldCurrency>::insert(&account, new_contract_info);
269
270 self.last_account = Some(account);
272
273 meter.consume(T::WeightInfo::v10_migration_step());
274 IsFinished::No
275 } else {
276 log::debug!(target: LOG_TARGET, "Done Migrating contract info");
277 meter.consume(T::WeightInfo::v10_migration_step());
278 IsFinished::Yes
279 }
280 }
281
282 #[cfg(feature = "try-runtime")]
283 fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
284 let sample: Vec<_> = v9::ContractInfoOf::<T, OldCurrency>::iter().take(10).collect();
285
286 log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len());
287 Ok(sample.encode())
288 }
289
290 #[cfg(feature = "try-runtime")]
291 fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
292 let sample = <Vec<(T::AccountId, v9::ContractInfo<T, OldCurrency>)> as Decode>::decode(
293 &mut &state[..],
294 )
295 .expect("pre_upgrade_step provides a valid state; qed");
296
297 log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len());
298 for (account, old_contract) in sample {
299 log::debug!(target: LOG_TARGET, "===");
300 log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
301 let contract = ContractInfoOf::<T, OldCurrency>::get(&account).unwrap();
302 ensure!(old_contract.trie_id == contract.trie_id, "invalid trie_id");
303 ensure!(old_contract.code_hash == contract.code_hash, "invalid code_hash");
304 ensure!(old_contract.storage_bytes == contract.storage_bytes, "invalid storage_bytes");
305 ensure!(old_contract.storage_items == contract.storage_items, "invalid storage_items");
306
307 let deposit = <OldCurrency as frame_support::traits::Currency<_>>::total_balance(
308 &contract.deposit_account,
309 );
310 ensure!(
311 deposit ==
312 contract
313 .storage_base_deposit
314 .saturating_add(contract.storage_item_deposit)
315 .saturating_add(contract.storage_byte_deposit),
316 "deposit mismatch"
317 );
318 }
319
320 Ok(())
321 }
322}