diff --git a/substrate/frame/contracts/src/benchmarking/mod.rs b/substrate/frame/contracts/src/benchmarking/mod.rs index 048465c1b71583aeb4cbb5558ac30fdc27a98afa..3eb5e8ed2fc0318d79ad8e6489e1e0381279b88d 100644 --- a/substrate/frame/contracts/src/benchmarking/mod.rs +++ b/substrate/frame/contracts/src/benchmarking/mod.rs @@ -30,7 +30,7 @@ use self::{ }; use crate::{ exec::{AccountIdOf, Key}, - migration::{v10, v11, v9, Migrate}, + migration::{v10, v11, v9, MigrationStep}, wasm::CallFlags, Pallet as Contracts, *, }; @@ -237,7 +237,7 @@ benchmarks! { // This benchmarks the v9 migration step. (update codeStorage) #[pov_mode = Measured] v9_migration_step { - let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); + let c in 0 .. T::MaxCodeLen::get(); v9::store_old_dummy_code::<T>(c as usize); let mut m = v9::Migration::<T>::default(); }: { @@ -251,7 +251,7 @@ benchmarks! { whitelisted_caller(), WasmModule::dummy(), vec![], )?; - v10::store_old_contrat_info::<T>(contract.account_id.clone(), contract.info()?); + v10::store_old_contract_info::<T>(contract.account_id.clone(), contract.info()?); let mut m = v10::Migration::<T>::default(); }: { m.step(); @@ -277,15 +277,15 @@ benchmarks! { assert_eq!(StorageVersion::get::<Pallet<T>>(), 2); } - // This benchmarks the weight of executing Migration::migrate when there are no migration in progress. + // This benchmarks the weight of dispatching migrate to execute 1 `NoopMigraton` #[pov_mode = Measured] migrate { StorageVersion::new(0).put::<Pallet<T>>(); <Migration::<T, false> as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); - let origin: RawOrigin<<T as frame_system::Config>::AccountId> = RawOrigin::Signed(whitelisted_caller()); - }: { - <Contracts<T>>::migrate(origin.into(), Weight::MAX).unwrap() - } verify { + let caller: T::AccountId = whitelisted_caller(); + let origin = RawOrigin::Signed(caller.clone()); + }: _(origin, Weight::MAX) + verify { assert_eq!(StorageVersion::get::<Pallet<T>>(), 1); } diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index a8cf473a45c8e487882990eea5227eb5e397b690..0b765a2e89956936cf439ea1006cbeec209c76cd 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -80,6 +80,7 @@ //! an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables writing //! WebAssembly based smart contracts in the Rust programming language. +#![allow(rustdoc::private_intra_doc_links)] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "1024")] @@ -328,19 +329,35 @@ pub mod pallet { /// # struct Runtime {}; /// type Migrations = (v9::Migration<Runtime>, v10::Migration<Runtime>, v11::Migration<Runtime>); /// ``` + /// + /// If you have a single migration step, you can use a tuple with a single element: + /// ``` + /// use pallet_contracts::migration::v9; + /// # struct Runtime {}; + /// type Migrations = (v9::Migration<Runtime>,); + /// ``` type Migrations: MigrateSequence; } #[pallet::hooks] impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { - fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { + fn on_idle(_block: T::BlockNumber, mut remaining_weight: Weight) -> Weight { use migration::MigrateResult::*; - let (result, weight) = Migration::<T>::migrate(remaining_weight); - let remaining_weight = remaining_weight.saturating_sub(weight); - - if !matches!(result, Completed | NoMigrationInProgress) { - return weight + loop { + let (result, weight) = Migration::<T>::migrate(remaining_weight); + remaining_weight.saturating_reduce(weight); + + match result { + // There is not enough weight to perform a migration, or make any progress, we + // just return the remaining weight. + NoMigrationPerformed | InProgress { steps_done: 0 } => return remaining_weight, + // Migration is still in progress, we can start the next step. + InProgress { .. } => continue, + // Either no migration is in progress, or we are done with all migrations, we + // can do some more other work with the remaining weight. + Completed | NoMigrationInProgress => break, + } } ContractInfo::<T>::process_deletion_queue_batch(remaining_weight) @@ -987,6 +1004,8 @@ pub mod pallet { pub(crate) type DeletionQueueCounter<T: Config> = StorageValue<_, DeletionQueueManager<T>, ValueQuery>; + /// A migration can span across multiple blocks. This storage defines a cursor to track the + /// progress of the migration, enabling us to resume from the last completed position. #[pallet::storage] pub(crate) type MigrationInProgress<T: Config> = StorageValue<_, migration::Cursor, OptionQuery>; diff --git a/substrate/frame/contracts/src/migration.rs b/substrate/frame/contracts/src/migration.rs index 41fc9753a09502bb93a0a74835537a7a46f7a44f..5da1bb70a62045f89b1ba3cfa59ac6c078769bad 100644 --- a/substrate/frame/contracts/src/migration.rs +++ b/substrate/frame/contracts/src/migration.rs @@ -15,7 +15,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Migration framework for pallets. +//! Multi-block Migration framework for pallet-contracts. +//! +//! This module allows us to define a migration as a sequence of [`MigrationStep`]s that can be +//! executed across multiple blocks. +//! +//! # Usage +//! +//! A migration step is defined under `src/migration/vX.rs`, where `X` is the version number. +//! For example, `vX.rs` defines a migration from version `X - 1` to version `X`. +//! +//! ## Example: +//! +//! To configure a migration to `v11` for a runtime using `v8` of pallet-contracts on the chain, you +//! would set the `Migrations` type as follows: +//! +//! ``` +//! use pallet_contracts::migration::{v9, v10, v11}; +//! # pub enum Runtime {}; +//! type Migrations = (v9::Migration<Runtime>, v10::Migration<Runtime>, v11::Migration<Runtime>); +//! ``` +//! +//! ## Notes: +//! +//! - Migrations should always be tested with `try-runtime` before being deployed. +//! - By testing with `try-runtime` against a live network, you ensure that all migration steps work +//! and that you have included the required steps. +//! +//! ## Low Level / Implementation Details +//! +//! When a migration starts and [`OnRuntimeUpgrade::on_runtime_upgrade`] is called, instead of +//! performing the actual migration, we set a custom storage item [`MigrationInProgress`]. +//! This storage item defines a [`Cursor`] for the current migration. +//! +//! If the [`MigrationInProgress`] storage item exists, it means a migration is in progress, and its +//! value holds a cursor for the current migration step. These migration steps are executed during +//! [`Hooks<BlockNumber>::on_idle`] or when the [`Pallet::migrate`] dispatchable is +//! called. +//! +//! While the migration is in progress, all dispatchables except `migrate`, are blocked, and returns +//! a `MigrationInProgress` error. pub mod v10; pub mod v11; @@ -28,6 +67,7 @@ use frame_support::{ pallet_prelude::*, traits::{ConstU32, OnRuntimeUpgrade}, }; +use sp_runtime::Saturating; use sp_std::marker::PhantomData; #[cfg(feature = "try-runtime")] @@ -44,7 +84,8 @@ fn invalid_version(version: StorageVersion) -> ! { panic!("Required migration {version:?} not supported by this runtime. This is a bug."); } -/// The cursor used to store the state of the current migration step. +/// The cursor used to encode the position (usually the last iterated key) of the current migration +/// step. pub type Cursor = BoundedVec<u8, ConstU32<1024>>; /// IsFinished describes whether a migration is finished or not. @@ -57,7 +98,7 @@ pub enum IsFinished { /// /// The migration is done in steps. The migration is finished when /// `step()` returns `IsFinished::Yes`. -pub trait Migrate: Codec + MaxEncodedLen + Default { +pub trait MigrationStep: Codec + MaxEncodedLen + Default { /// Returns the version of the migration. const VERSION: u16; @@ -110,7 +151,7 @@ pub trait Migrate: Codec + MaxEncodedLen + Default { #[derive(frame_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)] pub struct NoopMigration<const N: u16>; -impl<const N: u16> Migrate for NoopMigration<N> { +impl<const N: u16> MigrationStep for NoopMigration<N> { const VERSION: u16 = N; fn max_step_weight() -> Weight { Weight::zero() @@ -122,10 +163,10 @@ impl<const N: u16> Migrate for NoopMigration<N> { } mod private { - use crate::migration::Migrate; + use crate::migration::MigrationStep; pub trait Sealed {} #[impl_trait_for_tuples::impl_for_tuples(10)] - #[tuple_types_custom_trait_bound(Migrate)] + #[tuple_types_custom_trait_bound(MigrationStep)] impl Sealed for Tuple {} } @@ -134,11 +175,11 @@ mod private { /// The sequence must be defined by a tuple of migrations, each of which must implement the /// `Migrate` trait. Migrations must be ordered by their versions with no gaps. pub trait MigrateSequence: private::Sealed { - /// Returns the range of versions that this migration can handle. + /// Returns the range of versions that this migrations sequence can handle. /// Migrations must be ordered by their versions with no gaps. - /// The following code will fail to compile: /// /// The following code will fail to compile: + /// /// ```compile_fail /// # use pallet_contracts::{NoopMigration, MigrateSequence}; /// let _ = <(NoopMigration<1>, NoopMigration<3>)>::VERSION_RANGE; @@ -172,21 +213,10 @@ pub trait MigrateSequence: private::Sealed { /// Returns whether migrating from `in_storage` to `target` is supported. /// - /// A migration is supported if (in_storage + 1, target) is contained by `VERSION_RANGE`. + /// A migration is supported if `VERSION_RANGE` is (in_storage + 1, target). fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool { - if in_storage == target { - return true - } - if in_storage > target { - return false - } - let (low, high) = Self::VERSION_RANGE; - let Some(first_supported) = low.checked_sub(1) else { - return false - }; - - in_storage >= first_supported && target == high + target == high && in_storage + 1 == low } } @@ -276,17 +306,20 @@ impl<T: Config, const TEST_ALL_STEPS: bool> OnRuntimeUpgrade for Migration<T, TE let storage_version = <Pallet<T>>::on_chain_storage_version(); let target_version = <Pallet<T>>::current_storage_version(); + ensure!( + storage_version != target_version, + "No upgrade: Please remove this migration from your runtime upgrade configuration." + ); + log::debug!( target: LOG_TARGET, - "{}: Range supported {:?}, range requested {:?}", - <Pallet<T>>::name(), - T::Migrations::VERSION_RANGE, - (storage_version, target_version) + "Requested migration of {} from {:?}(on-chain storage version) to {:?}(current storage version)", + <Pallet<T>>::name(), storage_version, target_version ); ensure!( T::Migrations::is_upgrade_supported(storage_version, target_version), - "Unsupported upgrade" + "Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, current storage version)" ); Ok(Default::default()) } @@ -313,7 +346,8 @@ pub enum StepResult { } impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> { - /// Verify that each migration's step of the [`T::Migrations`] sequence fits into `Cursor`. + /// Verify that each migration's step of the [`Config::Migrations`] sequence fits into + /// `Cursor`. pub(crate) fn integrity_test() { let max_weight = <T as frame_system::Config>::BlockWeights::get().max_block; T::Migrations::integrity_test(max_weight) @@ -394,7 +428,7 @@ impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> { } #[impl_trait_for_tuples::impl_for_tuples(10)] -#[tuple_types_custom_trait_bound(Migrate)] +#[tuple_types_custom_trait_bound(MigrationStep)] impl MigrateSequence for Tuple { const VERSION_RANGE: (u16, u16) = { let mut versions: (u16, u16) = (0, 0); @@ -461,7 +495,7 @@ impl MigrateSequence for Tuple { let mut steps_done = 0; while weight_left.all_gt(max_weight) { let (finished, weight) = migration.step(); - steps_done += 1; + steps_done.saturating_accrue(1); weight_left.saturating_reduce(weight); if matches!(finished, IsFinished::Yes) { return StepResult::Completed{ steps_done } @@ -494,7 +528,7 @@ mod test { count: u16, } - impl<const N: u16> Migrate for MockMigration<N> { + impl<const N: u16> MigrationStep for MockMigration<N> { const VERSION: u16 = N; fn max_step_weight() -> Weight { Weight::from_all(1) @@ -519,30 +553,9 @@ mod test { #[test] fn is_upgrade_supported_works() { type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>); - - [(1, 1), (8, 11), (9, 11)].into_iter().for_each(|(from, to)| { - assert!( - Migrations::is_upgrade_supported( - StorageVersion::new(from), - StorageVersion::new(to) - ), - "{} -> {} is supported", - from, - to - ) - }); - - [(1, 0), (0, 3), (7, 11), (8, 10)].into_iter().for_each(|(from, to)| { - assert!( - !Migrations::is_upgrade_supported( - StorageVersion::new(from), - StorageVersion::new(to) - ), - "{} -> {} is not supported", - from, - to - ) - }); + assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12))); } #[test] diff --git a/substrate/frame/contracts/src/migration/v10.rs b/substrate/frame/contracts/src/migration/v10.rs index cddf67a53c4f88e3e4a3ff6655cd8ae42ab9df67..75d794a6d816700db4d022db54be637cb35c01c3 100644 --- a/substrate/frame/contracts/src/migration/v10.rs +++ b/substrate/frame/contracts/src/migration/v10.rs @@ -16,12 +16,12 @@ // limitations under the License. //! Don't rely on reserved balances keeping an account alive -//! See <https://github.com/paritytech/substrate/pull/13370>. +//! See <https://github.com/paritytech/substrate/pull/13369>. use crate::{ address::AddressGenerator, exec::AccountIdOf, - migration::{IsFinished, Migrate}, + migration::{IsFinished, MigrationStep}, weights::WeightInfo, BalanceOf, CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET, }; @@ -42,7 +42,7 @@ use sp_core::hexdisplay::HexDisplay; #[cfg(feature = "try-runtime")] use sp_runtime::TryRuntimeError; use sp_runtime::{traits::Zero, Perbill, Saturating}; -use sp_std::{marker::PhantomData, ops::Deref, prelude::*}; +use sp_std::{ops::Deref, prelude::*}; mod old { use super::*; @@ -69,7 +69,7 @@ mod old { } #[cfg(feature = "runtime-benchmarks")] -pub fn store_old_contrat_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) { +pub fn store_old_contract_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) { let info = old::ContractInfo { trie_id: info.trie_id, code_hash: info.code_hash, @@ -109,15 +109,14 @@ pub struct ContractInfo<T: Config> { #[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] pub struct Migration<T: Config> { - last_key: Option<BoundedVec<u8, ConstU32<256>>>, - _phantom: PhantomData<T>, + last_account: Option<T::AccountId>, } #[storage_alias] type ContractInfoOf<T: Config> = StorageMap<Pallet<T>, Twox64Concat, <T as frame_system::Config>::AccountId, ContractInfo<T>>; -impl<T: Config> Migrate for Migration<T> { +impl<T: Config> MigrationStep for Migration<T> { const VERSION: u16 = 10; fn max_step_weight() -> Weight { @@ -125,8 +124,10 @@ impl<T: Config> Migrate for Migration<T> { } fn step(&mut self) -> (IsFinished, Weight) { - let mut iter = if let Some(last_key) = self.last_key.take() { - old::ContractInfoOf::<T>::iter_from(last_key.to_vec()) + let mut iter = if let Some(last_account) = self.last_account.take() { + old::ContractInfoOf::<T>::iter_from(old::ContractInfoOf::<T>::hashed_key_for( + last_account, + )) } else { old::ContractInfoOf::<T>::iter() }; @@ -135,9 +136,6 @@ impl<T: Config> Migrate for Migration<T> { let min_balance = Pallet::<T>::min_balance(); log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode())); - // Store last key for next migration step - self.last_key = Some(iter.last_raw_key().to_vec().try_into().unwrap()); - // Get the new deposit account address let deposit_account: DepositAccount<T> = DepositAccount(T::AddressGenerator::deposit_address(&account)); @@ -181,14 +179,14 @@ impl<T: Config> Migrate for Migration<T> { }) // If it fails we fallback to minting the ED. .unwrap_or_else(|err| { - log::error!(target: LOG_TARGET, "Failed to transfer ED, reason: {:?}", err); + log::error!(target: LOG_TARGET, "Failed to transfer the base deposit, reason: {:?}", err); T::Currency::deposit_creating(&deposit_account, min_balance); min_balance }); // Calculate the new base_deposit to store in the contract: - // Ideally: it should be the same as the old one - // Ideally, it should be at least 2xED (for the contract and deposit account). + // Ideally, it should be the same as the old one + // Ideally, it should be at least 2xED (for the contract and deposit accounts). // It can't be more than the `new_deposit`. let new_base_deposit = min( max(contract.storage_base_deposit, min_balance.saturating_add(min_balance)), @@ -223,6 +221,10 @@ impl<T: Config> Migrate for Migration<T> { }; ContractInfoOf::<T>::insert(&account, new_contract_info); + + // Store last key for next migration step + self.last_account = Some(account); + (IsFinished::No, T::WeightInfo::v10_migration_step()) } else { log::debug!(target: LOG_TARGET, "Done Migrating contract info"); @@ -240,8 +242,8 @@ impl<T: Config> Migrate for Migration<T> { #[cfg(feature = "try-runtime")] fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> { - let sample = - <Vec<(T::AccountId, old::ContractInfo<T>)> as Decode>::decode(&mut &state[..]).unwrap(); + let sample = <Vec<(T::AccountId, old::ContractInfo<T>)> as Decode>::decode(&mut &state[..]) + .expect("pre_upgrade_step provides a valid state; qed"); log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len()); for (account, old_contract) in sample { diff --git a/substrate/frame/contracts/src/migration/v11.rs b/substrate/frame/contracts/src/migration/v11.rs index 27a4b96431e0641224ee2080369777571089d6de..67740cfaf6c804288f5ded7de716f3de052d6c1a 100644 --- a/substrate/frame/contracts/src/migration/v11.rs +++ b/substrate/frame/contracts/src/migration/v11.rs @@ -19,7 +19,7 @@ //! See <https://github.com/paritytech/substrate/pull/13702>. use crate::{ - migration::{IsFinished, Migrate}, + migration::{IsFinished, MigrationStep}, weights::WeightInfo, Config, Pallet, TrieId, Weight, LOG_TARGET, }; @@ -69,7 +69,7 @@ pub struct Migration<T: Config> { _phantom: PhantomData<T>, } -impl<T: Config> Migrate for Migration<T> { +impl<T: Config> MigrationStep for Migration<T> { const VERSION: u16 = 11; // It would be more correct to make our use the now removed [DeletionQueueDepth](https://github.com/paritytech/substrate/pull/13702/files#diff-70e9723e9db62816e35f6f885b6770a8449c75a6c2733e9fa7a245fe52c4656c) @@ -121,7 +121,8 @@ impl<T: Config> Migrate for Migration<T> { #[cfg(feature = "try-runtime")] fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> { - let len = <u32 as Decode>::decode(&mut &state[..]).unwrap(); + let len = <u32 as Decode>::decode(&mut &state[..]) + .expect("pre_upgrade_step provides a valid state; qed"); let counter = <DeletionQueueCounter<T>>::get(); ensure!(counter.insert_counter == len, "invalid insert counter"); ensure!(counter.delete_counter == 0, "invalid delete counter"); diff --git a/substrate/frame/contracts/src/migration/v9.rs b/substrate/frame/contracts/src/migration/v9.rs index 3dd86a89cf06da10783fcceb640b53c316d5c09e..e6c664295564202f3639bb883b0d3923b28a3088 100644 --- a/substrate/frame/contracts/src/migration/v9.rs +++ b/substrate/frame/contracts/src/migration/v9.rs @@ -18,17 +18,15 @@ //! Update `CodeStorage` with the new `determinism` field. use crate::{ - migration::{IsFinished, Migrate}, + migration::{IsFinished, MigrationStep}, weights::WeightInfo, CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET, }; use codec::{Decode, Encode}; -use frame_support::{ - codec, pallet_prelude::*, storage_alias, BoundedVec, DefaultNoBound, Identity, -}; +use frame_support::{codec, pallet_prelude::*, storage_alias, DefaultNoBound, Identity}; #[cfg(feature = "try-runtime")] use sp_runtime::TryRuntimeError; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::prelude::*; mod old { use super::*; @@ -79,11 +77,10 @@ type CodeStorage<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, Prefa #[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] pub struct Migration<T: Config> { - last_key: Option<BoundedVec<u8, ConstU32<256>>>, - _phantom: PhantomData<T>, + last_code_hash: Option<CodeHash<T>>, } -impl<T: Config> Migrate for Migration<T> { +impl<T: Config> MigrationStep for Migration<T> { const VERSION: u16 = 9; fn max_step_weight() -> Weight { @@ -91,8 +88,8 @@ impl<T: Config> Migrate for Migration<T> { } fn step(&mut self) -> (IsFinished, Weight) { - let mut iter = if let Some(last_key) = self.last_key.take() { - old::CodeStorage::<T>::iter_from(last_key.to_vec()) + let mut iter = if let Some(last_key) = self.last_code_hash.take() { + old::CodeStorage::<T>::iter_from(old::CodeStorage::<T>::hashed_key_for(last_key)) } else { old::CodeStorage::<T>::iter() }; @@ -108,7 +105,7 @@ impl<T: Config> Migrate for Migration<T> { determinism: Determinism::Enforced, }; CodeStorage::<T>::insert(key, module); - self.last_key = Some(iter.last_raw_key().to_vec().try_into().unwrap()); + self.last_code_hash = Some(key); (IsFinished::No, T::WeightInfo::v9_migration_step(len)) } else { log::debug!(target: LOG_TARGET, "No more contracts code to migrate"); @@ -126,8 +123,8 @@ impl<T: Config> Migrate for Migration<T> { #[cfg(feature = "try-runtime")] fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> { - let sample = - <Vec<(CodeHash<T>, old::PrefabWasmModule)> as Decode>::decode(&mut &state[..]).unwrap(); + let sample = <Vec<(CodeHash<T>, old::PrefabWasmModule)> as Decode>::decode(&mut &state[..]) + .expect("pre_upgrade_step provides a valid state; qed"); log::debug!(target: LOG_TARGET, "Validating sample of {} contract codes", sample.len()); for (code_hash, old) in sample { @@ -140,8 +137,6 @@ impl<T: Config> Migrate for Migration<T> { ensure!(module.initial == old.initial, "invalid initial"); ensure!(module.maximum == old.maximum, "invalid maximum"); ensure!(module.code == old.code, "invalid code"); - ensure!(module.maximum == old.maximum, "invalid maximum"); - ensure!(module.code == old.code, "invalid code"); } Ok(()) diff --git a/substrate/frame/contracts/src/storage/meter.rs b/substrate/frame/contracts/src/storage/meter.rs index 506f4f0d86649379e2d1032102e0c7f8d051c996..e5caa68a8848202f29b466b41b7d9aa2332decd1 100644 --- a/substrate/frame/contracts/src/storage/meter.rs +++ b/substrate/frame/contracts/src/storage/meter.rs @@ -90,7 +90,7 @@ pub trait Ext<T: Config> { /// This [`Ext`] is used for actual on-chain execution when balance needs to be charged. /// -/// It uses [`ReservableCurrency`] in order to do accomplish the reserves. +/// It uses [`frame_support::traits::ReservableCurrency`] in order to do accomplish the reserves. pub enum ReservingExt {} /// Used to implement a type state pattern for the meter. diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index 9425f25adf4b89c22548f5dd81272b8b4bb08c11..48b56128d5298a2c40f7bed1c672937ae4495584 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -559,6 +559,24 @@ fn calling_plain_account_fails() { }); } +#[test] +fn migration_on_idle_hooks_works() { + // Defines expectations of how many migration steps can be done given the weight limit. + let tests = [ + (Weight::zero(), 0), + (<Test as Config>::WeightInfo::migrate() + 1.into(), 1), + (Weight::MAX, 2), + ]; + + for (weight, expected_version) in tests { + ExtBuilder::default().set_storage_version(0).build().execute_with(|| { + MigrationInProgress::<Test>::set(Some(Default::default())); + Contracts::on_idle(System::block_number(), weight); + assert_eq!(StorageVersion::get::<Pallet<Test>>(), expected_version); + }); + } +} + #[test] fn migration_in_progress_works() { let (wasm, code_hash) = compile_module::<Test>("dummy").unwrap();