diff --git a/Cargo.lock b/Cargo.lock index 0b62991d81..c8b36f4ac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,7 +619,7 @@ dependencies = [ [[package]] name = "astar-collator" -version = "5.33.0" +version = "5.34.0" dependencies = [ "astar-primitives", "astar-runtime", @@ -733,6 +733,7 @@ dependencies = [ "log", "orml-traits", "pallet-assets", + "pallet-contracts", "pallet-evm", "pallet-evm-precompile-assets-erc20", "pallet-evm-precompile-dispatch", @@ -751,7 +752,7 @@ dependencies = [ [[package]] name = "astar-runtime" -version = "5.33.0" +version = "5.34.0" dependencies = [ "array-bytes", "astar-primitives", @@ -6618,7 +6619,7 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "local-runtime" -version = "5.33.0" +version = "5.34.0" dependencies = [ "array-bytes", "astar-primitives", @@ -13924,7 +13925,7 @@ dependencies = [ [[package]] name = "shibuya-runtime" -version = "5.33.0" +version = "5.34.0" dependencies = [ "array-bytes", "astar-primitives", @@ -14039,7 +14040,7 @@ dependencies = [ [[package]] name = "shiden-runtime" -version = "5.33.0" +version = "5.34.0" dependencies = [ "array-bytes", "astar-primitives", diff --git a/bin/collator/Cargo.toml b/bin/collator/Cargo.toml index dc05533a39..9ed2053f50 100644 --- a/bin/collator/Cargo.toml +++ b/bin/collator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astar-collator" -version = "5.33.0" +version = "5.34.0" description = "Astar collator implementation in Rust." build = "build.rs" default-run = "astar-collator" diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 8cac9bf8aa..6ec6bf028a 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -36,6 +36,8 @@ xcm-executor = { workspace = true } # ORML dependencies orml-traits = { workspace = true } +pallet-contracts = { workspace = true } + # Frontier dependencies pallet-evm = { workspace = true } @@ -67,8 +69,10 @@ std = [ "fp-evm/std", "pallet-assets/std", "pallet-evm/std", + "pallet-contracts/std", "pallet-evm-precompile-assets-erc20/std", "pallet-evm-precompile-dispatch/std", "sp-arithmetic/std", ] runtime-benchmarks = ["xcm-builder/runtime-benchmarks", "pallet-assets/runtime-benchmarks"] +try-runtime = ["pallet-contracts/try-runtime"] diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 7f7ed3302e..eefc28fd5c 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -44,6 +44,9 @@ pub mod testing; /// Oracle & price primitives. pub mod oracle; +/// Common Migrations +pub mod migrations; + /// Benchmark primitives #[cfg(feature = "runtime-benchmarks")] pub mod benchmarks; diff --git a/primitives/src/migrations/contract_v12.rs b/primitives/src/migrations/contract_v12.rs new file mode 100644 index 0000000000..0cbb866fa5 --- /dev/null +++ b/primitives/src/migrations/contract_v12.rs @@ -0,0 +1,379 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and +//! repay deposits. + +use frame_support::{ + pallet_prelude::*, + storage_alias, + traits::{fungible::Inspect, ReservableCurrency}, + DefaultNoBound, Identity, +}; +use pallet_contracts::{ + migration::{IsFinished, MigrationStep}, + weights::WeightInfo, + Config, Determinism, Pallet, +}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::prelude::format; +use sp_core::hexdisplay::HexDisplay; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating}; +use sp_std::prelude::*; + +const LOG_TARGET: &str = "runtime::contracts"; + +pub type AccountIdOf = ::AccountId; +type CodeHash = ::Hash; +type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; + +mod old { + use super::*; + + pub type BalanceOf = ::AccountId, + >>::Balance; + + #[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] + #[codec(mel_bound())] + #[scale_info(skip_type_params(T, OldCurrency))] + pub struct OwnerInfo + where + OldCurrency: ReservableCurrency<::AccountId>, + { + pub owner: AccountIdOf, + #[codec(compact)] + pub deposit: BalanceOf, + #[codec(compact)] + pub refcount: u64, + } + + #[derive(Encode, Decode, scale_info::TypeInfo)] + #[codec(mel_bound())] + #[scale_info(skip_type_params(T))] + pub struct PrefabWasmModule { + #[codec(compact)] + pub instruction_weights_version: u32, + #[codec(compact)] + pub initial: u32, + #[codec(compact)] + pub maximum: u32, + pub code: Vec, + pub determinism: Determinism, + } + + #[storage_alias] + pub type OwnerInfoOf = + StorageMap, Identity, CodeHash, OwnerInfo>; + + #[storage_alias] + pub type CodeStorage = + StorageMap, Identity, CodeHash, PrefabWasmModule>; +} + +#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T, OldCurrency))] +pub struct CodeInfo +where + OldCurrency: ReservableCurrency<::AccountId>, +{ + owner: AccountIdOf, + #[codec(compact)] + deposit: old::BalanceOf, + #[codec(compact)] + refcount: u64, + determinism: Determinism, + code_len: u32, +} + +#[storage_alias] +pub type CodeInfoOf = + StorageMap, Identity, CodeHash, CodeInfo>; + +#[storage_alias] +pub type PristineCode = StorageMap, Identity, CodeHash, Vec>; + +#[cfg(feature = "runtime-benchmarks")] +pub fn store_old_dummy_code(len: usize, account: T::AccountId) +where + OldCurrency: ReservableCurrency<::AccountId> + 'static, +{ + use sp_runtime::traits::Hash; + + let code = vec![42u8; len]; + let hash = T::Hashing::hash(&code); + PristineCode::::insert(hash, code.clone()); + + let module = old::PrefabWasmModule { + instruction_weights_version: Default::default(), + initial: Default::default(), + maximum: Default::default(), + code, + determinism: Determinism::Enforced, + }; + old::CodeStorage::::insert(hash, module); + + let info = old::OwnerInfo { + owner: account, + deposit: u32::MAX.into(), + refcount: u64::MAX, + }; + old::OwnerInfoOf::::insert(hash, info); +} + +#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] +pub struct Migration +where + OldCurrency: ReservableCurrency<::AccountId>, + OldCurrency::Balance: From>, +{ + last_code_hash: Option>, + _phantom: PhantomData, +} + +impl MigrationStep for Migration +where + OldCurrency: ReservableCurrency<::AccountId> + 'static, + OldCurrency::Balance: From>, +{ + const VERSION: u16 = 12; + + fn max_step_weight() -> Weight { + T::WeightInfo::v12_migration_step(T::MaxCodeLen::get()) + } + + fn step(&mut self) -> (IsFinished, Weight) { + let mut iter = if let Some(last_key) = self.last_code_hash.take() { + old::OwnerInfoOf::::iter_from( + old::OwnerInfoOf::::hashed_key_for(last_key), + ) + } else { + old::OwnerInfoOf::::iter() + }; + if let Some((hash, old_info)) = iter.next() { + log::debug!( + target: LOG_TARGET, + "Migrating OwnerInfo for code_hash {:?}", + hash + ); + + let module = old::CodeStorage::::take(hash) + .expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str()); + + let code_len = module.code.len(); + // We print this to measure the impact of the migration. + // Storage removed: deleted PrefabWasmModule's encoded len. + // Storage added: determinism field encoded len (as all other CodeInfo fields are the + // same as in the deleted OwnerInfo). + log::debug!( + target: LOG_TARGET, + "Storage removed: 1 item, {} bytes", + &code_len, + ); + + // Storage usage prices could change over time, and accounts who uploaded their + // contracts code before the storage deposits where introduced, had not been ever + // charged with any deposit for that (see migration v6). + // + // This is why deposit to be refunded here is calculated as follows: + // + // 1. Calculate the deposit amount for storage before the migration, given current + // prices. + // 2. Given current reserved deposit amount, calculate the correction factor. + // 3. Calculate the deposit amount for storage after the migration, given current + // prices. + // 4. Calculate real deposit amount to be reserved after the migration. + let price_per_byte = T::DepositPerByte::get(); + let price_per_item = T::DepositPerItem::get(); + let bytes_before = module + .encoded_size() + .saturating_add(code_len) + .saturating_add(old::OwnerInfo::::max_encoded_len()) + as u32; + let items_before = 3u32; + let deposit_expected_before = price_per_byte + .saturating_mul(bytes_before.into()) + .saturating_add(price_per_item.saturating_mul(items_before.into())); + let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + let bytes_after = + code_len.saturating_add(CodeInfo::::max_encoded_len()) as u32; + let items_after = 2u32; + let deposit_expected_after = price_per_byte + .saturating_mul(bytes_after.into()) + .saturating_add(price_per_item.saturating_mul(items_after.into())); + let deposit = ratio.saturating_mul_int(deposit_expected_after); + + let info = CodeInfo:: { + determinism: module.determinism, + owner: old_info.owner, + deposit: deposit.into(), + refcount: old_info.refcount, + code_len: code_len as u32, + }; + + let amount = old_info.deposit.saturating_sub(info.deposit); + if !amount.is_zero() { + OldCurrency::unreserve(&info.owner, amount); + log::debug!( + target: LOG_TARGET, + "Deposit refunded: {:?} Balance, to: {:?}", + &amount, + HexDisplay::from(&info.owner.encode()) + ); + } else { + log::warn!( + target: LOG_TARGET, + "new deposit: {:?} >= old deposit: {:?}", + &info.deposit, + &old_info.deposit + ); + } + CodeInfoOf::::insert(hash, info); + + self.last_code_hash = Some(hash); + + ( + IsFinished::No, + T::WeightInfo::v12_migration_step(code_len as u32), + ) + } else { + log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate"); + (IsFinished::Yes, T::WeightInfo::v12_migration_step(0)) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + let len = 100; + log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len); + let sample: Vec<_> = old::OwnerInfoOf::::iter() + .take(len) + .map(|(k, v)| { + let module = old::CodeStorage::::get(k) + .expect("No PrefabWasmModule found for code_hash: {:?}"); + let info: CodeInfo = CodeInfo { + determinism: module.determinism, + deposit: v.deposit, + refcount: v.refcount, + owner: v.owner, + code_len: module.code.len() as u32, + }; + (k, info) + }) + .collect(); + + let storage: u32 = old::CodeStorage::::iter() + .map(|(_k, v)| v.encoded_size() as u32) + .sum(); + let mut deposit: old::BalanceOf = Default::default(); + old::OwnerInfoOf::::iter().for_each(|(_k, v)| deposit += v.deposit); + + Ok((sample, deposit, storage).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(state: Vec) -> Result<(), TryRuntimeError> { + let state = <( + Vec<(CodeHash, CodeInfo)>, + old::BalanceOf, + u32, + ) as Decode>::decode(&mut &state[..]) + .unwrap(); + + log::debug!( + target: LOG_TARGET, + "Validating state of {} Codeinfo(s)", + state.0.len() + ); + for (hash, old) in state.0 { + let info = CodeInfoOf::::get(&hash) + .expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str()); + ensure!(info.determinism == old.determinism, "invalid determinism"); + ensure!(info.owner == old.owner, "invalid owner"); + ensure!(info.refcount == old.refcount, "invalid refcount"); + } + + if let Some((k, _)) = old::CodeStorage::::iter().next() { + log::warn!( + target: LOG_TARGET, + "CodeStorage is still NOT empty, found code_hash: {:?}", + k + ); + } else { + log::debug!(target: LOG_TARGET, "CodeStorage is empty."); + } + if let Some((k, _)) = old::OwnerInfoOf::::iter().next() { + log::warn!( + target: LOG_TARGET, + "OwnerInfoOf is still NOT empty, found code_hash: {:?}", + k + ); + } else { + log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty."); + } + + let mut deposit: old::BalanceOf = Default::default(); + let mut items = 0u32; + let mut storage_info = 0u32; + CodeInfoOf::::iter().for_each(|(_k, v)| { + deposit += v.deposit; + items += 1; + storage_info += v.encoded_size() as u32; + }); + let mut storage_code = 0u32; + PristineCode::::iter().for_each(|(_k, v)| { + storage_code += v.len() as u32; + }); + let (_, old_deposit, storage_module) = state; + // CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1 + // I.e. code info adds up 1 byte per record. + let info_bytes_added = items.clone(); + // We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code. + let storage_removed = storage_module.saturating_sub(info_bytes_added); + // module+code+info - bytes + let storage_was = storage_module + .saturating_add(storage_code) + .saturating_add(storage_info) + .saturating_sub(info_bytes_added); + // We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3 + // items per code). + let items_removed = items; + log::info!( + target: LOG_TARGET, + "Storage freed, bytes: {} (of {}), items: {} (of {})", + storage_removed, + storage_was, + items_removed, + items_removed * 3, + ); + log::info!( + target: LOG_TARGET, + "Deposits returned, total: {:?} Balance (of {:?} Balance)", + old_deposit.saturating_sub(deposit), + old_deposit, + ); + + Ok(()) + } +} diff --git a/primitives/src/migrations/contract_v12_fix.rs b/primitives/src/migrations/contract_v12_fix.rs new file mode 100644 index 0000000000..35b1918f28 --- /dev/null +++ b/primitives/src/migrations/contract_v12_fix.rs @@ -0,0 +1,161 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Migrate CodeInfo from faulty alias introduced on contracts's v12 migration + +use frame_support::{ + pallet_prelude::*, storage_alias, traits::fungible::Inspect, DefaultNoBound, Identity, +}; +use pallet_contracts::{ + migration::{IsFinished, MigrationStep}, + weights::WeightInfo, + Config, Determinism, Pallet, +}; +use parity_scale_codec::{Decode, Encode}; +#[cfg(feature = "try-runtime")] +use scale_info::prelude::format; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_std::marker::PhantomData; +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +const LOG_TARGET: &str = "runtime::contracts"; + +type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; +type AccountIdOf = ::AccountId; +type CodeHash = ::Hash; +type CodeVec = BoundedVec::MaxCodeLen>; + +mod old { + use super::*; + + #[storage_alias] + pub type CodeInfoOf = StorageMap, Twox64Concat, CodeHash, CodeInfo>; +} + +#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct CodeInfo { + owner: AccountIdOf, + #[codec(compact)] + deposit: BalanceOf, + #[codec(compact)] + refcount: u64, + determinism: Determinism, + code_len: u32, +} + +#[storage_alias] +pub type CodeInfoOf = StorageMap, Identity, CodeHash, CodeInfo>; + +#[storage_alias] +pub type PristineCode = StorageMap, Identity, CodeHash, CodeVec>; + +#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] +pub struct Migration { + last_code_hash: Option>, + _phantom: PhantomData, +} + +/// Logic as follows, +/// Since we need to modifiy `CodeInfoOf` mapping we cannot use `iter()` or `drain()` on it as +/// that will be undefined behaviour, so we are iterating over keys of `PristineCode` mappings +/// which are code hashes. +/// +/// Migration Weights: Reusing v12 migration weights as most heavy operation which is moving +/// code info is same. +impl MigrationStep for Migration { + const VERSION: u16 = 15; + + fn max_step_weight() -> Weight { + T::WeightInfo::v12_migration_step(T::MaxCodeLen::get()) + } + + fn step(&mut self) -> (IsFinished, Weight) { + let mut iter = if let Some(last_key) = self.last_code_hash.take() { + PristineCode::::iter_keys_from(PristineCode::::hashed_key_for(last_key)) + } else { + PristineCode::::iter_keys() + }; + + if let Some(code_hash) = iter.next() { + if let Some(code_info) = old::CodeInfoOf::::take(code_hash) { + log::debug!( + target: LOG_TARGET, + "Migrating CodeInfoOf for code_hash {:?}", + code_hash + ); + + let code_len = code_info.code_len; + + CodeInfoOf::::insert(code_hash, code_info); + + self.last_code_hash = Some(code_hash); + (IsFinished::No, T::WeightInfo::v12_migration_step(code_len)) + } else { + log::warn!( + target: LOG_TARGET, + "No CodeInfo found for code_hash {:?}, maybe new contract?", + code_hash + ); + // old CodeInfo not found, it's newly deployed contract + self.last_code_hash = Some(code_hash); + (IsFinished::No, T::WeightInfo::v12_migration_step(0)) + } + } else { + log::debug!(target: LOG_TARGET, "No more CodeInfo to migrate"); + (IsFinished::Yes, T::WeightInfo::v12_migration_step(0)) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + let len = 100; + let sample: Vec<_> = old::CodeInfoOf::::iter_keys().take(len).collect(); + log::debug!( + target: LOG_TARGET, + "Taking sample of {} CodeInfoOf(s)", + sample.len() + ); + + Ok(sample.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(state: Vec) -> Result<(), TryRuntimeError> { + let state = > as Decode>::decode(&mut &state[..]).unwrap(); + + log::debug!( + target: LOG_TARGET, + "Validating state of {} Codeinfo(s)", + state.len() + ); + for hash in state { + ensure!( + old::CodeInfoOf::::get(&hash).is_none(), + "Old CodeInfoFor is not none!" + ); + let _ = CodeInfoOf::::get(&hash) + .expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str()); + } + Ok(()) + } +} diff --git a/primitives/src/migrations/contract_v14.rs b/primitives/src/migrations/contract_v14.rs new file mode 100644 index 0000000000..4c5516fa22 --- /dev/null +++ b/primitives/src/migrations/contract_v14.rs @@ -0,0 +1,284 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Update the code owner balance, make the code upload deposit balance to be held instead of +//! reserved. Since [`Currency`](frame_support::traits::Currency) has been +//! [deprecated](https://github.com/paritytech/substrate/pull/12951), we need the deposits to be +//! handled by the [`frame_support::traits::fungible`] traits. + +#[cfg(feature = "try-runtime")] +use frame_support::traits::fungible::InspectHold; +use frame_support::{ + pallet_prelude::*, + storage_alias, + traits::{ + fungible::{Inspect, MutateHold}, + ReservableCurrency, + }, + DefaultNoBound, +}; +use pallet_contracts::{ + migration::{IsFinished, MigrationStep}, + weights::WeightInfo, + Config, Determinism, HoldReason, Pallet, +}; +use parity_scale_codec::{Decode, Encode}; +use sp_core::hexdisplay::HexDisplay; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_runtime::{traits::Zero, Saturating}; +#[cfg(feature = "try-runtime")] +use sp_std::collections::btree_map::BTreeMap; +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +const LOG_TARGET: &str = "runtime::contracts"; + +type CodeHash = ::Hash; +type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; +pub type AccountIdOf = ::AccountId; + +mod old { + use super::*; + + pub type BalanceOf = ::AccountId, + >>::Balance; + + #[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] + #[codec(mel_bound())] + #[scale_info(skip_type_params(T, OldCurrency))] + pub struct CodeInfo + where + T: Config, + OldCurrency: ReservableCurrency<::AccountId>, + { + pub owner: AccountIdOf, + #[codec(compact)] + pub deposit: old::BalanceOf, + #[codec(compact)] + pub refcount: u64, + pub determinism: Determinism, + pub code_len: u32, + } + + #[storage_alias] + pub type CodeInfoOf = + StorageMap, Identity, CodeHash, CodeInfo>; +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn store_dummy_code(account: T::AccountId) +where + T: Config, + OldCurrency: ReservableCurrency<::AccountId> + 'static, +{ + use sp_runtime::traits::Hash; + use sp_std::vec; + + let len = T::MaxCodeLen::get(); + let code = vec![42u8; len as usize]; + let hash = T::Hashing::hash(&code); + + let info = old::CodeInfo { + owner: account, + deposit: 10_000u32.into(), + refcount: u64::MAX, + determinism: Determinism::Enforced, + code_len: len, + }; + old::CodeInfoOf::::insert(hash, info); +} + +#[cfg(feature = "try-runtime")] +#[derive(Encode, Decode)] +/// Accounts for the balance allocation of a code owner. +struct BalanceAllocation +where + T: Config, + OldCurrency: ReservableCurrency<::AccountId>, +{ + /// Total reserved balance as code upload deposit for the owner. + reserved: old::BalanceOf, + /// Total balance of the owner. + total: old::BalanceOf, +} + +#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] +pub struct Migration +where + T: Config, + OldCurrency: ReservableCurrency<::AccountId>, +{ + last_code_hash: Option>, + _phantom: PhantomData<(T, OldCurrency)>, +} + +impl MigrationStep for Migration +where + T: Config, + OldCurrency: 'static + ReservableCurrency<::AccountId>, + BalanceOf: From, +{ + const VERSION: u16 = 14; + + fn max_step_weight() -> Weight { + T::WeightInfo::v14_migration_step() + } + + fn step(&mut self) -> (IsFinished, Weight) { + let mut iter = if let Some(last_hash) = self.last_code_hash.take() { + old::CodeInfoOf::::iter_from( + old::CodeInfoOf::::hashed_key_for(last_hash), + ) + } else { + old::CodeInfoOf::::iter() + }; + + if let Some((hash, code_info)) = iter.next() { + log::debug!( + target: LOG_TARGET, + "Migrating code upload deposit for 0x{:?}", + HexDisplay::from(&code_info.owner.encode()) + ); + + let remaining = OldCurrency::unreserve(&code_info.owner, code_info.deposit); + + if remaining > Zero::zero() { + log::warn!( + target: LOG_TARGET, + "Code owner's account 0x{:?} for code {:?} has some non-unreservable deposit {:?} from a total of {:?} that will remain in reserved.", + HexDisplay::from(&code_info.owner.encode()), + hash, + remaining, + code_info.deposit + ); + } + + let unreserved = code_info.deposit.saturating_sub(remaining); + let amount = BalanceOf::::from(unreserved); + + log::debug!( + target: LOG_TARGET, + "Holding {:?} on the code owner's account 0x{:?} for code {:?}.", + amount, + HexDisplay::from(&code_info.owner.encode()), + hash, + ); + + T::Currency::hold( + &HoldReason::CodeUploadDepositReserve.into(), + &code_info.owner, + amount, + ) + .unwrap_or_else(|err| { + log::error!( + target: LOG_TARGET, + "Failed to hold {:?} from the code owner's account 0x{:?} for code {:?}, reason: {:?}.", + amount, + HexDisplay::from(&code_info.owner.encode()), + hash, + err + ); + }); + + self.last_code_hash = Some(hash); + (IsFinished::No, T::WeightInfo::v14_migration_step()) + } else { + log::debug!(target: LOG_TARGET, "No more code upload deposit to migrate"); + (IsFinished::Yes, T::WeightInfo::v14_migration_step()) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + let info: Vec<_> = old::CodeInfoOf::::iter().collect(); + + let mut owner_balance_allocation = + BTreeMap::, BalanceAllocation>::new(); + + // Calculates the balance allocation by accumulating the code upload deposits of all codes + // owned by an owner. + for (_, code_info) in info { + owner_balance_allocation + .entry(code_info.owner.clone()) + .and_modify(|alloc| { + alloc.reserved = alloc.reserved.saturating_add(code_info.deposit); + }) + .or_insert(BalanceAllocation { + reserved: code_info.deposit, + total: OldCurrency::total_balance(&code_info.owner), + }); + } + + Ok(owner_balance_allocation.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(state: Vec) -> Result<(), TryRuntimeError> { + let owner_balance_allocation = + , BalanceAllocation> as Decode>::decode( + &mut &state[..], + ) + .expect("pre_upgrade_step provides a valid state; qed"); + + let mut total_held: BalanceOf = Zero::zero(); + let count = owner_balance_allocation.len(); + for (owner, old_balance_allocation) in owner_balance_allocation { + let held = + T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &owner); + log::debug!( + target: LOG_TARGET, + "Validating code upload deposit for owner 0x{:?}, reserved: {:?}, held: {:?}", + HexDisplay::from(&owner.encode()), + old_balance_allocation.reserved, + held + ); + ensure!( + held == old_balance_allocation.reserved.into(), + "Held amount mismatch" + ); + + log::debug!( + target: LOG_TARGET, + "Validating total balance for owner 0x{:?}, new: {:?}, old: {:?}", + HexDisplay::from(&owner.encode()), + T::Currency::total_balance(&owner), + old_balance_allocation.total + ); + ensure!( + T::Currency::total_balance(&owner) + == BalanceOf::::decode(&mut &old_balance_allocation.total.encode()[..]) + .unwrap(), + "Balance mismatch " + ); + total_held += held; + } + + log::info!(target: LOG_TARGET, "Code owners processed: {:?}.", count); + + log::info!( + target: LOG_TARGET, + "Total held amount for code upload deposit: {:?}", + total_held + ); + + Ok(()) + } +} diff --git a/primitives/src/migrations/mod.rs b/primitives/src/migrations/mod.rs new file mode 100644 index 0000000000..7d4ee838f1 --- /dev/null +++ b/primitives/src/migrations/mod.rs @@ -0,0 +1,66 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +use frame_support::traits::{OnRuntimeUpgrade, PalletInfoAccess, StorageVersion}; +use frame_support::weights::Weight; +use sp_core::Get; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_std::marker::PhantomData; +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +pub mod contract_v12; +pub mod contract_v12_fix; +pub mod contract_v14; + +/// Force set pallet-contracts version +pub struct ForceContractsVersion { + _phantom: PhantomData, +} + +impl OnRuntimeUpgrade for ForceContractsVersion { + fn on_runtime_upgrade() -> Weight { + StorageVersion::new(V).put::>(); + ::DbWeight::get().writes(1) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + let pallet_version = StorageVersion::get::>(); + ensure!(pallet_version == V, "Contracts pallet version mismatch"); + Ok(()) + } +} + +/// Noop migration that checks for pallet version in try-runtime + +pub struct EnsurePalletVersion { + _phantam: PhantomData

, +} + +impl OnRuntimeUpgrade for EnsurePalletVersion { + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + let pallet_version = StorageVersion::get::

(); + ensure!(pallet_version == V, "Pallet version mismatch"); + Ok(()) + } +} diff --git a/runtime/astar/Cargo.toml b/runtime/astar/Cargo.toml index 5853b5e51a..9f2c9fdcfd 100644 --- a/runtime/astar/Cargo.toml +++ b/runtime/astar/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astar-runtime" -version = "5.33.0" +version = "5.34.0" build = "build.rs" authors.workspace = true edition.workspace = true diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index 0e8a8a353a..39aa53a2c2 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -148,7 +148,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("astar"), impl_name: create_runtime_str!("astar"), authoring_version: 1, - spec_version: 81, + spec_version: 82, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -713,9 +713,9 @@ impl pallet_contracts::Config for Runtime { type Debug = (); type Environment = (); type Migrations = ( - pallet_contracts::migration::v12::Migration, + astar_primitives::migrations::contract_v12::Migration, pallet_contracts::migration::v13::Migration, - pallet_contracts::migration::v14::Migration, + astar_primitives::migrations::contract_v14::Migration, pallet_contracts::migration::v15::Migration, ); } diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index a1ddf3460e..bb4c741159 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "local-runtime" -version = "5.33.0" +version = "5.34.0" build = "build.rs" authors.workspace = true edition.workspace = true diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index fd66d7c4c1..544d505ba6 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shibuya-runtime" -version = "5.33.0" +version = "5.34.0" build = "build.rs" authors.workspace = true edition.workspace = true diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index 5218a771e9..176fc11f12 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -173,7 +173,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("shibuya"), impl_name: create_runtime_str!("shibuya"), authoring_version: 1, - spec_version: 123, + spec_version: 124, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -713,12 +713,7 @@ impl pallet_contracts::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = (); type Environment = (); - type Migrations = ( - pallet_contracts::migration::v12::Migration, - pallet_contracts::migration::v13::Migration, - pallet_contracts::migration::v14::Migration, - pallet_contracts::migration::v15::Migration, - ); + type Migrations = (astar_primitives::migrations::contract_v12_fix::Migration,); } // These values are based on the Astar 2.0 Tokenomics Modeling report. @@ -1371,7 +1366,13 @@ pub type Executive = frame_executive::Executive< /// All migrations that will run on the next runtime upgrade. /// /// Once done, migrations should be removed from the tuple. -pub type Migrations = (pallet_contracts::Migration,); +pub type Migrations = ( + // [contracts fix] Part of shiden-121 (added after v5.33.0 release) + // bump down version to 14 --> fixed storage alias and restore to 15 --> ensure it's 15 + astar_primitives::migrations::ForceContractsVersion, + pallet_contracts::Migration, + astar_primitives::migrations::EnsurePalletVersion, +); type EventRecord = frame_system::EventRecord< ::RuntimeEvent, diff --git a/runtime/shiden/Cargo.toml b/runtime/shiden/Cargo.toml index d335b30e35..e8aaadc193 100644 --- a/runtime/shiden/Cargo.toml +++ b/runtime/shiden/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shiden-runtime" -version = "5.33.0" +version = "5.34.0" build = "build.rs" authors.workspace = true edition.workspace = true @@ -239,6 +239,7 @@ runtime-benchmarks = [ "pallet-dynamic-evm-base-fee/runtime-benchmarks", ] try-runtime = [ + "astar-primitives/try-runtime", "fp-self-contained/try-runtime", "log", "frame-try-runtime/try-runtime", diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index d6779e2ab3..0f6ff496e7 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -150,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("shiden"), impl_name: create_runtime_str!("shiden"), authoring_version: 1, - spec_version: 120, + spec_version: 121, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -677,12 +677,7 @@ impl pallet_contracts::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = (); type Environment = (); - type Migrations = ( - pallet_contracts::migration::v12::Migration, - pallet_contracts::migration::v13::Migration, - pallet_contracts::migration::v14::Migration, - pallet_contracts::migration::v15::Migration, - ); + type Migrations = (astar_primitives::migrations::contract_v12_fix::Migration,); } parameter_types! { @@ -1116,34 +1111,20 @@ pub type Executive = frame_executive::Executive< Migrations, >; -parameter_types! { - pub const DappStakingMigrationName: &'static str = "DappStakingMigration"; -} /// All migrations that will run on the next runtime upgrade. /// /// Once done, migrations should be removed from the tuple. pub type Migrations = ( + // [contracts fix] Part of shiden-121 (added after v5.33.0 release) + // bump down version to 14 --> fixed storage alias and restore to 15 --> ensure it's 15 + astar_primitives::migrations::ForceContractsVersion, + pallet_contracts::Migration, + astar_primitives::migrations::EnsurePalletVersion, // Part of shiden-121 (added after v5.33.0 release) SetNewTierConfig, ); use frame_support::traits::OnRuntimeUpgrade; -pub struct RecalculationEraFix; -impl OnRuntimeUpgrade for RecalculationEraFix { - fn on_runtime_upgrade() -> Weight { - let first_dapp_staking_v3_era = 743; - - let expected_recalculation_era = - InflationCycleConfig::eras_per_cycle().saturating_add(first_dapp_staking_v3_era); - - pallet_inflation::ActiveInflationConfig::::mutate(|config| { - config.recalculation_era = expected_recalculation_era; - }); - - ::DbWeight::get().reads_writes(1, 1) - } -} - pub struct SetNewTierConfig; impl OnRuntimeUpgrade for SetNewTierConfig { fn on_runtime_upgrade() -> Weight {