1use super::*;
21use alloc::{vec, vec::Vec};
22use codec::{Decode, Encode};
23use core::cmp::Ordering;
24use frame_support::traits::{
25 Defensive, DefensiveOption, ExistenceRequirement::KeepAlive, Instance,
26 UncheckedOnRuntimeUpgrade,
27};
28
29#[cfg(feature = "try-runtime")]
30use sp_runtime::TryRuntimeError;
31
32const TARGET: &'static str = "runtime::society::migration";
34
35pub struct VersionUncheckedMigrateToV2<T: Config<I>, I: 'static, PastPayouts>(
37 core::marker::PhantomData<(T, I, PastPayouts)>,
38);
39
40impl<
41 T: Config<I>,
42 I: Instance + 'static,
43 PastPayouts: Get<Vec<(<T as frame_system::Config>::AccountId, BalanceOf<T, I>)>>,
44 > UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV2<T, I, PastPayouts>
45{
46 #[cfg(feature = "try-runtime")]
47 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
48 let in_code = Pallet::<T, I>::in_code_storage_version();
49 let on_chain = Pallet::<T, I>::on_chain_storage_version();
50 ensure!(on_chain == 0 && in_code == 2, "pallet_society: invalid version");
51
52 Ok((v0::Candidates::<T, I>::get(), v0::Members::<T, I>::get()).encode())
53 }
54
55 fn on_runtime_upgrade() -> Weight {
56 let onchain = Pallet::<T, I>::on_chain_storage_version();
57 if onchain < 2 {
58 log::info!(
59 target: TARGET,
60 "Running migration against onchain version {:?}",
61 onchain
62 );
63 from_original::<T, I>(&mut PastPayouts::get()).defensive_unwrap_or(Weight::MAX)
64 } else {
65 log::warn!("Unexpected onchain version: {:?} (expected 0)", onchain);
66 T::DbWeight::get().reads(1)
67 }
68 }
69
70 #[cfg(feature = "try-runtime")]
71 fn post_upgrade(data: Vec<u8>) -> Result<(), TryRuntimeError> {
72 let old: (
73 Vec<Bid<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>>,
74 Vec<<T as frame_system::Config>::AccountId>,
75 ) = Decode::decode(&mut &data[..]).expect("Bad data");
76 let mut old_candidates =
77 old.0.into_iter().map(|x| (x.who, x.kind, x.value)).collect::<Vec<_>>();
78 let mut old_members = old.1;
79 let mut candidates =
80 Candidates::<T, I>::iter().map(|(k, v)| (k, v.kind, v.bid)).collect::<Vec<_>>();
81 let mut members = Members::<T, I>::iter_keys().collect::<Vec<_>>();
82
83 old_candidates.sort_by_key(|x| x.0.clone());
84 candidates.sort_by_key(|x| x.0.clone());
85 assert_eq!(candidates, old_candidates);
86
87 members.sort();
88 old_members.sort();
89 assert_eq!(members, old_members);
90
91 ensure!(
92 Pallet::<T, I>::on_chain_storage_version() == 2,
93 "The onchain version must be updated after the migration."
94 );
95
96 assert_internal_consistency::<T, I>();
97 Ok(())
98 }
99}
100
101pub type MigrateToV2<T, I, PastPayouts> = frame_support::migrations::VersionedMigration<
104 0,
105 2,
106 VersionUncheckedMigrateToV2<T, I, PastPayouts>,
107 crate::pallet::Pallet<T, I>,
108 <T as frame_system::Config>::DbWeight,
109>;
110
111pub struct ReconcilePayoutsAccount<T, I = ()>(core::marker::PhantomData<(T, I)>);
121
122impl<T: Config<I>, I: Instance + 'static> frame_support::traits::OnRuntimeUpgrade
123 for ReconcilePayoutsAccount<T, I>
124{
125 fn on_runtime_upgrade() -> Weight {
126 let entries = Payouts::<T, I>::iter_keys().count() as u64;
127 let pending = Pallet::<T, I>::pending_payouts_total();
128 let society_account = Pallet::<T, I>::account_id();
129 let payouts_account = Pallet::<T, I>::payouts();
130 let balance = T::Currency::free_balance(&payouts_account);
131
132 let res = match balance.cmp(&pending) {
135 Ordering::Equal => Ok(()),
136 Ordering::Less => T::Currency::transfer(
137 &society_account,
138 &payouts_account,
139 pending - balance,
140 KeepAlive,
141 ),
142 Ordering::Greater if pending.is_zero() => {
143 T::Currency::transfer(&payouts_account, &society_account, balance, AllowDeath)
144 },
145 Ordering::Greater => T::Currency::transfer(
146 &payouts_account,
147 &society_account,
148 balance - pending,
149 KeepAlive,
150 ),
151 };
152 if let Err(e) = res {
153 frame_support::defensive!("failed to reconcile the payouts account", e);
154 }
155
156 T::DbWeight::get().reads_writes(entries.saturating_add(2), 2)
157 }
158
159 #[cfg(feature = "try-runtime")]
160 fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
161 Pallet::<T, I>::do_try_state()
162 }
163}
164
165pub(crate) mod v0 {
166 use super::*;
167 use frame_support::storage_alias;
168
169 #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo)]
171 pub enum Vote {
172 Skeptic,
174 Reject,
176 Approve,
178 }
179
180 #[storage_alias]
181 pub type Bids<T: Config<I>, I: 'static> = StorageValue<
182 Pallet<T, I>,
183 Vec<Bid<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>>,
184 ValueQuery,
185 >;
186 #[storage_alias]
187 pub type Candidates<T: Config<I>, I: 'static> = StorageValue<
188 Pallet<T, I>,
189 Vec<Bid<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>>,
190 ValueQuery,
191 >;
192 #[storage_alias]
193 pub type Votes<T: Config<I>, I: 'static> = StorageDoubleMap<
194 Pallet<T, I>,
195 Twox64Concat,
196 <T as frame_system::Config>::AccountId,
197 Twox64Concat,
198 <T as frame_system::Config>::AccountId,
199 Vote,
200 >;
201 #[storage_alias]
202 pub type SuspendedCandidates<T: Config<I>, I: 'static> = StorageMap<
203 Pallet<T, I>,
204 Twox64Concat,
205 <T as frame_system::Config>::AccountId,
206 (BalanceOf<T, I>, BidKind<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>),
207 >;
208 #[storage_alias]
209 pub type Members<T: Config<I>, I: 'static> =
210 StorageValue<Pallet<T, I>, Vec<<T as frame_system::Config>::AccountId>, ValueQuery>;
211 #[storage_alias]
212 pub type Vouching<T: Config<I>, I: 'static> = StorageMap<
213 Pallet<T, I>,
214 Twox64Concat,
215 <T as frame_system::Config>::AccountId,
216 VouchingStatus,
217 >;
218 #[storage_alias]
219 pub type Strikes<T: Config<I>, I: 'static> = StorageMap<
220 Pallet<T, I>,
221 Twox64Concat,
222 <T as frame_system::Config>::AccountId,
223 StrikeCount,
224 ValueQuery,
225 >;
226 #[storage_alias]
227 pub type Payouts<T: Config<I>, I: 'static> = StorageMap<
228 Pallet<T, I>,
229 Twox64Concat,
230 <T as frame_system::Config>::AccountId,
231 Vec<(BlockNumberFor<T, I>, BalanceOf<T, I>)>,
232 ValueQuery,
233 >;
234 #[storage_alias]
235 pub type SuspendedMembers<T: Config<I>, I: 'static> = StorageMap<
236 Pallet<T, I>,
237 Twox64Concat,
238 <T as frame_system::Config>::AccountId,
239 bool,
240 ValueQuery,
241 >;
242 #[storage_alias]
243 pub type Defender<T: Config<I>, I: 'static> =
244 StorageValue<Pallet<T, I>, <T as frame_system::Config>::AccountId>;
245 #[storage_alias]
246 pub type DefenderVotes<T: Config<I>, I: 'static> =
247 StorageMap<Pallet<T, I>, Twox64Concat, <T as frame_system::Config>::AccountId, Vote>;
248}
249
250pub fn assert_internal_consistency<T: Config<I>, I: Instance + 'static>() {
252 let mut members = vec![];
254 for m in Members::<T, I>::iter_keys() {
255 let r = Members::<T, I>::get(&m).expect("Member data must be valid");
256 members.push((m, r));
257 }
258 assert_eq!(MemberCount::<T, I>::get(), members.len() as u32);
259 for (who, record) in members.iter() {
260 assert_eq!(MemberByIndex::<T, I>::get(record.index).as_ref(), Some(who));
261 }
262 if let Some(founder) = Founder::<T, I>::get() {
263 assert_eq!(Members::<T, I>::get(founder).expect("founder is member").index, 0);
264 }
265 if let Some(head) = Head::<T, I>::get() {
266 assert!(Members::<T, I>::contains_key(head));
267 }
268 for (k1, k2) in Votes::<T, I>::iter_keys() {
270 assert!(Votes::<T, I>::get(k1, k2).is_some());
271 }
272 for (k1, k2) in DefenderVotes::<T, I>::iter_keys() {
274 assert!(DefenderVotes::<T, I>::get(k1, k2).is_some());
275 }
276 for k in Candidates::<T, I>::iter_keys() {
278 assert!(Candidates::<T, I>::get(k).is_some());
279 }
280 for m in SuspendedMembers::<T, I>::iter_keys() {
282 assert!(SuspendedMembers::<T, I>::get(m).is_some());
283 }
284 for p in Payouts::<T, I>::iter_keys() {
286 let k = Payouts::<T, I>::hashed_key_for(&p);
287 let v = frame_support::storage::unhashed::get_raw(&k[..]).expect("value is in map");
288 assert!(PayoutRecordFor::<T, I>::decode(&mut &v[..]).is_ok());
289 }
290
291 assert_eq!(v0::SuspendedCandidates::<T, I>::iter().count(), 0);
293 assert_eq!(v0::Strikes::<T, I>::iter().count(), 0);
294 assert_eq!(v0::Vouching::<T, I>::iter().count(), 0);
295 assert!(!v0::Defender::<T, I>::exists());
296 assert!(!v0::Members::<T, I>::exists());
297}
298
299pub fn from_original<T: Config<I>, I: Instance + 'static>(
300 past_payouts: &mut [(<T as frame_system::Config>::AccountId, BalanceOf<T, I>)],
301) -> Result<Weight, &'static str> {
302 Bids::<T, I>::put(BoundedVec::<_, T::MaxBids>::truncate_from(v0::Bids::<T, I>::take()));
304
305 RoundCount::<T, I>::put(0);
307
308 for Bid { who: candidate, kind, value } in v0::Candidates::<T, I>::take().into_iter() {
310 let mut tally = Tally::default();
311 for (voter, vote) in v0::Votes::<T, I>::iter_prefix(&candidate) {
314 Votes::<T, I>::insert(
315 &candidate,
316 &voter,
317 Vote { approve: vote == v0::Vote::Approve, weight: 1 },
318 );
319 match vote {
320 v0::Vote::Approve => tally.approvals.saturating_inc(),
321 v0::Vote::Reject => tally.rejections.saturating_inc(),
322 v0::Vote::Skeptic => Skeptic::<T, I>::put(&voter),
323 }
324 }
325 Candidates::<T, I>::insert(
326 &candidate,
327 Candidacy { round: 0, kind, tally, skeptic_struck: false, bid: value },
328 );
329 }
330
331 let mut member_count = 0;
333 for member in v0::Members::<T, I>::take() {
334 let strikes = v0::Strikes::<T, I>::take(&member);
335 let vouching = v0::Vouching::<T, I>::take(&member);
336 let record = MemberRecord { index: member_count, rank: 0, strikes, vouching };
337 Members::<T, I>::insert(&member, record);
338 MemberByIndex::<T, I>::insert(member_count, &member);
339
340 if member == Founder::<T, I>::get().defensive_ok_or("founder must always be set")? &&
343 member_count > 0
344 {
345 let member_to_swap = MemberByIndex::<T, I>::get(0)
346 .defensive_ok_or("member_count > 0, we must have at least 1 member")?;
347 MemberByIndex::<T, I>::swap(0, member_count);
349 Members::<T, I>::mutate(&member, |m| {
351 if let Some(member) = m {
352 member.index = 0;
353 } else {
354 frame_support::defensive!(
355 "Member somehow disappeared from storage after it was inserted"
356 );
357 }
358 });
359 Members::<T, I>::mutate(&member_to_swap, |m| {
360 if let Some(member) = m {
361 member.index = member_count;
362 } else {
363 frame_support::defensive!(
364 "Member somehow disappeared from storage after it was queried"
365 );
366 }
367 });
368 }
369 member_count.saturating_inc();
370 }
371 MemberCount::<T, I>::put(member_count);
372
373 past_payouts.sort();
376 for (who, mut payouts) in v0::Payouts::<T, I>::iter() {
377 payouts.truncate(T::MaxPayouts::get() as usize);
378 let paid = past_payouts
380 .binary_search_by_key(&&who, |x| &x.0)
381 .ok()
382 .map(|p| past_payouts[p].1)
383 .unwrap_or(Zero::zero());
384 match BoundedVec::try_from(payouts) {
385 Ok(payouts) => Payouts::<T, I>::insert(who, PayoutRecord { paid, payouts }),
386 Err(_) => debug_assert!(false, "Truncation of Payouts ineffective??"),
387 }
388 }
389
390 for who in v0::SuspendedMembers::<T, I>::iter_keys() {
392 let strikes = v0::Strikes::<T, I>::take(&who);
393 let vouching = v0::Vouching::<T, I>::take(&who);
394 let record = MemberRecord { index: 0, rank: 0, strikes, vouching };
395 SuspendedMembers::<T, I>::insert(&who, record);
396 }
397
398 let _ = v0::SuspendedCandidates::<T, I>::clear(u32::MAX, None);
400
401 v0::Defender::<T, I>::kill();
403 let _ = v0::DefenderVotes::<T, I>::clear(u32::MAX, None);
404
405 Ok(T::BlockWeights::get().max_block)
406}
407
408pub fn from_raw_past_payouts<T: Config<I>, I: Instance + 'static>(
409 past_payouts_raw: impl Iterator<Item = ([u8; 32], u128)>,
410) -> Vec<(<T as frame_system::Config>::AccountId, BalanceOf<T, I>)> {
411 past_payouts_raw
412 .filter_map(|(x, y)| Some((Decode::decode(&mut &x[..]).ok()?, y.try_into().ok()?)))
413 .collect()
414}