// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! # Migrations for Society Pezpallet use super::*; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use pezframe_support::traits::{Defensive, DefensiveOption, Instance, UncheckedOnRuntimeUpgrade}; #[cfg(feature = "try-runtime")] use pezsp_runtime::TryRuntimeError; /// The log target. const TARGET: &'static str = "runtime::society::migration"; /// This migration moves all the state to v2 of Society. pub struct VersionUncheckedMigrateToV2, I: 'static, PastPayouts>( core::marker::PhantomData<(T, I, PastPayouts)>, ); impl< T: Config, I: Instance + 'static, PastPayouts: Get::AccountId, BalanceOf)>>, > UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV2 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { let in_code = Pezpallet::::in_code_storage_version(); let on_chain = Pezpallet::::on_chain_storage_version(); ensure!(on_chain == 0 && in_code == 2, "pezpallet_society: invalid version"); Ok((v0::Candidates::::get(), v0::Members::::get()).encode()) } fn on_runtime_upgrade() -> Weight { let onchain = Pezpallet::::on_chain_storage_version(); if onchain < 2 { log::info!( target: TARGET, "Running migration against onchain version {:?}", onchain ); from_original::(&mut PastPayouts::get()).defensive_unwrap_or(Weight::MAX) } else { log::warn!("Unexpected onchain version: {:?} (expected 0)", onchain); T::DbWeight::get().reads(1) } } #[cfg(feature = "try-runtime")] fn post_upgrade(data: Vec) -> Result<(), TryRuntimeError> { let old: ( Vec::AccountId, BalanceOf>>, Vec<::AccountId>, ) = Decode::decode(&mut &data[..]).expect("Bad data"); let mut old_candidates = old.0.into_iter().map(|x| (x.who, x.kind, x.value)).collect::>(); let mut old_members = old.1; let mut candidates = Candidates::::iter().map(|(k, v)| (k, v.kind, v.bid)).collect::>(); let mut members = Members::::iter_keys().collect::>(); old_candidates.sort_by_key(|x| x.0.clone()); candidates.sort_by_key(|x| x.0.clone()); assert_eq!(candidates, old_candidates); members.sort(); old_members.sort(); assert_eq!(members, old_members); ensure!( Pezpallet::::on_chain_storage_version() == 2, "The onchain version must be updated after the migration." ); assert_internal_consistency::(); Ok(()) } } /// [`VersionUncheckedMigrateToV2`] wrapped in a /// [`pezframe_support::migrations::VersionedMigration`], ensuring the migration is only performed /// when on-chain version is 0. pub type MigrateToV2 = pezframe_support::migrations::VersionedMigration< 0, 2, VersionUncheckedMigrateToV2, crate::pezpallet::Pezpallet, ::DbWeight, >; pub(crate) mod v0 { use super::*; use pezframe_support::storage_alias; /// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum Vote { /// The member has been chosen to be skeptic and has not yet taken any action. Skeptic, /// The member has rejected the candidate's application. Reject, /// The member approves of the candidate's application. Approve, } #[storage_alias] pub type Bids, I: 'static> = StorageValue< Pezpallet, Vec::AccountId, BalanceOf>>, ValueQuery, >; #[storage_alias] pub type Candidates, I: 'static> = StorageValue< Pezpallet, Vec::AccountId, BalanceOf>>, ValueQuery, >; #[storage_alias] pub type Votes, I: 'static> = StorageDoubleMap< Pezpallet, Twox64Concat, ::AccountId, Twox64Concat, ::AccountId, Vote, >; #[storage_alias] pub type SuspendedCandidates, I: 'static> = StorageMap< Pezpallet, Twox64Concat, ::AccountId, (BalanceOf, BidKind<::AccountId, BalanceOf>), >; #[storage_alias] pub type Members, I: 'static> = StorageValue, Vec<::AccountId>, ValueQuery>; #[storage_alias] pub type Vouching, I: 'static> = StorageMap< Pezpallet, Twox64Concat, ::AccountId, VouchingStatus, >; #[storage_alias] pub type Strikes, I: 'static> = StorageMap< Pezpallet, Twox64Concat, ::AccountId, StrikeCount, ValueQuery, >; #[storage_alias] pub type Payouts, I: 'static> = StorageMap< Pezpallet, Twox64Concat, ::AccountId, Vec<(BlockNumberFor, BalanceOf)>, ValueQuery, >; #[storage_alias] pub type SuspendedMembers, I: 'static> = StorageMap< Pezpallet, Twox64Concat, ::AccountId, bool, ValueQuery, >; #[storage_alias] pub type Defender, I: 'static> = StorageValue, ::AccountId>; #[storage_alias] pub type DefenderVotes, I: 'static> = StorageMap, Twox64Concat, ::AccountId, Vote>; } /// Will panic if there are any inconsistencies in the pezpallet's state or old keys remaining. pub fn assert_internal_consistency, I: Instance + 'static>() { // Check all members are valid data. let mut members = vec![]; for m in Members::::iter_keys() { let r = Members::::get(&m).expect("Member data must be valid"); members.push((m, r)); } assert_eq!(MemberCount::::get(), members.len() as u32); for (who, record) in members.iter() { assert_eq!(MemberByIndex::::get(record.index).as_ref(), Some(who)); } if let Some(founder) = Founder::::get() { assert_eq!(Members::::get(founder).expect("founder is member").index, 0); } if let Some(head) = Head::::get() { assert!(Members::::contains_key(head)); } // Check all votes are valid data. for (k1, k2) in Votes::::iter_keys() { assert!(Votes::::get(k1, k2).is_some()); } // Check all defender votes are valid data. for (k1, k2) in DefenderVotes::::iter_keys() { assert!(DefenderVotes::::get(k1, k2).is_some()); } // Check all candidates are valid data. for k in Candidates::::iter_keys() { assert!(Candidates::::get(k).is_some()); } // Check all suspended members are valid data. for m in SuspendedMembers::::iter_keys() { assert!(SuspendedMembers::::get(m).is_some()); } // Check all payouts are valid data. for p in Payouts::::iter_keys() { let k = Payouts::::hashed_key_for(&p); let v = pezframe_support::storage::unhashed::get_raw(&k[..]).expect("value is in map"); assert!(PayoutRecordFor::::decode(&mut &v[..]).is_ok()); } // We don't use these - make sure they don't exist. assert_eq!(v0::SuspendedCandidates::::iter().count(), 0); assert_eq!(v0::Strikes::::iter().count(), 0); assert_eq!(v0::Vouching::::iter().count(), 0); assert!(!v0::Defender::::exists()); assert!(!v0::Members::::exists()); } pub fn from_original, I: Instance + 'static>( past_payouts: &mut [(::AccountId, BalanceOf)], ) -> Result { // Migrate Bids from old::Bids (just a truncation). Bids::::put(BoundedVec::<_, T::MaxBids>::truncate_from(v0::Bids::::take())); // Initialise round counter. RoundCount::::put(0); // Migrate Candidates from old::Candidates for Bid { who: candidate, kind, value } in v0::Candidates::::take().into_iter() { let mut tally = Tally::default(); // Migrate Votes from old::Votes // No need to drain, since we're overwriting values. for (voter, vote) in v0::Votes::::iter_prefix(&candidate) { Votes::::insert( &candidate, &voter, Vote { approve: vote == v0::Vote::Approve, weight: 1 }, ); match vote { v0::Vote::Approve => tally.approvals.saturating_inc(), v0::Vote::Reject => tally.rejections.saturating_inc(), v0::Vote::Skeptic => Skeptic::::put(&voter), } } Candidates::::insert( &candidate, Candidacy { round: 0, kind, tally, skeptic_struck: false, bid: value }, ); } // Migrate Members from old::Members old::Strikes old::Vouching let mut member_count = 0; for member in v0::Members::::take() { let strikes = v0::Strikes::::take(&member); let vouching = v0::Vouching::::take(&member); let record = MemberRecord { index: member_count, rank: 0, strikes, vouching }; Members::::insert(&member, record); MemberByIndex::::insert(member_count, &member); // The founder must be the first member in Society V2. If we find the founder not in index // zero, we swap it with the first member. if member == Founder::::get().defensive_ok_or("founder must always be set")? && member_count > 0 { let member_to_swap = MemberByIndex::::get(0) .defensive_ok_or("member_count > 0, we must have at least 1 member")?; // Swap the founder with the first member in MemberByIndex. MemberByIndex::::swap(0, member_count); // Update the indices of the swapped member MemberRecords. Members::::mutate(&member, |m| { if let Some(member) = m { member.index = 0; } else { pezframe_support::defensive!( "Member somehow disappeared from storage after it was inserted" ); } }); Members::::mutate(&member_to_swap, |m| { if let Some(member) = m { member.index = member_count; } else { pezframe_support::defensive!( "Member somehow disappeared from storage after it was queried" ); } }); } member_count.saturating_inc(); } MemberCount::::put(member_count); // Migrate Payouts from: old::Payouts and raw info (needed since we can't query old chain // state). past_payouts.sort(); for (who, mut payouts) in v0::Payouts::::iter() { payouts.truncate(T::MaxPayouts::get() as usize); // ^^ Safe since we already truncated. let paid = past_payouts .binary_search_by_key(&&who, |x| &x.0) .ok() .map(|p| past_payouts[p].1) .unwrap_or(Zero::zero()); match BoundedVec::try_from(payouts) { Ok(payouts) => Payouts::::insert(who, PayoutRecord { paid, payouts }), Err(_) => debug_assert!(false, "Truncation of Payouts ineffective??"), } } // Migrate SuspendedMembers from old::SuspendedMembers old::Strikes old::Vouching. for who in v0::SuspendedMembers::::iter_keys() { let strikes = v0::Strikes::::take(&who); let vouching = v0::Vouching::::take(&who); let record = MemberRecord { index: 0, rank: 0, strikes, vouching }; SuspendedMembers::::insert(&who, record); } // Any suspended candidates remaining are rejected. let _ = v0::SuspendedCandidates::::clear(u32::MAX, None); // We give the current defender the benefit of the doubt. v0::Defender::::kill(); let _ = v0::DefenderVotes::::clear(u32::MAX, None); Ok(T::BlockWeights::get().max_block) } pub fn from_raw_past_payouts, I: Instance + 'static>( past_payouts_raw: impl Iterator, ) -> Vec<(::AccountId, BalanceOf)> { past_payouts_raw .filter_map(|(x, y)| Some((Decode::decode(&mut &x[..]).ok()?, y.try_into().ok()?))) .collect() }