Skip to content

Commit 664cb89

Browse files
committed
feat: add fixed v12, v14 migrations
1 parent 9eaf9fa commit 664cb89

File tree

5 files changed

+679
-10
lines changed

5 files changed

+679
-10
lines changed

Diff for: primitives/src/migrations/contract_v12.rs

+379
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
// This file is part of Astar.
2+
3+
// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
4+
// SPDX-License-Identifier: GPL-3.0-or-later
5+
6+
// Astar is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU General Public License as published by
8+
// the Free Software Foundation, either version 3 of the License, or
9+
// (at your option) any later version.
10+
11+
// Astar is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU General Public License for more details.
15+
16+
// You should have received a copy of the GNU General Public License
17+
// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18+
19+
//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and
20+
//! repay deposits.
21+
22+
use frame_support::{
23+
pallet_prelude::*,
24+
storage_alias,
25+
traits::{fungible::Inspect, ReservableCurrency},
26+
DefaultNoBound, Identity,
27+
};
28+
use pallet_contracts::{
29+
migration::{IsFinished, MigrationStep},
30+
weights::WeightInfo,
31+
Config, Determinism, Pallet,
32+
};
33+
use parity_scale_codec::{Decode, Encode};
34+
use scale_info::prelude::format;
35+
use sp_core::hexdisplay::HexDisplay;
36+
#[cfg(feature = "try-runtime")]
37+
use sp_runtime::TryRuntimeError;
38+
use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating};
39+
use sp_std::prelude::*;
40+
41+
const LOG_TARGET: &str = "runtime::contracts";
42+
43+
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
44+
type CodeHash<T> = <T as frame_system::Config>::Hash;
45+
type BalanceOf<T> =
46+
<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
47+
48+
mod old {
49+
use super::*;
50+
51+
pub type BalanceOf<T, OldCurrency> = <OldCurrency as frame_support::traits::Currency<
52+
<T as frame_system::Config>::AccountId,
53+
>>::Balance;
54+
55+
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
56+
#[codec(mel_bound())]
57+
#[scale_info(skip_type_params(T, OldCurrency))]
58+
pub struct OwnerInfo<T: Config, OldCurrency>
59+
where
60+
OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
61+
{
62+
pub owner: AccountIdOf<T>,
63+
#[codec(compact)]
64+
pub deposit: BalanceOf<T, OldCurrency>,
65+
#[codec(compact)]
66+
pub refcount: u64,
67+
}
68+
69+
#[derive(Encode, Decode, scale_info::TypeInfo)]
70+
#[codec(mel_bound())]
71+
#[scale_info(skip_type_params(T))]
72+
pub struct PrefabWasmModule {
73+
#[codec(compact)]
74+
pub instruction_weights_version: u32,
75+
#[codec(compact)]
76+
pub initial: u32,
77+
#[codec(compact)]
78+
pub maximum: u32,
79+
pub code: Vec<u8>,
80+
pub determinism: Determinism,
81+
}
82+
83+
#[storage_alias]
84+
pub type OwnerInfoOf<T: Config, OldCurrency> =
85+
StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T, OldCurrency>>;
86+
87+
#[storage_alias]
88+
pub type CodeStorage<T: Config> =
89+
StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
90+
}
91+
92+
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
93+
#[codec(mel_bound())]
94+
#[scale_info(skip_type_params(T, OldCurrency))]
95+
pub struct CodeInfo<T: Config, OldCurrency>
96+
where
97+
OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
98+
{
99+
owner: AccountIdOf<T>,
100+
#[codec(compact)]
101+
deposit: old::BalanceOf<T, OldCurrency>,
102+
#[codec(compact)]
103+
refcount: u64,
104+
determinism: Determinism,
105+
code_len: u32,
106+
}
107+
108+
#[storage_alias]
109+
pub type CodeInfoOf<T: Config, OldCurrency> =
110+
StorageMap<Pallet<T>, Identity, CodeHash<T>, CodeInfo<T, OldCurrency>>;
111+
112+
#[storage_alias]
113+
pub type PristineCode<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, Vec<u8>>;
114+
115+
#[cfg(feature = "runtime-benchmarks")]
116+
pub fn store_old_dummy_code<T: Config, OldCurrency>(len: usize, account: T::AccountId)
117+
where
118+
OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId> + 'static,
119+
{
120+
use sp_runtime::traits::Hash;
121+
122+
let code = vec![42u8; len];
123+
let hash = T::Hashing::hash(&code);
124+
PristineCode::<T>::insert(hash, code.clone());
125+
126+
let module = old::PrefabWasmModule {
127+
instruction_weights_version: Default::default(),
128+
initial: Default::default(),
129+
maximum: Default::default(),
130+
code,
131+
determinism: Determinism::Enforced,
132+
};
133+
old::CodeStorage::<T>::insert(hash, module);
134+
135+
let info = old::OwnerInfo {
136+
owner: account,
137+
deposit: u32::MAX.into(),
138+
refcount: u64::MAX,
139+
};
140+
old::OwnerInfoOf::<T, OldCurrency>::insert(hash, info);
141+
}
142+
143+
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
144+
pub struct Migration<T: Config, OldCurrency>
145+
where
146+
OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId>,
147+
OldCurrency::Balance: From<BalanceOf<T>>,
148+
{
149+
last_code_hash: Option<CodeHash<T>>,
150+
_phantom: PhantomData<OldCurrency>,
151+
}
152+
153+
impl<T: Config, OldCurrency> MigrationStep for Migration<T, OldCurrency>
154+
where
155+
OldCurrency: ReservableCurrency<<T as frame_system::Config>::AccountId> + 'static,
156+
OldCurrency::Balance: From<BalanceOf<T>>,
157+
{
158+
const VERSION: u16 = 12;
159+
160+
fn max_step_weight() -> Weight {
161+
T::WeightInfo::v12_migration_step(T::MaxCodeLen::get())
162+
}
163+
164+
fn step(&mut self) -> (IsFinished, Weight) {
165+
let mut iter = if let Some(last_key) = self.last_code_hash.take() {
166+
old::OwnerInfoOf::<T, OldCurrency>::iter_from(
167+
old::OwnerInfoOf::<T, OldCurrency>::hashed_key_for(last_key),
168+
)
169+
} else {
170+
old::OwnerInfoOf::<T, OldCurrency>::iter()
171+
};
172+
if let Some((hash, old_info)) = iter.next() {
173+
log::debug!(
174+
target: LOG_TARGET,
175+
"Migrating OwnerInfo for code_hash {:?}",
176+
hash
177+
);
178+
179+
let module = old::CodeStorage::<T>::take(hash)
180+
.expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str());
181+
182+
let code_len = module.code.len();
183+
// We print this to measure the impact of the migration.
184+
// Storage removed: deleted PrefabWasmModule's encoded len.
185+
// Storage added: determinism field encoded len (as all other CodeInfo fields are the
186+
// same as in the deleted OwnerInfo).
187+
log::debug!(
188+
target: LOG_TARGET,
189+
"Storage removed: 1 item, {} bytes",
190+
&code_len,
191+
);
192+
193+
// Storage usage prices could change over time, and accounts who uploaded their
194+
// contracts code before the storage deposits where introduced, had not been ever
195+
// charged with any deposit for that (see migration v6).
196+
//
197+
// This is why deposit to be refunded here is calculated as follows:
198+
//
199+
// 1. Calculate the deposit amount for storage before the migration, given current
200+
// prices.
201+
// 2. Given current reserved deposit amount, calculate the correction factor.
202+
// 3. Calculate the deposit amount for storage after the migration, given current
203+
// prices.
204+
// 4. Calculate real deposit amount to be reserved after the migration.
205+
let price_per_byte = T::DepositPerByte::get();
206+
let price_per_item = T::DepositPerItem::get();
207+
let bytes_before = module
208+
.encoded_size()
209+
.saturating_add(code_len)
210+
.saturating_add(old::OwnerInfo::<T, OldCurrency>::max_encoded_len())
211+
as u32;
212+
let items_before = 3u32;
213+
let deposit_expected_before = price_per_byte
214+
.saturating_mul(bytes_before.into())
215+
.saturating_add(price_per_item.saturating_mul(items_before.into()));
216+
let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before)
217+
.unwrap_or_default()
218+
.min(FixedU128::from_u32(1));
219+
let bytes_after =
220+
code_len.saturating_add(CodeInfo::<T, OldCurrency>::max_encoded_len()) as u32;
221+
let items_after = 2u32;
222+
let deposit_expected_after = price_per_byte
223+
.saturating_mul(bytes_after.into())
224+
.saturating_add(price_per_item.saturating_mul(items_after.into()));
225+
let deposit = ratio.saturating_mul_int(deposit_expected_after);
226+
227+
let info = CodeInfo::<T, OldCurrency> {
228+
determinism: module.determinism,
229+
owner: old_info.owner,
230+
deposit: deposit.into(),
231+
refcount: old_info.refcount,
232+
code_len: code_len as u32,
233+
};
234+
235+
let amount = old_info.deposit.saturating_sub(info.deposit);
236+
if !amount.is_zero() {
237+
OldCurrency::unreserve(&info.owner, amount);
238+
log::debug!(
239+
target: LOG_TARGET,
240+
"Deposit refunded: {:?} Balance, to: {:?}",
241+
&amount,
242+
HexDisplay::from(&info.owner.encode())
243+
);
244+
} else {
245+
log::warn!(
246+
target: LOG_TARGET,
247+
"new deposit: {:?} >= old deposit: {:?}",
248+
&info.deposit,
249+
&old_info.deposit
250+
);
251+
}
252+
CodeInfoOf::<T, OldCurrency>::insert(hash, info);
253+
254+
self.last_code_hash = Some(hash);
255+
256+
(
257+
IsFinished::No,
258+
T::WeightInfo::v12_migration_step(code_len as u32),
259+
)
260+
} else {
261+
log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate");
262+
(IsFinished::Yes, T::WeightInfo::v12_migration_step(0))
263+
}
264+
}
265+
266+
#[cfg(feature = "try-runtime")]
267+
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
268+
let len = 100;
269+
log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len);
270+
let sample: Vec<_> = old::OwnerInfoOf::<T, OldCurrency>::iter()
271+
.take(len)
272+
.map(|(k, v)| {
273+
let module = old::CodeStorage::<T>::get(k)
274+
.expect("No PrefabWasmModule found for code_hash: {:?}");
275+
let info: CodeInfo<T, OldCurrency> = CodeInfo {
276+
determinism: module.determinism,
277+
deposit: v.deposit,
278+
refcount: v.refcount,
279+
owner: v.owner,
280+
code_len: module.code.len() as u32,
281+
};
282+
(k, info)
283+
})
284+
.collect();
285+
286+
let storage: u32 = old::CodeStorage::<T>::iter()
287+
.map(|(_k, v)| v.encoded_size() as u32)
288+
.sum();
289+
let mut deposit: old::BalanceOf<T, OldCurrency> = Default::default();
290+
old::OwnerInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| deposit += v.deposit);
291+
292+
Ok((sample, deposit, storage).encode())
293+
}
294+
295+
#[cfg(feature = "try-runtime")]
296+
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
297+
let state = <(
298+
Vec<(CodeHash<T>, CodeInfo<T, OldCurrency>)>,
299+
old::BalanceOf<T, OldCurrency>,
300+
u32,
301+
) as Decode>::decode(&mut &state[..])
302+
.unwrap();
303+
304+
log::debug!(
305+
target: LOG_TARGET,
306+
"Validating state of {} Codeinfo(s)",
307+
state.0.len()
308+
);
309+
for (hash, old) in state.0 {
310+
let info = CodeInfoOf::<T, OldCurrency>::get(&hash)
311+
.expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str());
312+
ensure!(info.determinism == old.determinism, "invalid determinism");
313+
ensure!(info.owner == old.owner, "invalid owner");
314+
ensure!(info.refcount == old.refcount, "invalid refcount");
315+
}
316+
317+
if let Some((k, _)) = old::CodeStorage::<T>::iter().next() {
318+
log::warn!(
319+
target: LOG_TARGET,
320+
"CodeStorage is still NOT empty, found code_hash: {:?}",
321+
k
322+
);
323+
} else {
324+
log::debug!(target: LOG_TARGET, "CodeStorage is empty.");
325+
}
326+
if let Some((k, _)) = old::OwnerInfoOf::<T, OldCurrency>::iter().next() {
327+
log::warn!(
328+
target: LOG_TARGET,
329+
"OwnerInfoOf is still NOT empty, found code_hash: {:?}",
330+
k
331+
);
332+
} else {
333+
log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty.");
334+
}
335+
336+
let mut deposit: old::BalanceOf<T, OldCurrency> = Default::default();
337+
let mut items = 0u32;
338+
let mut storage_info = 0u32;
339+
CodeInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| {
340+
deposit += v.deposit;
341+
items += 1;
342+
storage_info += v.encoded_size() as u32;
343+
});
344+
let mut storage_code = 0u32;
345+
PristineCode::<T>::iter().for_each(|(_k, v)| {
346+
storage_code += v.len() as u32;
347+
});
348+
let (_, old_deposit, storage_module) = state;
349+
// CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1
350+
// I.e. code info adds up 1 byte per record.
351+
let info_bytes_added = items.clone();
352+
// We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code.
353+
let storage_removed = storage_module.saturating_sub(info_bytes_added);
354+
// module+code+info - bytes
355+
let storage_was = storage_module
356+
.saturating_add(storage_code)
357+
.saturating_add(storage_info)
358+
.saturating_sub(info_bytes_added);
359+
// We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3
360+
// items per code).
361+
let items_removed = items;
362+
log::info!(
363+
target: LOG_TARGET,
364+
"Storage freed, bytes: {} (of {}), items: {} (of {})",
365+
storage_removed,
366+
storage_was,
367+
items_removed,
368+
items_removed * 3,
369+
);
370+
log::info!(
371+
target: LOG_TARGET,
372+
"Deposits returned, total: {:?} Balance (of {:?} Balance)",
373+
old_deposit.saturating_sub(deposit),
374+
old_deposit,
375+
);
376+
377+
Ok(())
378+
}
379+
}

0 commit comments

Comments
 (0)