referrerpolicy=no-referrer-when-downgrade

pallet_society/
migrations.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Migrations for Society Pallet
19
20use super::*;
21use alloc::{vec, vec::Vec};
22use codec::{Decode, Encode};
23use frame_support::traits::{Defensive, DefensiveOption, Instance, UncheckedOnRuntimeUpgrade};
24
25#[cfg(feature = "try-runtime")]
26use sp_runtime::TryRuntimeError;
27
28/// The log target.
29const TARGET: &'static str = "runtime::society::migration";
30
31/// This migration moves all the state to v2 of Society.
32pub struct VersionUncheckedMigrateToV2<T: Config<I>, I: 'static, PastPayouts>(
33	core::marker::PhantomData<(T, I, PastPayouts)>,
34);
35
36impl<
37		T: Config<I>,
38		I: Instance + 'static,
39		PastPayouts: Get<Vec<(<T as frame_system::Config>::AccountId, BalanceOf<T, I>)>>,
40	> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV2<T, I, PastPayouts>
41{
42	#[cfg(feature = "try-runtime")]
43	fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
44		let in_code = Pallet::<T, I>::in_code_storage_version();
45		let on_chain = Pallet::<T, I>::on_chain_storage_version();
46		ensure!(on_chain == 0 && in_code == 2, "pallet_society: invalid version");
47
48		Ok((v0::Candidates::<T, I>::get(), v0::Members::<T, I>::get()).encode())
49	}
50
51	fn on_runtime_upgrade() -> Weight {
52		let onchain = Pallet::<T, I>::on_chain_storage_version();
53		if onchain < 2 {
54			log::info!(
55				target: TARGET,
56				"Running migration against onchain version {:?}",
57				onchain
58			);
59			from_original::<T, I>(&mut PastPayouts::get()).defensive_unwrap_or(Weight::MAX)
60		} else {
61			log::warn!("Unexpected onchain version: {:?} (expected 0)", onchain);
62			T::DbWeight::get().reads(1)
63		}
64	}
65
66	#[cfg(feature = "try-runtime")]
67	fn post_upgrade(data: Vec<u8>) -> Result<(), TryRuntimeError> {
68		let old: (
69			Vec<Bid<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>>,
70			Vec<<T as frame_system::Config>::AccountId>,
71		) = Decode::decode(&mut &data[..]).expect("Bad data");
72		let mut old_candidates =
73			old.0.into_iter().map(|x| (x.who, x.kind, x.value)).collect::<Vec<_>>();
74		let mut old_members = old.1;
75		let mut candidates =
76			Candidates::<T, I>::iter().map(|(k, v)| (k, v.kind, v.bid)).collect::<Vec<_>>();
77		let mut members = Members::<T, I>::iter_keys().collect::<Vec<_>>();
78
79		old_candidates.sort_by_key(|x| x.0.clone());
80		candidates.sort_by_key(|x| x.0.clone());
81		assert_eq!(candidates, old_candidates);
82
83		members.sort();
84		old_members.sort();
85		assert_eq!(members, old_members);
86
87		ensure!(
88			Pallet::<T, I>::on_chain_storage_version() == 2,
89			"The onchain version must be updated after the migration."
90		);
91
92		assert_internal_consistency::<T, I>();
93		Ok(())
94	}
95}
96
97/// [`VersionUncheckedMigrateToV2`] wrapped in a [`frame_support::migrations::VersionedMigration`],
98/// ensuring the migration is only performed when on-chain version is 0.
99pub type MigrateToV2<T, I, PastPayouts> = frame_support::migrations::VersionedMigration<
100	0,
101	2,
102	VersionUncheckedMigrateToV2<T, I, PastPayouts>,
103	crate::pallet::Pallet<T, I>,
104	<T as frame_system::Config>::DbWeight,
105>;
106
107pub(crate) mod v0 {
108	use super::*;
109	use frame_support::storage_alias;
110
111	/// A vote by a member on a candidate application.
112	#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
113	pub enum Vote {
114		/// The member has been chosen to be skeptic and has not yet taken any action.
115		Skeptic,
116		/// The member has rejected the candidate's application.
117		Reject,
118		/// The member approves of the candidate's application.
119		Approve,
120	}
121
122	#[storage_alias]
123	pub type Bids<T: Config<I>, I: 'static> = StorageValue<
124		Pallet<T, I>,
125		Vec<Bid<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>>,
126		ValueQuery,
127	>;
128	#[storage_alias]
129	pub type Candidates<T: Config<I>, I: 'static> = StorageValue<
130		Pallet<T, I>,
131		Vec<Bid<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>>,
132		ValueQuery,
133	>;
134	#[storage_alias]
135	pub type Votes<T: Config<I>, I: 'static> = StorageDoubleMap<
136		Pallet<T, I>,
137		Twox64Concat,
138		<T as frame_system::Config>::AccountId,
139		Twox64Concat,
140		<T as frame_system::Config>::AccountId,
141		Vote,
142	>;
143	#[storage_alias]
144	pub type SuspendedCandidates<T: Config<I>, I: 'static> = StorageMap<
145		Pallet<T, I>,
146		Twox64Concat,
147		<T as frame_system::Config>::AccountId,
148		(BalanceOf<T, I>, BidKind<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>),
149	>;
150	#[storage_alias]
151	pub type Members<T: Config<I>, I: 'static> =
152		StorageValue<Pallet<T, I>, Vec<<T as frame_system::Config>::AccountId>, ValueQuery>;
153	#[storage_alias]
154	pub type Vouching<T: Config<I>, I: 'static> = StorageMap<
155		Pallet<T, I>,
156		Twox64Concat,
157		<T as frame_system::Config>::AccountId,
158		VouchingStatus,
159	>;
160	#[storage_alias]
161	pub type Strikes<T: Config<I>, I: 'static> = StorageMap<
162		Pallet<T, I>,
163		Twox64Concat,
164		<T as frame_system::Config>::AccountId,
165		StrikeCount,
166		ValueQuery,
167	>;
168	#[storage_alias]
169	pub type Payouts<T: Config<I>, I: 'static> = StorageMap<
170		Pallet<T, I>,
171		Twox64Concat,
172		<T as frame_system::Config>::AccountId,
173		Vec<(BlockNumberFor<T, I>, BalanceOf<T, I>)>,
174		ValueQuery,
175	>;
176	#[storage_alias]
177	pub type SuspendedMembers<T: Config<I>, I: 'static> = StorageMap<
178		Pallet<T, I>,
179		Twox64Concat,
180		<T as frame_system::Config>::AccountId,
181		bool,
182		ValueQuery,
183	>;
184	#[storage_alias]
185	pub type Defender<T: Config<I>, I: 'static> =
186		StorageValue<Pallet<T, I>, <T as frame_system::Config>::AccountId>;
187	#[storage_alias]
188	pub type DefenderVotes<T: Config<I>, I: 'static> =
189		StorageMap<Pallet<T, I>, Twox64Concat, <T as frame_system::Config>::AccountId, Vote>;
190}
191
192/// Will panic if there are any inconsistencies in the pallet's state or old keys remaining.
193pub fn assert_internal_consistency<T: Config<I>, I: Instance + 'static>() {
194	// Check all members are valid data.
195	let mut members = vec![];
196	for m in Members::<T, I>::iter_keys() {
197		let r = Members::<T, I>::get(&m).expect("Member data must be valid");
198		members.push((m, r));
199	}
200	assert_eq!(MemberCount::<T, I>::get(), members.len() as u32);
201	for (who, record) in members.iter() {
202		assert_eq!(MemberByIndex::<T, I>::get(record.index).as_ref(), Some(who));
203	}
204	if let Some(founder) = Founder::<T, I>::get() {
205		assert_eq!(Members::<T, I>::get(founder).expect("founder is member").index, 0);
206	}
207	if let Some(head) = Head::<T, I>::get() {
208		assert!(Members::<T, I>::contains_key(head));
209	}
210	// Check all votes are valid data.
211	for (k1, k2) in Votes::<T, I>::iter_keys() {
212		assert!(Votes::<T, I>::get(k1, k2).is_some());
213	}
214	// Check all defender votes are valid data.
215	for (k1, k2) in DefenderVotes::<T, I>::iter_keys() {
216		assert!(DefenderVotes::<T, I>::get(k1, k2).is_some());
217	}
218	// Check all candidates are valid data.
219	for k in Candidates::<T, I>::iter_keys() {
220		assert!(Candidates::<T, I>::get(k).is_some());
221	}
222	// Check all suspended members are valid data.
223	for m in SuspendedMembers::<T, I>::iter_keys() {
224		assert!(SuspendedMembers::<T, I>::get(m).is_some());
225	}
226	// Check all payouts are valid data.
227	for p in Payouts::<T, I>::iter_keys() {
228		let k = Payouts::<T, I>::hashed_key_for(&p);
229		let v = frame_support::storage::unhashed::get_raw(&k[..]).expect("value is in map");
230		assert!(PayoutRecordFor::<T, I>::decode(&mut &v[..]).is_ok());
231	}
232
233	// We don't use these - make sure they don't exist.
234	assert_eq!(v0::SuspendedCandidates::<T, I>::iter().count(), 0);
235	assert_eq!(v0::Strikes::<T, I>::iter().count(), 0);
236	assert_eq!(v0::Vouching::<T, I>::iter().count(), 0);
237	assert!(!v0::Defender::<T, I>::exists());
238	assert!(!v0::Members::<T, I>::exists());
239}
240
241pub fn from_original<T: Config<I>, I: Instance + 'static>(
242	past_payouts: &mut [(<T as frame_system::Config>::AccountId, BalanceOf<T, I>)],
243) -> Result<Weight, &'static str> {
244	// Migrate Bids from old::Bids (just a truncation).
245	Bids::<T, I>::put(BoundedVec::<_, T::MaxBids>::truncate_from(v0::Bids::<T, I>::take()));
246
247	// Initialise round counter.
248	RoundCount::<T, I>::put(0);
249
250	// Migrate Candidates from old::Candidates
251	for Bid { who: candidate, kind, value } in v0::Candidates::<T, I>::take().into_iter() {
252		let mut tally = Tally::default();
253		// Migrate Votes from old::Votes
254		// No need to drain, since we're overwriting values.
255		for (voter, vote) in v0::Votes::<T, I>::iter_prefix(&candidate) {
256			Votes::<T, I>::insert(
257				&candidate,
258				&voter,
259				Vote { approve: vote == v0::Vote::Approve, weight: 1 },
260			);
261			match vote {
262				v0::Vote::Approve => tally.approvals.saturating_inc(),
263				v0::Vote::Reject => tally.rejections.saturating_inc(),
264				v0::Vote::Skeptic => Skeptic::<T, I>::put(&voter),
265			}
266		}
267		Candidates::<T, I>::insert(
268			&candidate,
269			Candidacy { round: 0, kind, tally, skeptic_struck: false, bid: value },
270		);
271	}
272
273	// Migrate Members from old::Members old::Strikes old::Vouching
274	let mut member_count = 0;
275	for member in v0::Members::<T, I>::take() {
276		let strikes = v0::Strikes::<T, I>::take(&member);
277		let vouching = v0::Vouching::<T, I>::take(&member);
278		let record = MemberRecord { index: member_count, rank: 0, strikes, vouching };
279		Members::<T, I>::insert(&member, record);
280		MemberByIndex::<T, I>::insert(member_count, &member);
281
282		// The founder must be the first member in Society V2. If we find the founder not in index
283		// zero, we swap it with the first member.
284		if member == Founder::<T, I>::get().defensive_ok_or("founder must always be set")? &&
285			member_count > 0
286		{
287			let member_to_swap = MemberByIndex::<T, I>::get(0)
288				.defensive_ok_or("member_count > 0, we must have at least 1 member")?;
289			// Swap the founder with the first member in MemberByIndex.
290			MemberByIndex::<T, I>::swap(0, member_count);
291			// Update the indices of the swapped member MemberRecords.
292			Members::<T, I>::mutate(&member, |m| {
293				if let Some(member) = m {
294					member.index = 0;
295				} else {
296					frame_support::defensive!(
297						"Member somehow disappeared from storage after it was inserted"
298					);
299				}
300			});
301			Members::<T, I>::mutate(&member_to_swap, |m| {
302				if let Some(member) = m {
303					member.index = member_count;
304				} else {
305					frame_support::defensive!(
306						"Member somehow disappeared from storage after it was queried"
307					);
308				}
309			});
310		}
311		member_count.saturating_inc();
312	}
313	MemberCount::<T, I>::put(member_count);
314
315	// Migrate Payouts from: old::Payouts and raw info (needed since we can't query old chain
316	// state).
317	past_payouts.sort();
318	for (who, mut payouts) in v0::Payouts::<T, I>::iter() {
319		payouts.truncate(T::MaxPayouts::get() as usize);
320		// ^^ Safe since we already truncated.
321		let paid = past_payouts
322			.binary_search_by_key(&&who, |x| &x.0)
323			.ok()
324			.map(|p| past_payouts[p].1)
325			.unwrap_or(Zero::zero());
326		match BoundedVec::try_from(payouts) {
327			Ok(payouts) => Payouts::<T, I>::insert(who, PayoutRecord { paid, payouts }),
328			Err(_) => debug_assert!(false, "Truncation of Payouts ineffective??"),
329		}
330	}
331
332	// Migrate SuspendedMembers from old::SuspendedMembers old::Strikes old::Vouching.
333	for who in v0::SuspendedMembers::<T, I>::iter_keys() {
334		let strikes = v0::Strikes::<T, I>::take(&who);
335		let vouching = v0::Vouching::<T, I>::take(&who);
336		let record = MemberRecord { index: 0, rank: 0, strikes, vouching };
337		SuspendedMembers::<T, I>::insert(&who, record);
338	}
339
340	// Any suspended candidates remaining are rejected.
341	let _ = v0::SuspendedCandidates::<T, I>::clear(u32::MAX, None);
342
343	// We give the current defender the benefit of the doubt.
344	v0::Defender::<T, I>::kill();
345	let _ = v0::DefenderVotes::<T, I>::clear(u32::MAX, None);
346
347	Ok(T::BlockWeights::get().max_block)
348}
349
350pub fn from_raw_past_payouts<T: Config<I>, I: Instance + 'static>(
351	past_payouts_raw: impl Iterator<Item = ([u8; 32], u128)>,
352) -> Vec<(<T as frame_system::Config>::AccountId, BalanceOf<T, I>)> {
353	past_payouts_raw
354		.filter_map(|(x, y)| Some((Decode::decode(&mut &x[..]).ok()?, y.try_into().ok()?)))
355		.collect()
356}