#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
pub use pallet::*;
pub use weights::WeightInfo;
use codec::DecodeAll;
use frame_support::{
pallet_prelude::*,
traits::{
fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze, MutateHold, Unbalanced},
tokens::{Fortitude, IdAmount, Precision, Preservation},
Defensive, LockableCurrency, ReservableCurrency, WithdrawReasons as LockWithdrawReasons,
},
};
use frame_system::pallet_prelude::*;
use pallet_balances::{AccountData, BalanceLock, Reasons as LockReasons};
use sp_application_crypto::ByteArray;
use sp_runtime::{traits::BlockNumberProvider, AccountId32};
use sp_std::prelude::*;
pub const LOG_TARGET: &str = "runtime::ah-ops";
pub type BalanceOf<T> = <T as pallet_balances::Config>::Balance;
pub type DerivationIndex = u16;
pub type ParaId = u16;
#[frame_support::pallet]
pub mod pallet {
use super::*;
#[pallet::config]
pub trait Config:
frame_system::Config<AccountData = AccountData<u128>, AccountId = AccountId32>
+ pallet_balances::Config<Balance = u128>
+ pallet_timestamp::Config<Moment = u64> {
type Currency: Mutate<Self::AccountId, Balance = u128>
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
+ InspectFreeze<Self::AccountId, Id = Self::FreezeIdentifier>
+ MutateFreeze<Self::AccountId>
+ Unbalanced<Self::AccountId>
+ ReservableCurrency<Self::AccountId, Balance = u128>
+ LockableCurrency<Self::AccountId, Balance = u128>;
type RcBlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
type WeightInfo: WeightInfo;
}
#[pallet::storage]
pub type RcLeaseReserve<T: Config> = StorageNMap<
_,
(
NMapKey<Twox64Concat, BlockNumberFor<T>>,
NMapKey<Twox64Concat, ParaId>,
NMapKey<Twox64Concat, T::AccountId>,
),
BalanceOf<T>,
OptionQuery,
>;
#[pallet::storage]
pub type RcCrowdloanContribution<T: Config> = StorageNMap<
_,
(
NMapKey<Twox64Concat, BlockNumberFor<T>>,
NMapKey<Twox64Concat, ParaId>,
NMapKey<Twox64Concat, T::AccountId>,
),
(T::AccountId, BalanceOf<T>),
OptionQuery,
>;
#[pallet::storage]
pub type RcCrowdloanReserve<T: Config> = StorageNMap<
_,
(
NMapKey<Twox64Concat, BlockNumberFor<T>>,
NMapKey<Twox64Concat, ParaId>,
NMapKey<Twox64Concat, T::AccountId>,
),
BalanceOf<T>,
OptionQuery,
>;
#[pallet::error]
pub enum Error<T> {
NoLeaseReserve,
NoCrowdloanContribution,
NoCrowdloanReserve,
FailedToWithdrawCrowdloanContribution,
NotYet,
ContributionsRemaining,
WrongSovereignTranslation,
WrongDerivedTranslation,
NotSovereign,
InternalError,
WouldReap,
FailedToPutHold,
FailedToReleaseHold,
FailedToThaw,
FailedToSetFreeze,
FailedToTransfer,
FailedToReserve,
CannotUnreserve,
AccountIdentical,
}
#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
LeaseUnreserveRemaining {
depositor: T::AccountId,
para_id: ParaId,
remaining: BalanceOf<T>,
},
CrowdloanUnreserveRemaining {
depositor: T::AccountId,
para_id: ParaId,
remaining: BalanceOf<T>,
},
SovereignMigrated {
para_id: ParaId,
from: T::AccountId,
to: T::AccountId,
derivation_index: Option<DerivationIndex>,
},
HoldPlaced { account: T::AccountId, amount: BalanceOf<T>, reason: T::RuntimeHoldReason },
HoldReleased { account: T::AccountId, amount: BalanceOf<T>, reason: T::RuntimeHoldReason },
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::unreserve_lease_deposit())]
pub fn unreserve_lease_deposit(
origin: OriginFor<T>,
block: BlockNumberFor<T>,
depositor: Option<T::AccountId>,
para_id: ParaId,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let depositor = depositor.unwrap_or(sender);
Self::do_unreserve_lease_deposit(block, depositor, para_id).map_err(Into::into)
}
#[pallet::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::withdraw_crowdloan_contribution())]
pub fn withdraw_crowdloan_contribution(
origin: OriginFor<T>,
block: BlockNumberFor<T>,
depositor: Option<T::AccountId>,
para_id: ParaId,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let depositor = depositor.unwrap_or(sender);
Self::do_withdraw_crowdloan_contribution(block, depositor, para_id).map_err(Into::into)
}
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::unreserve_crowdloan_reserve())]
pub fn unreserve_crowdloan_reserve(
origin: OriginFor<T>,
block: BlockNumberFor<T>,
depositor: Option<T::AccountId>,
para_id: ParaId,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let depositor = depositor.unwrap_or(sender);
Self::do_unreserve_crowdloan_reserve(block, depositor, para_id).map_err(Into::into)
}
#[pallet::call_index(3)]
#[pallet::weight(T::DbWeight::get().reads_writes(15, 15)
.saturating_add(Weight::from_parts(0, 50_000)))]
pub fn migrate_parachain_sovereign_acc(
origin: OriginFor<T>,
from: T::AccountId,
to: T::AccountId,
) -> DispatchResult {
ensure_root(origin)?;
Self::do_migrate_parachain_sovereign_derived_acc(&from, &to, None).map_err(Into::into)
}
#[pallet::call_index(5)]
#[pallet::weight(T::DbWeight::get().reads_writes(15, 15)
.saturating_add(Weight::from_parts(0, 50_000)))]
pub fn migrate_parachain_sovereign_derived_acc(
origin: OriginFor<T>,
from: T::AccountId,
to: T::AccountId,
derivation: (T::AccountId, DerivationIndex),
) -> DispatchResult {
ensure_root(origin)?;
Self::do_migrate_parachain_sovereign_derived_acc(&from, &to, Some(derivation))
.map_err(Into::into)
}
#[pallet::call_index(4)]
#[pallet::weight(T::DbWeight::get().reads_writes(10, 10)
.saturating_add(Weight::from_parts(0, 50_000)))]
pub fn force_unreserve(
origin: OriginFor<T>,
account: T::AccountId,
amount: BalanceOf<T>,
reason: Option<T::RuntimeHoldReason>,
) -> DispatchResult {
ensure_root(origin)?;
Self::do_force_unreserve(account, amount, reason).map_err(Into::into)
}
}
impl<T: Config> Pallet<T> {
pub fn do_unreserve_lease_deposit(
block: BlockNumberFor<T>,
depositor: T::AccountId,
para_id: ParaId,
) -> Result<(), Error<T>> {
ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::<T>::NotYet);
let balance = RcLeaseReserve::<T>::take((block, para_id, &depositor))
.ok_or(Error::<T>::NoLeaseReserve)?;
let remaining = <T as Config>::Currency::unreserve(&depositor, balance);
if remaining > 0 {
defensive!("Should be able to unreserve all");
Self::deposit_event(Event::LeaseUnreserveRemaining {
depositor,
remaining,
para_id,
});
}
Ok(())
}
pub fn do_withdraw_crowdloan_contribution(
block: BlockNumberFor<T>,
depositor: T::AccountId,
para_id: ParaId,
) -> Result<(), Error<T>> {
ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::<T>::NotYet);
let (pot, contribution) =
RcCrowdloanContribution::<T>::take((block, para_id, &depositor))
.ok_or(Error::<T>::NoCrowdloanContribution)?;
match Self::do_unreserve_lease_deposit(block, pot.clone(), para_id) {
Ok(()) => (),
Err(Error::<T>::NoLeaseReserve) => (), Err(e) => return Err(e),
}
let transferred = <T as Config>::Currency::transfer(
&pot,
&depositor,
contribution,
Preservation::Preserve,
)
.defensive()
.map_err(|_| Error::<T>::FailedToWithdrawCrowdloanContribution)?;
defensive_assert!(transferred == contribution);
<T as Config>::Currency::reactivate(transferred);
Ok(())
}
pub fn do_unreserve_crowdloan_reserve(
block: BlockNumberFor<T>,
depositor: T::AccountId,
para_id: ParaId,
) -> Result<(), Error<T>> {
ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::<T>::NotYet);
ensure!(
Self::contributions_withdrawn(block, para_id),
Error::<T>::ContributionsRemaining
);
let amount = RcCrowdloanReserve::<T>::take((block, para_id, &depositor))
.ok_or(Error::<T>::NoCrowdloanReserve)?;
let remaining = <T as Config>::Currency::unreserve(&depositor, amount);
if remaining > 0 {
defensive!("Should be able to unreserve all");
Self::deposit_event(Event::CrowdloanUnreserveRemaining {
depositor,
remaining,
para_id,
});
}
Ok(())
}
fn contributions_withdrawn(block: BlockNumberFor<T>, para_id: ParaId) -> bool {
let mut contrib_iter = RcCrowdloanContribution::<T>::iter_prefix((block, para_id));
contrib_iter.next().is_none()
}
pub fn do_migrate_parachain_sovereign_derived_acc(
from: &T::AccountId,
to: &T::AccountId,
derivation: Option<(T::AccountId, DerivationIndex)>,
) -> Result<(), Error<T>> {
if frame_system::Account::<T>::get(from) == Default::default() {
return Ok(());
}
if from == to {
return Err(Error::<T>::AccountIdentical);
}
pallet_balances::Pallet::<T>::ensure_upgraded(from);
let (translated_acc, para_id, index) = if let Some((parent, index)) = derivation {
let (parent_translated, para_id) =
Self::try_rc_sovereign_derived_to_ah(from, &parent, index)?;
(parent_translated, para_id, Some(index))
} else {
let (translated_acc, para_id) = Self::try_translate_rc_sovereign_to_ah(from)?;
(translated_acc, para_id, None)
};
ensure!(translated_acc == *to, Error::<T>::WrongSovereignTranslation);
let locks: Vec<BalanceLock<T::Balance>> =
pallet_balances::Locks::<T>::get(from).into_inner();
for lock in &locks {
let () = <T as Config>::Currency::remove_lock(lock.id, from);
}
let freezes: Vec<IdAmount<T::FreezeIdentifier, T::Balance>> =
pallet_balances::Freezes::<T>::get(from).into();
for freeze in &freezes {
let () = <T as Config>::Currency::thaw(&freeze.id, from)
.map_err(|_| Error::<T>::FailedToThaw)?;
}
let holds: Vec<IdAmount<T::RuntimeHoldReason, T::Balance>> =
pallet_balances::Holds::<T>::get(from).into();
for IdAmount { id, amount } in &holds {
let _ = <T as Config>::Currency::release(id, from, *amount, Precision::Exact)
.map_err(|_| Error::<T>::FailedToReleaseHold)?;
Self::deposit_event(Event::HoldReleased {
account: from.clone(),
amount: *amount,
reason: *id,
});
}
let unnamed_reserve = <T as Config>::Currency::reserved_balance(from);
let missing = <T as Config>::Currency::unreserve(from, unnamed_reserve);
defensive_assert!(missing == 0, "Should have unreserved the full amount");
let consumers = frame_system::Pallet::<T>::consumers(from);
frame_system::Account::<T>::mutate(from, |acc| {
acc.consumers = 0;
});
ensure!(frame_system::Pallet::<T>::sufficients(from) == 0, Error::<T>::InternalError);
let total = <T as Config>::Currency::total_balance(from);
let reducible = <T as Config>::Currency::reducible_balance(
from,
Preservation::Expendable,
Fortitude::Polite,
);
defensive_assert!(
total >= <T as Config>::Currency::minimum_balance(),
"Must have at least ED"
);
defensive_assert!(total == reducible, "Total balance should be reducible");
<T as Config>::Currency::transfer(from, to, total, Preservation::Expendable)
.defensive()
.map_err(|_| Error::<T>::FailedToTransfer)?;
frame_system::Account::<T>::mutate(to, |acc| {
acc.consumers += consumers;
});
for hold in &holds {
<T as Config>::Currency::hold(&hold.id, to, hold.amount)
.map_err(|_| Error::<T>::FailedToPutHold)?;
Self::deposit_event(Event::HoldPlaced {
account: to.clone(),
amount: hold.amount,
reason: hold.id,
});
}
<T as Config>::Currency::reserve(to, unnamed_reserve)
.defensive()
.map_err(|_| Error::<T>::FailedToReserve)?;
for lock in &locks {
let reasons = map_lock_reason(lock.reasons);
<T as Config>::Currency::set_lock(lock.id, to, lock.amount, reasons);
}
for freeze in &freezes {
<T as Config>::Currency::set_freeze(&freeze.id, to, freeze.amount)
.map_err(|_| Error::<T>::FailedToSetFreeze)?;
}
defensive_assert!(
frame_system::Account::<T>::get(from) == Default::default(),
"Must reap old account"
);
ensure!(
frame_system::Account::<T>::get(to) != Default::default(),
Error::<T>::WouldReap
);
Self::deposit_event(Event::SovereignMigrated {
para_id,
from: from.clone(),
to: to.clone(),
derivation_index: index,
});
Ok(())
}
pub fn do_force_unreserve(
account: T::AccountId,
amount: BalanceOf<T>,
reason: Option<T::RuntimeHoldReason>,
) -> Result<(), Error<T>> {
if let Some(reason) = reason {
<T as Config>::Currency::release(&reason, &account, amount, Precision::Exact)
.map_err(|_| Error::<T>::FailedToReleaseHold)?;
Self::deposit_event(Event::HoldReleased {
account: account.clone(),
amount,
reason,
});
} else {
let remaining = <T as Config>::Currency::unreserve(&account, amount);
if remaining > 0 {
return Err(Error::<T>::CannotUnreserve);
}
}
Ok(())
}
pub fn try_translate_rc_sovereign_to_ah(
from: &AccountId32,
) -> Result<(AccountId32, ParaId), Error<T>> {
let raw = from.to_raw_vec();
let Some(raw) = raw.strip_prefix(b"para") else {
return Err(Error::<T>::NotSovereign);
};
let Some(raw) = raw.strip_suffix(&[0u8; 26]) else {
return Err(Error::<T>::NotSovereign);
};
let para_id = u16::decode_all(&mut &raw[..]).map_err(|_| Error::<T>::InternalError)?;
let mut ah_raw = [0u8; 32];
ah_raw[0..4].copy_from_slice(b"sibl");
ah_raw[4..6].copy_from_slice(¶_id.encode());
Ok((ah_raw.into(), para_id))
}
pub fn try_rc_sovereign_derived_to_ah(
from: &AccountId32,
parent: &AccountId32,
index: DerivationIndex,
) -> Result<(AccountId32, ParaId), Error<T>> {
{
let derived = pallet_utility::derivative_account_id(parent.clone(), index);
ensure!(derived == *from, Error::<T>::WrongDerivedTranslation);
}
let (parent_translated, para_id) = Self::try_translate_rc_sovereign_to_ah(parent)?;
let parent_translated_derived =
pallet_utility::derivative_account_id(parent_translated, index);
Ok((parent_translated_derived, para_id))
}
}
}
pub fn map_lock_reason(reasons: LockReasons) -> LockWithdrawReasons {
match reasons {
LockReasons::All => LockWithdrawReasons::TRANSACTION_PAYMENT | LockWithdrawReasons::RESERVE,
LockReasons::Fee => LockWithdrawReasons::TRANSACTION_PAYMENT,
LockReasons::Misc => LockWithdrawReasons::TIP,
}
}