1#![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
59pub 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> {
76 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 type RcBlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
87
88 type WeightInfo: WeightInfo;
90 }
91
92 #[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 #[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 #[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 NoLeaseReserve,
167 NoCrowdloanContribution,
169 NoCrowdloanReserve,
171 FailedToWithdrawCrowdloanContribution,
173 NotYet,
175 ContributionsRemaining,
177 WrongSovereignTranslation,
179 WrongDerivedTranslation,
181 NotSovereign,
183 InternalError,
185 WouldReap,
187 FailedToPutHold,
189 FailedToReleaseHold,
191 FailedToThaw,
193 FailedToSetFreeze,
195 FailedToTransfer,
197 FailedToReserve,
199 CannotUnreserve,
201 AccountIdentical,
203 }
204
205 #[pallet::event]
206 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
207 pub enum Event<T: Config> {
208 LeaseUnreserveRemaining {
210 depositor: T::AccountId,
211 para_id: ParaId,
212 remaining: BalanceOf<T>,
213 },
214
215 CrowdloanUnreserveRemaining {
217 depositor: T::AccountId,
218 para_id: ParaId,
219 remaining: BalanceOf<T>,
220 },
221
222 SovereignMigrated {
225 para_id: ParaId,
227 from: T::AccountId,
229 to: T::AccountId,
231 derivation_index: Option<DerivationIndex>,
233 },
234
235 HoldPlaced { account: T::AccountId, amount: BalanceOf<T>, reason: T::RuntimeHoldReason },
237
238 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 #[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 #[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 #[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 #[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 #[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 #[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 match Self::do_unreserve_lease_deposit(block, pot.clone(), para_id) {
403 Ok(()) => (),
404 Err(Error::<T>::NoLeaseReserve) => (), Err(e) => return Err(e),
406 }
407
408 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 <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 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 return Ok(());
464 }
465 if from == to {
466 return Err(Error::<T>::AccountIdentical);
467 }
468 pallet_balances::Pallet::<T>::ensure_upgraded(from); 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 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 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 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 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 let consumers = frame_system::Pallet::<T>::consumers(from);
517 frame_system::Account::<T>::mutate(from, |acc| {
518 acc.consumers = 0;
519 });
520 ensure!(frame_system::Pallet::<T>::sufficients(from) == 0, Error::<T>::InternalError);
522
523 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 <T as Config>::Currency::transfer(from, to, total, Preservation::Expendable)
538 .defensive()
539 .map_err(|_| Error::<T>::FailedToTransfer)?;
540
541 frame_system::Account::<T>::mutate(to, |acc| {
543 acc.consumers += consumers;
544 });
545
546 for hold in &holds {
548 <T as Config>::Currency::hold(&hold.id, to, hold.amount)
549 .map_err(|_| Error::<T>::FailedToPutHold)?;
550 Self::deposit_event(Event::HoldPlaced {
552 account: to.clone(),
553 amount: hold.amount,
554 reason: hold.id,
555 });
556 }
557
558 <T as Config>::Currency::reserve(to, unnamed_reserve)
560 .defensive()
561 .map_err(|_| Error::<T>::FailedToReserve)?;
562
563 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 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 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 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 let Some(raw) = raw.strip_prefix(b"para") else {
641 return Err(Error::<T>::NotSovereign);
642 };
643 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 let mut ah_raw = [0u8; 32];
651 ah_raw[0..4].copy_from_slice(b"sibl");
652 ah_raw[4..6].copy_from_slice(¶_id.encode());
653
654 Ok((ah_raw.into(), para_id))
655 }
656
657 pub fn try_rc_sovereign_derived_to_ah(
659 from: &AccountId32,
660 parent: &AccountId32,
661 index: DerivationIndex,
662 ) -> Result<(AccountId32, ParaId), Error<T>> {
663 {
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
677pub 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}