referrerpolicy=no-referrer-when-downgrade

pallet_ah_ops/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! The operational pallet for the Asset Hub, designed to manage and facilitate the migration of
18//! subsystems such as Governance, Staking, Balances from the Relay Chain to the Asset Hub. This
19//! pallet works alongside its counterpart, `pallet_rc_migrator`, which handles migration
20//! processes on the Relay Chain side.
21//!
22//! This pallet is responsible for controlling the initiation, progression, and completion of the
23//! migration process, including managing its various stages and transferring the necessary data.
24//! The pallet directly accesses the storage of other pallets for read/write operations while
25//! maintaining compatibility with their existing APIs.
26//!
27//! To simplify development and avoid the need to edit the original pallets, this pallet may
28//! duplicate private items such as storage entries from the original pallets. This ensures that the
29//! migration logic can be implemented without altering the original implementations.
30
31#![cfg_attr(not(feature = "std"), no_std)]
32
33#[cfg(feature = "runtime-benchmarks")]
34pub mod benchmarking;
35#[cfg(test)]
36mod mock;
37#[cfg(test)]
38mod tests;
39pub mod weights;
40
41pub use pallet::*;
42pub use weights::WeightInfo;
43
44use codec::DecodeAll;
45use frame_support::{
46	pallet_prelude::*,
47	traits::{
48		fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze, MutateHold, Unbalanced},
49		tokens::{Fortitude, IdAmount, Precision, Preservation},
50		Defensive, LockableCurrency, ReservableCurrency, WithdrawReasons as LockWithdrawReasons,
51	},
52};
53use frame_system::pallet_prelude::*;
54use pallet_balances::{AccountData, BalanceLock, Reasons as LockReasons};
55use sp_application_crypto::ByteArray;
56use sp_runtime::{traits::BlockNumberProvider, AccountId32};
57use sp_std::prelude::*;
58
59/// The log target of this pallet.
60pub const LOG_TARGET: &str = "runtime::ah-ops";
61
62pub type BalanceOf<T> = <T as pallet_balances::Config>::Balance;
63pub type DerivationIndex = u16;
64pub type ParaId = u16;
65
66#[frame_support::pallet]
67pub mod pallet {
68	use super::*;
69
70	#[pallet::config]
71	pub trait Config:
72		frame_system::Config<AccountData = AccountData<u128>, AccountId = AccountId32>
73		+ pallet_balances::Config<Balance = u128>
74		+ pallet_timestamp::Config<Moment = u64> // Needed for testing
75	{
76		/// Native asset type.
77		type Currency: Mutate<Self::AccountId, Balance = u128>
78			+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
79			+ InspectFreeze<Self::AccountId, Id = Self::FreezeIdentifier>
80			+ MutateFreeze<Self::AccountId>
81			+ Unbalanced<Self::AccountId>
82			+ ReservableCurrency<Self::AccountId, Balance = u128>
83			+ LockableCurrency<Self::AccountId, Balance = u128>;
84
85		/// Access the block number of the Relay Chain.
86		type RcBlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
87
88		/// The Weight information for extrinsics in this pallet.
89		type WeightInfo: WeightInfo;
90	}
91
92	/// Amount of balance that was reserved for winning a lease auction.
93	///
94	/// `unreserve_lease_deposit` can be permissionlessly called once the block number passed to
95	/// unreserve the deposit. It is implicitly called by `withdraw_crowdloan_contribution`.
96	///  
97	/// The account here can either be a crowdloan account or a solo bidder. If it is a crowdloan
98	/// account, then the summed up contributions for it in the contributions map will equate the
99	/// reserved balance here.
100	///
101	/// The keys are as follows:
102	/// - Block number after which the deposit can be unreserved.
103	/// - The para_id of the lease slot.
104	/// - The account that will have the balance unreserved.
105	/// - The balance to be unreserved.
106	#[pallet::storage]
107	pub type RcLeaseReserve<T: Config> = StorageNMap<
108		_,
109		(
110			NMapKey<Twox64Concat, BlockNumberFor<T>>,
111			NMapKey<Twox64Concat, ParaId>,
112			NMapKey<Twox64Concat, T::AccountId>,
113		),
114		BalanceOf<T>,
115		OptionQuery,
116	>;
117
118	/// Amount of balance that a contributor made towards a crowdloan.
119	///
120	/// `withdraw_crowdloan_contribution` can be permissionlessly called once the block number
121	/// passed to unlock the balance for a specific account.
122	///
123	/// The keys are as follows:
124	/// - Block number after which the balance can be unlocked.
125	/// - The para_id of the crowdloan.
126	/// - The account that made the contribution.
127	///
128	/// The value is (fund_pot, balance). The contribution pot is the second key in the
129	/// `RcCrowdloanContribution` storage.
130	#[pallet::storage]
131	pub type RcCrowdloanContribution<T: Config> = StorageNMap<
132		_,
133		(
134			NMapKey<Twox64Concat, BlockNumberFor<T>>,
135			NMapKey<Twox64Concat, ParaId>,
136			NMapKey<Twox64Concat, T::AccountId>,
137		),
138		(T::AccountId, BalanceOf<T>),
139		OptionQuery,
140	>;
141
142	/// The reserve that was taken to create a crowdloan.
143	///
144	/// This is normally 500 DOT and can be refunded as last step after all
145	/// `RcCrowdloanContribution`s of this loan have been withdrawn.
146	///
147	/// Keys:
148	/// - Block number after which this can be unreserved
149	/// - The para_id of the crowdloan
150	/// - The account that will have the balance unreserved
151	#[pallet::storage]
152	pub type RcCrowdloanReserve<T: Config> = StorageNMap<
153		_,
154		(
155			NMapKey<Twox64Concat, BlockNumberFor<T>>,
156			NMapKey<Twox64Concat, ParaId>,
157			NMapKey<Twox64Concat, T::AccountId>,
158		),
159		BalanceOf<T>,
160		OptionQuery,
161	>;
162
163	#[pallet::error]
164	pub enum Error<T> {
165		/// Either no lease deposit or already unreserved.
166		NoLeaseReserve,
167		/// Either no crowdloan contribution or already withdrawn.
168		NoCrowdloanContribution,
169		/// Either no crowdloan reserve or already unreserved.
170		NoCrowdloanReserve,
171		/// Failed to withdraw crowdloan contribution.
172		FailedToWithdrawCrowdloanContribution,
173		/// Block number is not yet reached.
174		NotYet,
175		/// Not all contributions are withdrawn.
176		ContributionsRemaining,
177		/// Passed account IDs are not matching unmigrated child and sibling accounts.
178		WrongSovereignTranslation,
179		/// The account is not a derived account.
180		WrongDerivedTranslation,
181		/// Account cannot be migrated since it is not a sovereign parachain account.
182		NotSovereign,
183		/// Internal error, please bug report.
184		InternalError,
185		/// The migrated account would get reaped in the process.
186		WouldReap,
187		/// Failed to put a hold on an account.
188		FailedToPutHold,
189		/// Failed to release a hold from an account.
190		FailedToReleaseHold,
191		/// Failed to thaw a frozen balance.
192		FailedToThaw,
193		/// Failed to set a freeze on an account.
194		FailedToSetFreeze,
195		/// Failed to transfer a balance.
196		FailedToTransfer,
197		/// Failed to reserve a balance.
198		FailedToReserve,
199		/// Failed to unreserve the full balance.
200		CannotUnreserve,
201		/// The from and to accounts are identical.
202		AccountIdentical,
203	}
204
205	#[pallet::event]
206	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
207	pub enum Event<T: Config> {
208		/// Some lease reserve could not be unreserved and needs manual cleanup.
209		LeaseUnreserveRemaining {
210			depositor: T::AccountId,
211			para_id: ParaId,
212			remaining: BalanceOf<T>,
213		},
214
215		/// Some amount for a crowdloan reserve could not be unreserved and needs manual cleanup.
216		CrowdloanUnreserveRemaining {
217			depositor: T::AccountId,
218			para_id: ParaId,
219			remaining: BalanceOf<T>,
220		},
221
222		/// A sovereign parachain account has been migrated from its child to sibling
223		/// representation.
224		SovereignMigrated {
225			/// The parachain ID that had its account migrated.
226			para_id: ParaId,
227			/// The old account that was migrated out of.
228			from: T::AccountId,
229			/// The new account that was migrated into.
230			to: T::AccountId,
231			/// Set if this account was derived from a para sovereign account.
232			derivation_index: Option<DerivationIndex>,
233		},
234
235		/// An amount of fungible balance was put on hold.
236		HoldPlaced { account: T::AccountId, amount: BalanceOf<T>, reason: T::RuntimeHoldReason },
237
238		/// An amount of fungible balance was released from its hold.
239		HoldReleased { account: T::AccountId, amount: BalanceOf<T>, reason: T::RuntimeHoldReason },
240	}
241
242	#[pallet::pallet]
243	pub struct Pallet<T>(_);
244
245	#[pallet::call]
246	impl<T: Config> Pallet<T> {
247		/// Unreserve the deposit that was taken for creating a crowdloan.
248		///
249		/// This can be called by any signed origin. It unreserves the lease deposit on the account
250		/// that won the lease auction. It can be unreserved once all leases expired. Note that it
251		/// will be called automatically from `withdraw_crowdloan_contribution` for the matching
252		/// crowdloan account.
253		///
254		/// Solo bidder accounts that won lease auctions can use this to unreserve their amount.
255		#[pallet::call_index(0)]
256		#[pallet::weight(<T as Config>::WeightInfo::unreserve_lease_deposit())]
257		pub fn unreserve_lease_deposit(
258			origin: OriginFor<T>,
259			block: BlockNumberFor<T>,
260			depositor: Option<T::AccountId>,
261			para_id: ParaId,
262		) -> DispatchResult {
263			let sender = ensure_signed(origin)?;
264			let depositor = depositor.unwrap_or(sender);
265
266			Self::do_unreserve_lease_deposit(block, depositor, para_id).map_err(Into::into)
267		}
268
269		/// Withdraw the contribution of a finished crowdloan.
270		///
271		/// A crowdloan contribution can be withdrawn if either:
272		/// - The crowdloan failed to in an auction and timed out
273		/// - Won an auction and all leases expired
274		///
275		/// Can be called by any signed origin.
276		#[pallet::call_index(1)]
277		#[pallet::weight(<T as Config>::WeightInfo::withdraw_crowdloan_contribution())]
278		pub fn withdraw_crowdloan_contribution(
279			origin: OriginFor<T>,
280			block: BlockNumberFor<T>,
281			depositor: Option<T::AccountId>,
282			para_id: ParaId,
283		) -> DispatchResult {
284			let sender = ensure_signed(origin)?;
285			let depositor = depositor.unwrap_or(sender);
286
287			Self::do_withdraw_crowdloan_contribution(block, depositor, para_id).map_err(Into::into)
288		}
289
290		/// Unreserve the deposit that was taken for creating a crowdloan.
291		///
292		/// This can be called once either:
293		/// - The crowdloan failed to win an auction and timed out
294		/// - Won an auction, all leases expired and all contributions are withdrawn
295		///
296		/// Can be called by any signed origin. The condition that all contributions are withdrawn
297		/// is in place since the reserve acts as a storage deposit.
298		#[pallet::call_index(2)]
299		#[pallet::weight(<T as Config>::WeightInfo::unreserve_crowdloan_reserve())]
300		pub fn unreserve_crowdloan_reserve(
301			origin: OriginFor<T>,
302			block: BlockNumberFor<T>,
303			depositor: Option<T::AccountId>,
304			para_id: ParaId,
305		) -> DispatchResult {
306			let sender = ensure_signed(origin)?;
307			let depositor = depositor.unwrap_or(sender);
308
309			Self::do_unreserve_crowdloan_reserve(block, depositor, para_id).map_err(Into::into)
310		}
311
312		/// Try to migrate a parachain sovereign child account to its respective sibling.
313		///
314		/// Takes the old and new account and migrates it only if they are as expected. An event of
315		/// `SovereignMigrated` will be emitted if the account was migrated successfully.
316		///
317		/// Callable by any signed origin.
318		#[pallet::call_index(3)]
319		#[pallet::weight(T::DbWeight::get().reads_writes(15, 15)
320					.saturating_add(Weight::from_parts(0, 50_000)))]
321		pub fn migrate_parachain_sovereign_acc(
322			origin: OriginFor<T>,
323			from: T::AccountId,
324			to: T::AccountId,
325		) -> DispatchResult {
326			ensure_root(origin)?;
327
328			Self::do_migrate_parachain_sovereign_derived_acc(&from, &to, None).map_err(Into::into)
329		}
330
331		/// Try to migrate a parachain sovereign child account to its respective sibling.
332		///
333		/// Takes the old and new account and migrates it only if they are as expected. An event of
334		/// `SovereignMigrated` will be emitted if the account was migrated successfully.
335		///
336		/// Callable by any signed origin.
337		#[pallet::call_index(5)]
338		#[pallet::weight(T::DbWeight::get().reads_writes(15, 15)
339					.saturating_add(Weight::from_parts(0, 50_000)))]
340		pub fn migrate_parachain_sovereign_derived_acc(
341			origin: OriginFor<T>,
342			from: T::AccountId,
343			to: T::AccountId,
344			derivation: (T::AccountId, DerivationIndex),
345		) -> DispatchResult {
346			ensure_root(origin)?;
347
348			Self::do_migrate_parachain_sovereign_derived_acc(&from, &to, Some(derivation))
349				.map_err(Into::into)
350		}
351
352		/// Force unreserve a named or unnamed reserve.
353		#[pallet::call_index(4)]
354		#[pallet::weight(T::DbWeight::get().reads_writes(10, 10)
355					.saturating_add(Weight::from_parts(0, 50_000)))]
356		pub fn force_unreserve(
357			origin: OriginFor<T>,
358			account: T::AccountId,
359			amount: BalanceOf<T>,
360			reason: Option<T::RuntimeHoldReason>,
361		) -> DispatchResult {
362			ensure_root(origin)?;
363
364			Self::do_force_unreserve(account, amount, reason).map_err(Into::into)
365		}
366	}
367
368	impl<T: Config> Pallet<T> {
369		pub fn do_unreserve_lease_deposit(
370			block: BlockNumberFor<T>,
371			depositor: T::AccountId,
372			para_id: ParaId,
373		) -> Result<(), Error<T>> {
374			ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::<T>::NotYet);
375			let balance = RcLeaseReserve::<T>::take((block, para_id, &depositor))
376				.ok_or(Error::<T>::NoLeaseReserve)?;
377
378			let remaining = <T as Config>::Currency::unreserve(&depositor, balance);
379			if remaining > 0 {
380				defensive!("Should be able to unreserve all");
381				Self::deposit_event(Event::LeaseUnreserveRemaining {
382					depositor,
383					remaining,
384					para_id,
385				});
386			}
387
388			Ok(())
389		}
390
391		pub fn do_withdraw_crowdloan_contribution(
392			block: BlockNumberFor<T>,
393			depositor: T::AccountId,
394			para_id: ParaId,
395		) -> Result<(), Error<T>> {
396			ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::<T>::NotYet);
397			let (pot, contribution) =
398				RcCrowdloanContribution::<T>::take((block, para_id, &depositor))
399					.ok_or(Error::<T>::NoCrowdloanContribution)?;
400
401			// Maybe this is the first one to withdraw and we need to unreserve it from the pot
402			match Self::do_unreserve_lease_deposit(block, pot.clone(), para_id) {
403				Ok(()) => (),
404				Err(Error::<T>::NoLeaseReserve) => (), // fine
405				Err(e) => return Err(e),
406			}
407
408			// Ideally this does not fail. But if it does, then we keep it for manual inspection.
409			let transferred = <T as Config>::Currency::transfer(
410				&pot,
411				&depositor,
412				contribution,
413				Preservation::Preserve,
414			)
415			.defensive()
416			.map_err(|_| Error::<T>::FailedToWithdrawCrowdloanContribution)?;
417			defensive_assert!(transferred == contribution);
418			// Need to reactivate since we deactivated it here https://github.com/paritytech/polkadot-sdk/blob/04847d515ef56da4d0801c9b89a4241dfa827b33/polkadot/runtime/common/src/crowdloan/mod.rs#L793
419			<T as Config>::Currency::reactivate(transferred);
420
421			Ok(())
422		}
423
424		pub fn do_unreserve_crowdloan_reserve(
425			block: BlockNumberFor<T>,
426			depositor: T::AccountId,
427			para_id: ParaId,
428		) -> Result<(), Error<T>> {
429			ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::<T>::NotYet);
430			ensure!(
431				Self::contributions_withdrawn(block, para_id),
432				Error::<T>::ContributionsRemaining
433			);
434			let amount = RcCrowdloanReserve::<T>::take((block, para_id, &depositor))
435				.ok_or(Error::<T>::NoCrowdloanReserve)?;
436
437			let remaining = <T as Config>::Currency::unreserve(&depositor, amount);
438			if remaining > 0 {
439				defensive!("Should be able to unreserve all");
440				Self::deposit_event(Event::CrowdloanUnreserveRemaining {
441					depositor,
442					remaining,
443					para_id,
444				});
445			}
446
447			Ok(())
448		}
449
450		// TODO Test this
451		fn contributions_withdrawn(block: BlockNumberFor<T>, para_id: ParaId) -> bool {
452			let mut contrib_iter = RcCrowdloanContribution::<T>::iter_prefix((block, para_id));
453			contrib_iter.next().is_none()
454		}
455
456		pub fn do_migrate_parachain_sovereign_derived_acc(
457			from: &T::AccountId,
458			to: &T::AccountId,
459			derivation: Option<(T::AccountId, DerivationIndex)>,
460		) -> Result<(), Error<T>> {
461			if frame_system::Account::<T>::get(from) == Default::default() {
462				// Nothing to do if the account does not exist
463				return Ok(());
464			}
465			if from == to {
466				return Err(Error::<T>::AccountIdentical);
467			}
468			pallet_balances::Pallet::<T>::ensure_upgraded(from); // prevent future headache
469
470			let (translated_acc, para_id, index) = if let Some((parent, index)) = derivation {
471				let (parent_translated, para_id) =
472					Self::try_rc_sovereign_derived_to_ah(from, &parent, index)?;
473				(parent_translated, para_id, Some(index))
474			} else {
475				let (translated_acc, para_id) = Self::try_translate_rc_sovereign_to_ah(from)?;
476				(translated_acc, para_id, None)
477			};
478			ensure!(translated_acc == *to, Error::<T>::WrongSovereignTranslation);
479
480			// Release all locks
481			let locks: Vec<BalanceLock<T::Balance>> =
482				pallet_balances::Locks::<T>::get(from).into_inner();
483			for lock in &locks {
484				let () = <T as Config>::Currency::remove_lock(lock.id, from);
485			}
486
487			// Thaw all the freezes
488			let freezes: Vec<IdAmount<T::FreezeIdentifier, T::Balance>> =
489				pallet_balances::Freezes::<T>::get(from).into();
490
491			for freeze in &freezes {
492				let () = <T as Config>::Currency::thaw(&freeze.id, from)
493					.map_err(|_| Error::<T>::FailedToThaw)?;
494			}
495
496			// Release all holds
497			let holds: Vec<IdAmount<T::RuntimeHoldReason, T::Balance>> =
498				pallet_balances::Holds::<T>::get(from).into();
499
500			for IdAmount { id, amount } in &holds {
501				let _ = <T as Config>::Currency::release(id, from, *amount, Precision::Exact)
502					.map_err(|_| Error::<T>::FailedToReleaseHold)?;
503				Self::deposit_event(Event::HoldReleased {
504					account: from.clone(),
505					amount: *amount,
506					reason: *id,
507				});
508			}
509
510			// Unreserve unnamed reserves
511			let unnamed_reserve = <T as Config>::Currency::reserved_balance(from);
512			let missing = <T as Config>::Currency::unreserve(from, unnamed_reserve);
513			defensive_assert!(missing == 0, "Should have unreserved the full amount");
514
515			// Set consumer refs to zero
516			let consumers = frame_system::Pallet::<T>::consumers(from);
517			frame_system::Account::<T>::mutate(from, |acc| {
518				acc.consumers = 0;
519			});
520			// We dont handle sufficients and there should be none
521			ensure!(frame_system::Pallet::<T>::sufficients(from) == 0, Error::<T>::InternalError);
522
523			// Sanity check
524			let total = <T as Config>::Currency::total_balance(from);
525			let reducible = <T as Config>::Currency::reducible_balance(
526				from,
527				Preservation::Expendable,
528				Fortitude::Polite,
529			);
530			defensive_assert!(
531				total >= <T as Config>::Currency::minimum_balance(),
532				"Must have at least ED"
533			);
534			defensive_assert!(total == reducible, "Total balance should be reducible");
535
536			// Now the actual balance transfer to the new account
537			<T as Config>::Currency::transfer(from, to, total, Preservation::Expendable)
538				.defensive()
539				.map_err(|_| Error::<T>::FailedToTransfer)?;
540
541			// Apply consumer refs
542			frame_system::Account::<T>::mutate(to, |acc| {
543				acc.consumers += consumers;
544			});
545
546			// Reapply the holds
547			for hold in &holds {
548				<T as Config>::Currency::hold(&hold.id, to, hold.amount)
549					.map_err(|_| Error::<T>::FailedToPutHold)?;
550				// Somehow there are no events for this being emitted... so we emit our own.
551				Self::deposit_event(Event::HoldPlaced {
552					account: to.clone(),
553					amount: hold.amount,
554					reason: hold.id,
555				});
556			}
557
558			// Reapply the reserve
559			<T as Config>::Currency::reserve(to, unnamed_reserve)
560				.defensive()
561				.map_err(|_| Error::<T>::FailedToReserve)?;
562
563			// Reapply the locks
564			for lock in &locks {
565				let reasons = map_lock_reason(lock.reasons);
566				<T as Config>::Currency::set_lock(lock.id, to, lock.amount, reasons);
567			}
568			// Reapply the freezes
569			for freeze in &freezes {
570				<T as Config>::Currency::set_freeze(&freeze.id, to, freeze.amount)
571					.map_err(|_| Error::<T>::FailedToSetFreeze)?;
572			}
573
574			defensive_assert!(
575				frame_system::Account::<T>::get(from) == Default::default(),
576				"Must reap old account"
577			);
578			// If new account would die from this, then lets rather not do it and check it manually.
579			ensure!(
580				frame_system::Account::<T>::get(to) != Default::default(),
581				Error::<T>::WouldReap
582			);
583
584			Self::deposit_event(Event::SovereignMigrated {
585				para_id,
586				from: from.clone(),
587				to: to.clone(),
588				derivation_index: index,
589			});
590
591			Ok(())
592		}
593
594		pub fn do_force_unreserve(
595			account: T::AccountId,
596			amount: BalanceOf<T>,
597			reason: Option<T::RuntimeHoldReason>,
598		) -> Result<(), Error<T>> {
599			if let Some(reason) = reason {
600				<T as Config>::Currency::release(&reason, &account, amount, Precision::Exact)
601					.map_err(|_| Error::<T>::FailedToReleaseHold)?;
602				Self::deposit_event(Event::HoldReleased {
603					account: account.clone(),
604					amount,
605					reason,
606				});
607			} else {
608				let remaining = <T as Config>::Currency::unreserve(&account, amount);
609				if remaining > 0 {
610					return Err(Error::<T>::CannotUnreserve);
611				}
612			}
613
614			Ok(())
615		}
616
617		/// Try to translate a Parachain sovereign account to the Parachain AH sovereign account.
618		///
619		/// Returns:
620		/// - `Ok(None)` if the account is not a Parachain sovereign account
621		/// - `Ok(Some((ah_account, para_id)))` with the translated account and the para id
622		/// - `Err(())` otherwise
623		///
624		/// The way that this normally works is through the configured
625		/// `SiblingParachainConvertsVia`: <https://github.com/polkadot-fellows/runtimes/blob/7b096c14c2b16cc81ca4e2188eea9103f120b7a4/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs#L93-L94>
626		/// it passes the `Sibling` type into it which has type-ID `sibl`:
627		/// <https://github.com/paritytech/polkadot-sdk/blob/c10e25aaa8b8afd8665b53f0a0b02e4ea44caa77/polkadot/parachain/src/primitives.rs#L272-L274>
628		/// This type-ID gets used by the converter here:
629		/// <https://github.com/paritytech/polkadot-sdk/blob/7ecf3f757a5d6f622309cea7f788e8a547a5dce8/polkadot/xcm/xcm-builder/src/location_conversion.rs#L314>
630		/// and eventually ends up in the encoding here
631		/// <https://github.com/paritytech/polkadot-sdk/blob/cdf107de700388a52a17b2fb852c98420c78278e/substrate/primitives/runtime/src/traits/mod.rs#L1997-L1999>
632		/// The `para` conversion is likewise with `ChildParachainConvertsVia` and the `para`
633		/// type-ID <https://github.com/paritytech/polkadot-sdk/blob/c10e25aaa8b8afd8665b53f0a0b02e4ea44caa77/polkadot/parachain/src/primitives.rs#L162-L164>
634		pub fn try_translate_rc_sovereign_to_ah(
635			from: &AccountId32,
636		) -> Result<(AccountId32, ParaId), Error<T>> {
637			let raw = from.to_raw_vec();
638
639			// Must start with "para"
640			let Some(raw) = raw.strip_prefix(b"para") else {
641				return Err(Error::<T>::NotSovereign);
642			};
643			// Must end with 26 zero bytes
644			let Some(raw) = raw.strip_suffix(&[0u8; 26]) else {
645				return Err(Error::<T>::NotSovereign);
646			};
647			let para_id = u16::decode_all(&mut &raw[..]).map_err(|_| Error::<T>::InternalError)?;
648
649			// Translate to AH sibling account
650			let mut ah_raw = [0u8; 32];
651			ah_raw[0..4].copy_from_slice(b"sibl");
652			ah_raw[4..6].copy_from_slice(&para_id.encode());
653
654			Ok((ah_raw.into(), para_id))
655		}
656
657		/// Same as `try_translate_rc_sovereign_to_ah` but for derived accounts.
658		pub fn try_rc_sovereign_derived_to_ah(
659			from: &AccountId32,
660			parent: &AccountId32,
661			index: DerivationIndex,
662		) -> Result<(AccountId32, ParaId), Error<T>> {
663			// check the derivation proof
664			{
665				let derived = pallet_utility::derivative_account_id(parent.clone(), index);
666				ensure!(derived == *from, Error::<T>::WrongDerivedTranslation);
667			}
668
669			let (parent_translated, para_id) = Self::try_translate_rc_sovereign_to_ah(parent)?;
670			let parent_translated_derived =
671				pallet_utility::derivative_account_id(parent_translated, index);
672			Ok((parent_translated_derived, para_id))
673		}
674	}
675}
676
677/// Backward mapping from <https://github.com/paritytech/polkadot-sdk/blob/74a5e1a242274ddaadac1feb3990fc95c8612079/substrate/frame/balances/src/types.rs#L38>
678pub fn map_lock_reason(reasons: LockReasons) -> LockWithdrawReasons {
679	match reasons {
680		LockReasons::All => LockWithdrawReasons::TRANSACTION_PAYMENT | LockWithdrawReasons::RESERVE,
681		LockReasons::Fee => LockWithdrawReasons::TRANSACTION_PAYMENT,
682		LockReasons::Misc => LockWithdrawReasons::TIP,
683	}
684}