diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 658fcc8fc..8512b50e2 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -20,6 +20,8 @@ pub use pallet::*; +pub mod migrations; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(any(test, feature = "runtime-benchmarks"))] diff --git a/pallets/pallet-bonded-coins/src/migrations/mod.rs b/pallets/pallet-bonded-coins/src/migrations/mod.rs new file mode 100644 index 000000000..a3a6d96c3 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/pallets/pallet-bonded-coins/src/migrations/v1.rs b/pallets/pallet-bonded-coins/src/migrations/v1.rs new file mode 100644 index 000000000..4f56bf754 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/migrations/v1.rs @@ -0,0 +1,177 @@ +use frame_support::{ + pallet_prelude::*, + traits::{Get, OnRuntimeUpgrade}, +}; +use sp_runtime::traits::Saturating; + +use crate::{ + curves::Curve, + types::{Locks, PoolStatus}, + BondedCurrenciesSettingsOf, BoundedCurrencyVec, CollateralAssetIdOf, Config, CurveParameterTypeOf, + DepositBalanceOf, FungiblesBalanceOf, +}; + +#[cfg(feature = "try-runtime")] +const LOG_TARGET: &str = "migration::pallet-bonded-coins"; + +/// Collection of storage item formats from the previous storage version. +/// +/// Required so we can read values in the v0 storage format during the +/// migration. +mod v0 { + use super::*; + use frame_support::{storage_alias, Twox64Concat}; + use parity_scale_codec::{Decode, Encode}; + use scale_info::TypeInfo; + + // V0 pool details + #[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen, Debug)] + pub struct PoolDetails { + /// The owner of the pool. + pub owner: AccountId, + /// The manager of the pool. If a manager is set, the pool is + /// permissioned. + pub manager: Option, + /// The curve of the pool. + pub curve: ParametrizedCurve, + /// The collateral currency of the pool. + pub collateral: BaseCurrencyId, + /// The bonded currencies of the pool. + pub bonded_currencies: Currencies, + /// The status of the pool. + pub state: PoolStatus, + /// Whether the pool is transferable or not. + pub transferable: bool, + /// The denomination of the pool. + pub denomination: u8, + /// The minimum amount that can be minted/burnt. + pub min_operation_balance: FungiblesBalance, + /// The deposit to be returned upon destruction of this pool. + pub deposit: DepositBalance, + } + + pub type PoolDetailsOf = PoolDetails< + ::AccountId, + Curve>, + BoundedCurrencyVec, + CollateralAssetIdOf, + DepositBalanceOf, + FungiblesBalanceOf, + >; + + /// V0 type for [`crate::Pools`]. + #[storage_alias] + pub type Pools = + StorageMap, Twox64Concat, ::PoolId, PoolDetailsOf, OptionQuery>; +} + +fn v0_to_v1(old_value: v0::PoolDetailsOf) -> crate::PoolDetailsOf { + let v0::PoolDetailsOf:: { + owner, + curve, + manager, + collateral, + bonded_currencies, + state, + transferable, + denomination, + min_operation_balance, + deposit, + } = old_value; + + crate::PoolDetailsOf:: { + owner, + curve, + manager, + collateral, + bonded_currencies, + state, + deposit, + currencies_settings: BondedCurrenciesSettingsOf:: { + denomination, + min_operation_balance, + transferable, + allow_reset_team: true, + }, + } +} + +pub struct InnerMigrateV0ToV1(core::marker::PhantomData); + +impl OnRuntimeUpgrade for InnerMigrateV0ToV1 +where + T::PoolId: sp_std::fmt::Debug, +{ + /// Return a vector of existing [`crate::Pools`] values so we can check that + /// they were correctly set in `InnerMigrateV0ToV1::post_upgrade`. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + // Access the old value using the `storage_alias` type + let old_value: sp_std::vec::Vec<(T::PoolId, v0::PoolDetailsOf)> = v0::Pools::::iter().collect(); + // Return it as an encoded `Vec` + Ok(old_value.encode()) + } + + /// Migrate the storage from V0 to V1. + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let mut translated = 0u64; + // Read values in-place + crate::Pools::::translate_values::, _>(|old_value| { + translated.saturating_inc(); + Some(v0_to_v1::(old_value)) + }); + + // One read for taking the old value, and one write for setting the new value + T::DbWeight::get().reads_writes(translated, translated) + } + + /// Verifies the storage was migrated correctly. + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: sp_std::vec::Vec) -> Result<(), sp_runtime::TryRuntimeError> { + use sp_runtime::traits::SaturatedConversion; + + let old_values = sp_std::vec::Vec::<(T::PoolId, v0::PoolDetailsOf)>::decode(&mut &state[..]) + .map_err(|_| sp_runtime::TryRuntimeError::Other("Failed to decode old value from storage"))?; + + let prev_count: u32 = old_values.len().saturated_into(); + let post_count: u32 = crate::Pools::::iter().count().saturated_into(); + + ensure!( + prev_count == post_count, + "the pool count before and after the migration should be the same" + ); + + log::info!(target: LOG_TARGET, "Migrated {} pool entries", post_count); + + old_values.into_iter().try_for_each(|(pool_id, old_value)| { + let expected_new_value = v0_to_v1::(old_value); + let actual_new_value = crate::Pools::::get(&pool_id); + + ensure!(actual_new_value.is_some(), { + log::error!(target: LOG_TARGET, "Expected pool with id {:?} but found none", &pool_id); + sp_runtime::TryRuntimeError::Other("Pool not migrated") + }); + ensure!(actual_new_value == Some(expected_new_value), { + log::error!(target: LOG_TARGET, "Pool with id {:?} contains unexpected data", &pool_id); + sp_runtime::TryRuntimeError::Other("Incorrect Pool Data") + }); + + ensure!(actual_new_value.unwrap().currencies_settings.allow_reset_team, { + log::error!(target: LOG_TARGET, "Pool with id {:?} has allow_reset_team = false", &pool_id); + sp_runtime::TryRuntimeError::Other( + "all migrated pools should have the allow_reset_team flag set to true", + ) + }); + + Ok(()) + }) + } +} + +pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< + 0, // The migration will only execute when the on-chain storage version is 0 + 1, // The on-chain storage version will be set to 1 after the migration is complete + InnerMigrateV0ToV1, + crate::pallet::Pallet, + ::DbWeight, +>; diff --git a/runtimes/peregrine/src/migrations/mod.rs b/runtimes/peregrine/src/migrations/mod.rs index f6ca110fa..76762e1c0 100644 --- a/runtimes/peregrine/src/migrations/mod.rs +++ b/runtimes/peregrine/src/migrations/mod.rs @@ -32,6 +32,7 @@ pub type RuntimeMigrations = ( frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, + pallet_bonded_coins::migrations::v1::MigrateV0ToV1, ); impl pallet_migration::Config for Runtime {