1use 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
28const TARGET: &'static str = "runtime::society::migration";
30
31pub 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
97pub 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 #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
113 pub enum Vote {
114 Skeptic,
116 Reject,
118 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
192pub fn assert_internal_consistency<T: Config<I>, I: Instance + 'static>() {
194 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 for (k1, k2) in Votes::<T, I>::iter_keys() {
212 assert!(Votes::<T, I>::get(k1, k2).is_some());
213 }
214 for (k1, k2) in DefenderVotes::<T, I>::iter_keys() {
216 assert!(DefenderVotes::<T, I>::get(k1, k2).is_some());
217 }
218 for k in Candidates::<T, I>::iter_keys() {
220 assert!(Candidates::<T, I>::get(k).is_some());
221 }
222 for m in SuspendedMembers::<T, I>::iter_keys() {
224 assert!(SuspendedMembers::<T, I>::get(m).is_some());
225 }
226 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 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 Bids::<T, I>::put(BoundedVec::<_, T::MaxBids>::truncate_from(v0::Bids::<T, I>::take()));
246
247 RoundCount::<T, I>::put(0);
249
250 for Bid { who: candidate, kind, value } in v0::Candidates::<T, I>::take().into_iter() {
252 let mut tally = Tally::default();
253 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 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 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 MemberByIndex::<T, I>::swap(0, member_count);
291 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 past_payouts.sort();
318 for (who, mut payouts) in v0::Payouts::<T, I>::iter() {
319 payouts.truncate(T::MaxPayouts::get() as usize);
320 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 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 let _ = v0::SuspendedCandidates::<T, I>::clear(u32::MAX, None);
342
343 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}