Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
  • Loading branch information
ggwpez committed Nov 6, 2024
1 parent 4f46baf commit f9301be
Show file tree
Hide file tree
Showing 7 changed files with 516 additions and 25 deletions.
39 changes: 29 additions & 10 deletions substrate/frame/balances/src/impl_currency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,33 @@ where
}
}

impl<T: Config<I>, I: 'static> Pallet<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug
{
pub(crate) fn set_lock_with_reason(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: Reasons,
) {
if amount.is_zero() {
Self::remove_lock(id, who);
return
}

let mut new_lock = Some(BalanceLock { id, amount, reasons });
let mut locks = Self::locks(who)
.into_iter()
.filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) })
.collect::<Vec<_>>();
if let Some(lock) = new_lock {
locks.push(lock)
}
Self::update_locks(who, &locks[..]);
}
}

impl<T: Config<I>, I: 'static> LockableCurrency<T::AccountId> for Pallet<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug,
Expand All @@ -864,20 +891,12 @@ where
amount: T::Balance,
reasons: WithdrawReasons,
) {
if reasons.is_empty() || amount.is_zero() {
if reasons.is_empty() {
Self::remove_lock(id, who);
return
}

let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
let mut locks = Self::locks(who)
.into_iter()
.filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) })
.collect::<Vec<_>>();
if let Some(lock) = new_lock {
locks.push(lock)
}
Self::update_locks(who, &locks[..]);
Self::set_lock_with_reason(id, who, amount, reasons.into())
}

// Extend a lock on the balance of `who`.
Expand Down
222 changes: 217 additions & 5 deletions substrate/frame/balances/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,16 @@ use frame_support::{
},
BoundedSlice, WeakBoundedVec,
};
use frame_support::traits::TryDrop;
use frame_support::traits::Imbalance;
use frame_support::traits::ExistenceRequirement;
use frame_support::weights::constants::WEIGHT_REF_TIME_PER_MILLIS;
use frame_support::traits::WithdrawReasons;
use frame_support::traits::LockIdentifier;
use frame_support::traits::NamedReservableCurrency;
use frame_system as system;
pub use impl_currency::{NegativeImbalance, PositiveImbalance};
use frame_support::traits::LockableCurrency;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
Expand Down Expand Up @@ -379,6 +386,12 @@ pub mod pallet {
// AHM events
MigratedEdOut { who: T::AccountId, amount: T::Balance },
MigratedEdIn { who : T::AccountId, amount: T::Balance },

MigratedOutLock { who: T::AccountId, id: LockIdentifier },
MigratedInLock { who: T::AccountId, id: LockIdentifier },

MigratedOutReserve { who: T::AccountId, id: Option<T::ReserveIdentifier> },
MigratedInReserve { who: T::AccountId, id: Option<T::ReserveIdentifier> },
}

#[pallet::error]
Expand Down Expand Up @@ -407,6 +420,16 @@ pub mod pallet {
IssuanceDeactivated,
/// The delta cannot be zero.
DeltaZero,

/// There are no locks on this account at all.
AhmNoLocks,
AhmNoLock,
AhmNoReserves,
AhmNoReserve,
AhmNoAccount,
AhmCannotBurn,
AhmCannotBurnOffset,
AhmCannotMintOffset,
}

/// The total units issued in the system.
Expand All @@ -426,6 +449,24 @@ pub mod pallet {
#[pallet::storage]
pub type SufficientAccounts<T: Config<I>, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, ()>;

/// The amount that was burned during the AH migration.
///
/// `Relay::MigrationMintedAmount - Relay::MigrationBurnedAmount` should be identical to
/// `Ah::MigrationMintedAmount - Ah::MigrationBurnedAmount` after the migration and ideally be
/// zero.
/// Not that generally, the Relay burns while the AH mints. There are some cases where changes
/// cannot be applied on AH and AH rejects some mints. In this case, we would mint them again on
/// the Relay.
/// This is equivalent to a `NegativeImbalance`.
#[pallet::storage]
pub type MigrationBurnedAmount<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>;

/// The amount that was minted during the AH migration.
///
/// This is equivalent to a `PositiveImbalance`.
#[pallet::storage]
pub type MigrationMintedAmount<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>;

/// The Balances pallet example of storing the balance of an account.
///
/// # Example
Expand Down Expand Up @@ -845,15 +886,148 @@ pub mod pallet {

Ok(())
}
}

pub enum MigrationError {
NoLock,
#[pallet::call_index(12)]
#[pallet::weight(T::WeightInfo::burn_keep_alive())] // TODO
pub fn migrate_in_locks(
origin: OriginFor<T>,
locks: Vec<(LockIdentifier, T::AccountId, T::Balance, Reasons)>,
) -> DispatchResult {
// TODO ensure origin

for (id, who, amount, reason) in locks {
Self::set_lock_with_reason(id, &who, amount, reason);
Self::deposit_event(Event::MigratedInLock { who, id });
}

Ok(())
}

#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::burn_keep_alive())] // TODO
pub fn migrate_in_reserves(
origin: OriginFor<T>,
reserves: Vec<(T::AccountId, T::Balance, Option<T::ReserveIdentifier>)>,
) -> DispatchResult {
// TODO ensure origin

for (who, amount, id) in reserves {
Self::mint_into(who.clone(), amount)?;

if let Some(id) = id {
Self::reserve_named(&id, &who, amount);
} else {
Self::reserve(&who, amount);
}
Self::deposit_event(Event::MigratedInReserve { who, id });
}

Ok(())
}
}

impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn migrate_lock(who: T::AccountId, amount: BalanceOf<T>) -> Result<Call<T>, MigrationError> {
Ok(())
/// Must be run in a transactional context that reverts all changes upon error.
pub fn migrate_out_named_reserve(id: T::ReserveIdentifier, who: T::AccountId, amount: T::Balance) -> Result<Call<T, I>, DispatchError> {
Self::ensure_sufficient(&who);
let remainder = Self::unreserve_named(&id, &who, amount);
if !remainder.is_zero() {
defensive!("Could not unreserve full amount");
}
let unreserved = amount.saturating_sub(remainder);
Self::deposit_event(Event::MigratedOutReserve { who: who.clone(), id: Some(id) });

Self::burn_from(who.clone(), unreserved)?;

let call = Call::<T, I>::migrate_in_reserves {
// We only migrate as much as we could unreserve.
reserves: vec![(who.clone(), amount, Some(id))], // FAIL-CI
};

Ok(call)
}

/// Must be run in a transactional context that reverts all changes upon error.
pub fn migrate_out_anon_reserve(id: T::ReserveIdentifier, who: T::AccountId, amount: T::Balance) -> Result<Call<T, I>, DispatchError> {
Self::ensure_sufficient(&who);
let remainder = Self::uneserve(&who, amount);
if !remainder.is_zero() {
defensive!("Could not unreserve full amount");
}
let unreserved = amount.saturating_sub(remainder);
Self::deposit_event(Event::MigratedOutReserve { who: who.clone(), id: Some(id) });

Self::burn_from(who.clone(), unreserved)?;

let call = Call::<T, I>::migrate_in_reserves {
// We only migrate as much as we could unreserve.
reserves: vec![(who.clone(), amount, Some(id))], // FAIL-CI
};

Ok(call)
}

/// Burn some balance from an account and from the total issuance.
///
/// This function must run in transactional context and revert any storage changes upon err.
fn burn_from(who: T::AccountId, amount: T::Balance) -> Result<T::Balance, DispatchError> {
if amount.is_zero() {
return Ok(Zero::zero());
}
Self::ensure_sufficient(&who);
// We have to use `ExistenceRequirement` here, since the balance pallet is quite coarse
// in its check of whether it would kill the account. It misses quiet a few cases where
// the assumption is that the account would be killed, while it would not.
let mut credit = <Self as Currency<_>>::withdraw(
&who, amount, WithdrawReasons::TRANSFER, ExistenceRequirement::AllowDeath)?;
let withdrawn = credit.peek();
MigrationBurnedAmount::<T, I>::mutate(|total| total.saturating_accrue(withdrawn));

// Adjust the TI, since this balance will dissapear from here.
let burned = <Self as Currency<_>>::burn(withdrawn);
let rest = credit.offset(burned);

if let Err(e) = rest.try_drop() {
defensive!("Should be able to burn the withdrawn amount");
return Err(Error::<T, I>::AhmCannotBurnOffset.into());
}

Ok(withdrawn)
}

/// Must be run in a transactional context that reverts all changes upon error.
fn mint_into(who: T::AccountId, amount: T::Balance) -> Result<T::Balance, DispatchError> {
if amount.is_zero() {
return Ok(Zero::zero());
}

// Try to resolve the positive imbalance by crediting the account
let credit = <Self as Currency<_>>::deposit_creating(&who, amount);
let issued = credit.peek();
MigrationMintedAmount::<T, I>::mutate(|total| total.saturating_accrue(issued));

let minted = <Self as Currency<_>>::issue(issued);
let imbalance = credit.offset(minted);

if let Err(e) = imbalance.try_drop() {
defensive!("Should be able to mint the deposited amount");
return Err(Error::<T, I>::AhmCannotMintOffset.into());
}

Ok(amount)
}

fn ensure_sufficient(who: &T::AccountId) {
if SufficientAccounts::<T, I>::contains_key(who) {
if frame_system::Pallet::<T>::sufficients(who) < 1 {
defensive!("SufficientAccounts contains key but sufficients is 0");
frame_system::Pallet::<T>::inc_sufficients(who);
}

} else {
SufficientAccounts::<T, I>::insert(who, ());
frame_system::Pallet::<T>::inc_sufficients(who);
}
}

/// Migrates the untouchable balance. This endowes the account on the AH if it does not exist yet.
Expand Down Expand Up @@ -1369,4 +1543,42 @@ pub mod pallet {
Ok(actual)
}
}

/// Minimal config trait to migrate named reserved *without* pulling in the whole pallet config.
///
/// This is used by the AHM controller pallet and selectively injected into other pallets that need to migrate named reserves. These other pallets cannot depend on pallet-balances directly - since they only have a `Currency` trait in their config and calling into `pallet_balances` would require them to also posess a `pallet_balances::Config`.
pub trait AhmNamedReserveConfig {
type System: frame_system::Config;
type ReserveIdentifier: Parameter + Member + AtLeast32BitUnsigned + Default + Copy;
type Balance: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ MaxEncodedLen
+ TypeInfo
+ FixedPointOperand;
}

// TODO replace with enum
pub type EncodedPalletBalancesCall = Vec<u8>;

pub trait AhMigrator<T: AhmNamedReserveConfig> {
fn migrate_out_named_reserve(id: T::ReserveIdentifier, who: <T::System as frame_system::Config>::AccountId, amount: T::Balance) -> Result<EncodedPalletBalancesCall, DispatchError>;
}

impl<T: Config<I>, I: 'static, S: AhmNamedReserveConfig> AhMigrator<S> for Pallet<T, I>
where
T::AccountId: From<<S::System as frame_system::Config>::AccountId>,
T::Balance: From<S::Balance>,
T::ReserveIdentifier: From<S::ReserveIdentifier>,
{
fn migrate_out_named_reserve(id: S::ReserveIdentifier, who: <S::System as frame_system::Config>::AccountId, amount: S::Balance) -> Result<EncodedPalletBalancesCall, DispatchError> {
let call = Self::migrate_out_named_reserve(id.into(), who.into(), amount.into())?;
Ok(call.encode())
}
}
}
Loading

0 comments on commit f9301be

Please sign in to comment.