referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_common/crowdloan/
migration.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use super::*;
18use frame_support::{
19	storage_alias,
20	traits::{GetStorageVersion, OnRuntimeUpgrade, StorageVersion},
21	Twox64Concat,
22};
23
24pub struct MigrateToTrackInactiveV2<T>(core::marker::PhantomData<T>);
25impl<T: Config> OnRuntimeUpgrade for MigrateToTrackInactiveV2<T> {
26	fn on_runtime_upgrade() -> Weight {
27		let on_chain_version = Pallet::<T>::on_chain_storage_version();
28
29		if on_chain_version == 1 {
30			let mut translated = 0u64;
31			for item in Funds::<T>::iter_values() {
32				let b =
33					CurrencyOf::<T>::total_balance(&Pallet::<T>::fund_account_id(item.fund_index));
34				CurrencyOf::<T>::deactivate(b);
35				translated.saturating_inc();
36			}
37
38			StorageVersion::new(2).put::<Pallet<T>>();
39			log::info!(target: "runtime::crowdloan", "Summed {} funds, storage to version 1", translated);
40			T::DbWeight::get().reads_writes(translated * 2 + 1, translated * 2 + 1)
41		} else {
42			log::info!(target: "runtime::crowdloan",  "Migration did not execute. This probably should be removed");
43			T::DbWeight::get().reads(1)
44		}
45	}
46
47	#[cfg(feature = "try-runtime")]
48	fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
49		let total = Funds::<T>::iter_values()
50			.map(|item| {
51				CurrencyOf::<T>::total_balance(&Pallet::<T>::fund_account_id(item.fund_index))
52			})
53			.fold(BalanceOf::<T>::zero(), |a, i| a.saturating_add(i));
54		Ok((total, CurrencyOf::<T>::active_issuance()).encode())
55	}
56
57	#[cfg(feature = "try-runtime")]
58	fn post_upgrade(total: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
59		if let Ok((total, active)) = <(BalanceOf<T>, BalanceOf<T>)>::decode(&mut total.as_slice()) {
60			ensure!(active - total == CurrencyOf::<T>::active_issuance(), "the total be correct");
61			Ok(())
62		} else {
63			Err("the state parameter should be something that was generated by pre_upgrade".into())
64		}
65	}
66}
67
68/// Migrations for using fund index to create fund accounts instead of para ID.
69pub mod crowdloan_index_migration {
70	use super::*;
71
72	#[storage_alias]
73	type NextTrieIndex<T: Config> = StorageValue<Pallet<T>, FundIndex>;
74
75	#[storage_alias]
76	type Leases<T: Config> = StorageMap<
77		Slots,
78		Twox64Concat,
79		ParaId,
80		Vec<Option<(<T as frame_system::Config>::AccountId, BalanceOf<T>)>>,
81	>;
82
83	// The old way we generated fund accounts.
84	fn old_fund_account_id<T: Config>(index: ParaId) -> T::AccountId {
85		T::PalletId::get().into_sub_account_truncating(index)
86	}
87
88	pub fn pre_migrate<T: Config>() -> Result<(), &'static str> {
89		// `NextTrieIndex` should have a value.
90
91		let next_index = NextTrieIndex::<T>::get().unwrap_or_default();
92		ensure!(next_index > 0, "Next index is zero, which implies no migration is needed.");
93
94		log::info!(
95			target: "runtime",
96			"next trie index: {:?}",
97			next_index,
98		);
99
100		for (para_id, fund) in Funds::<T>::iter() {
101			let old_fund_account = old_fund_account_id::<T>(para_id);
102			let total_balance = CurrencyOf::<T>::total_balance(&old_fund_account);
103
104			log::info!(
105				target: "runtime",
106				"para_id={:?}, old_fund_account={:?}, total_balance={:?}, fund.raised={:?}",
107				para_id, old_fund_account, total_balance, fund.raised
108			);
109
110			// Each fund should have some non-zero balance.
111			ensure!(
112				total_balance >= fund.raised,
113				"Total balance is not equal to the funds raised."
114			);
115
116			let leases = Leases::<T>::get(para_id).unwrap_or_default();
117			let mut found_lease_deposit = false;
118			for (who, _amount) in leases.iter().flatten() {
119				if *who == old_fund_account {
120					found_lease_deposit = true;
121					break
122				}
123			}
124			if found_lease_deposit {
125				log::info!(
126					target: "runtime",
127					"para_id={:?}, old_fund_account={:?}, leases={:?}",
128					para_id, old_fund_account, leases,
129				);
130			}
131		}
132
133		Ok(())
134	}
135
136	/// This migration converts crowdloans to use a crowdloan index rather than the parachain id as
137	/// a unique identifier. This makes it easier to swap two crowdloans between parachains.
138	pub fn migrate<T: Config>() -> frame_support::weights::Weight {
139		let mut weight = Weight::zero();
140
141		// First migrate `NextTrieIndex` counter to `NextFundIndex`.
142
143		let next_index = NextTrieIndex::<T>::take().unwrap_or_default();
144		NextFundIndex::<T>::set(next_index);
145
146		weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2));
147
148		// Migrate all accounts from `old_fund_account` to `fund_account` using `fund_index`.
149		for (para_id, fund) in Funds::<T>::iter() {
150			let old_fund_account = old_fund_account_id::<T>(para_id);
151			let new_fund_account = Pallet::<T>::fund_account_id(fund.fund_index);
152
153			// Funds should only have a free balance and a reserve balance. Both of these are in the
154			// `Account` storage item, so we just swap them.
155			let account_info = frame_system::Account::<T>::take(&old_fund_account);
156			frame_system::Account::<T>::insert(&new_fund_account, account_info);
157
158			weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2));
159
160			let mut leases = Leases::<T>::get(para_id).unwrap_or_default();
161			for (who, _amount) in leases.iter_mut().flatten() {
162				if *who == old_fund_account {
163					*who = new_fund_account.clone();
164				}
165			}
166
167			Leases::<T>::insert(para_id, leases);
168		}
169
170		weight
171	}
172
173	pub fn post_migrate<T: Config>() -> Result<(), &'static str> {
174		// `NextTrieIndex` should not have a value, and `NextFundIndex` should.
175		ensure!(NextTrieIndex::<T>::get().is_none(), "NextTrieIndex still has a value.");
176
177		let next_index = NextFundIndex::<T>::get();
178		log::info!(
179			target: "runtime",
180			"next fund index: {:?}",
181			next_index,
182		);
183
184		ensure!(
185			next_index > 0,
186			"NextFundIndex was not migrated or is zero. We assume it cannot be zero else no migration is needed."
187		);
188
189		// Each fund should have balance migrated correctly.
190		for (para_id, fund) in Funds::<T>::iter() {
191			// Old fund account is deleted.
192			let old_fund_account = old_fund_account_id::<T>(para_id);
193			ensure!(
194				frame_system::Account::<T>::get(&old_fund_account) == Default::default(),
195				"Old account wasn't reset to default value."
196			);
197
198			// New fund account has the correct balance.
199			let new_fund_account = Pallet::<T>::fund_account_id(fund.fund_index);
200			let total_balance = CurrencyOf::<T>::total_balance(&new_fund_account);
201
202			ensure!(
203				total_balance >= fund.raised,
204				"Total balance in new account is different than the funds raised."
205			);
206
207			let leases = Leases::<T>::get(para_id).unwrap_or_default();
208			let mut new_account_found = false;
209			for (who, _amount) in leases.iter().flatten() {
210				if *who == old_fund_account {
211					panic!("Old fund account found after migration!");
212				} else if *who == new_fund_account {
213					new_account_found = true;
214				}
215			}
216			if new_account_found {
217				log::info!(
218					target: "runtime::crowdloan",
219					"para_id={:?}, new_fund_account={:?}, leases={:?}",
220					para_id, new_fund_account, leases,
221				);
222			}
223		}
224
225		Ok(())
226	}
227}