Skip to content

Commit

Permalink
Merge branch 'main' of github.com:subspace/subspace into withdraw-limit
Browse files Browse the repository at this point in the history
  • Loading branch information
NingLin-P committed Sep 25, 2024
2 parents 0741b12 + 700113c commit 7ef7baf
Show file tree
Hide file tree
Showing 41 changed files with 416 additions and 514 deletions.
24 changes: 23 additions & 1 deletion crates/pallet-domains/src/bundle_storage_fund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub enum Error {
FailToDeposit,
WithdrawAndHold,
BalanceTransfer,
FailToWithdraw,
}

/// The type of system account being created.
Expand Down Expand Up @@ -184,7 +185,7 @@ pub fn withdraw_and_hold<T: Config>(
}

let storage_fund_acc = storage_fund_account::<T>(operator_id);
let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal(operator_id);
let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
T::Currency::transfer_and_hold(
&storage_fund_hold_id,
&storage_fund_acc,
Expand All @@ -197,6 +198,27 @@ pub fn withdraw_and_hold<T: Config>(
.map_err(|_| Error::WithdrawAndHold)
}

/// Transfer the given `withdraw_amount` of balance from the bundle storage fund to the
/// given `dest_account`
pub fn withdraw_to<T: Config>(
operator_id: OperatorId,
dest_account: &T::AccountId,
withdraw_amount: BalanceOf<T>,
) -> Result<BalanceOf<T>, Error> {
if withdraw_amount.is_zero() {
return Ok(Zero::zero());
}

let storage_fund_acc = storage_fund_account::<T>(operator_id);
T::Currency::transfer(
&storage_fund_acc,
dest_account,
withdraw_amount,
Preservation::Expendable,
)
.map_err(|_| Error::FailToWithdraw)
}

/// Return the total balance of the bundle storage fund the given `operator_id`
pub fn total_balance<T: Config>(operator_id: OperatorId) -> BalanceOf<T> {
let storage_fund_acc = storage_fund_account::<T>(operator_id);
Expand Down
10 changes: 7 additions & 3 deletions crates/pallet-domains/src/domain_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ pub struct DomainObject<Number, ReceiptHash, AccountId: Ord, Balance> {
pub domain_config: DomainConfig<AccountId, Balance>,
/// Domain runtime specific information.
pub domain_runtime_info: DomainRuntimeInfo,
/// The amount of balance hold on the domain owner account
pub domain_instantiation_deposit: Balance,
}

pub(crate) fn can_instantiate_domain<T: Config>(
Expand Down Expand Up @@ -205,6 +207,7 @@ pub(crate) fn do_instantiate_domain<T: Config>(
) -> Result<DomainId, Error> {
can_instantiate_domain::<T>(&owner_account_id, &domain_config)?;

let domain_instantiation_deposit = T::DomainInstantiationDeposit::get();
let domain_id = NextDomainId::<T>::get();
let runtime_obj = RuntimeRegistry::<T>::mutate(domain_config.runtime_id, |maybe_runtime_obj| {
let mut runtime_object = maybe_runtime_obj
Expand Down Expand Up @@ -277,17 +280,18 @@ pub(crate) fn do_instantiate_domain<T: Config>(
genesis_receipt_hash,
domain_config,
domain_runtime_info,
domain_instantiation_deposit,
};
DomainRegistry::<T>::insert(domain_id, domain_obj);

let next_domain_id = domain_id.checked_add(&1.into()).ok_or(Error::MaxDomainId)?;
NextDomainId::<T>::set(next_domain_id);

// Lock up fund of the domain instance creator
// Lock up `domain_instantiation_deposit` amount of fund of the domain instance creator
T::Currency::hold(
&T::HoldIdentifier::domain_instantiation_id(domain_id),
&T::HoldIdentifier::domain_instantiation_id(),
&owner_account_id,
T::DomainInstantiationDeposit::get(),
domain_instantiation_deposit,
)
.map_err(|_| Error::BalanceFreeze)?;

Expand Down
11 changes: 8 additions & 3 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ pub(crate) type FungibleHoldId<T> =
pub(crate) type NominatorId<T> = <T as frame_system::Config>::AccountId;

pub trait HoldIdentifier<T: Config> {
fn staking_staked(operator_id: OperatorId) -> FungibleHoldId<T>;
fn domain_instantiation_id(domain_id: DomainId) -> FungibleHoldId<T>;
fn storage_fund_withdrawal(operator_id: OperatorId) -> FungibleHoldId<T>;
fn staking_staked() -> FungibleHoldId<T>;
fn domain_instantiation_id() -> FungibleHoldId<T>;
fn storage_fund_withdrawal() -> FungibleHoldId<T>;
}

pub trait BlockSlot<T: frame_system::Config> {
Expand Down Expand Up @@ -551,6 +551,11 @@ mod pallet {
OptionQuery,
>;

/// The amount of balance the nominator hold for a given operator
#[pallet::storage]
pub(super) type DepositOnHold<T: Config> =
StorageMap<_, Identity, (OperatorId, NominatorId<T>), BalanceOf<T>, ValueQuery>;

/// Tracks the nominator count under given operator.
/// This storage is necessary since CountedStorageNMap does not support prefix key count, so
/// cannot use that storage type for `Nominators` storage.
Expand Down
169 changes: 101 additions & 68 deletions crates/pallet-domains/src/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use crate::pallet::{
};
use crate::staking_epoch::{mint_funds, mint_into_treasury};
use crate::{
BalanceOf, Config, DomainBlockNumberFor, Event, HoldIdentifier, NominatorId,
BalanceOf, Config, DepositOnHold, DomainBlockNumberFor, Event, HoldIdentifier, NominatorId,
OperatorEpochSharePrice, Pallet, ReceiptHashFor, SlashedReason,
};
use codec::{Decode, Encode};
use frame_support::traits::fungible::{Inspect, InspectHold, MutateHold};
use frame_support::traits::fungible::{Inspect, MutateHold};
use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
use frame_support::{ensure, PalletError};
use scale_info::TypeInfo;
Expand Down Expand Up @@ -122,7 +122,10 @@ pub(crate) struct Withdrawal<Balance, Share, DomainBlockNumber> {
/// Total withdrawal amount requested by the nominator that are in unlocking state excluding withdrawal
/// in shares and the storage fee
pub(crate) total_withdrawal_amount: Balance,
/// Total amount of storage fee on withdraw (including withdrawal in shares)
pub(crate) total_storage_fee_withdrawal: Balance,
/// Individual withdrawal amounts with their unlocking block for a given domain
// TODO: fixed number of withdrawal & uhlock all ready withdrawal at once
pub(crate) withdrawals: VecDeque<WithdrawalInBalance<DomainBlockNumber, Balance>>,
/// Withdrawal that was initiated by nominator and not converted to balance due to
/// unfinished domain epoch.
Expand Down Expand Up @@ -658,7 +661,14 @@ pub(crate) fn hold_deposit<T: Config>(
Error::InsufficientBalance
);

let pending_deposit_hold_id = T::HoldIdentifier::staking_staked(operator_id);
DepositOnHold::<T>::try_mutate((operator_id, who), |deposit_on_hold| {
*deposit_on_hold = deposit_on_hold
.checked_add(&amount)
.ok_or(Error::BalanceOverflow)?;
Ok(())
})?;

let pending_deposit_hold_id = T::HoldIdentifier::staking_staked();
T::Currency::hold(&pending_deposit_hold_id, who, amount).map_err(|_| Error::BalanceFreeze)?;

Ok(())
Expand Down Expand Up @@ -952,6 +962,10 @@ pub(crate) fn do_withdraw_stake<T: Config>(
},
};
withdrawal.withdrawal_in_shares = Some(new_withdrawal_in_shares);
withdrawal.total_storage_fee_withdrawal = withdrawal
.total_storage_fee_withdrawal
.checked_add(&withdraw_storage_fee)
.ok_or(Error::BalanceOverflow)?;

*maybe_withdrawal = Some(withdrawal);
Ok(())
Expand Down Expand Up @@ -1001,12 +1015,6 @@ pub(crate) fn do_unlock_funds<T: Config>(
.pop_front()
.expect("Must not empty as checked above; qed");

// deduct the amount unlocked from total
withdrawal.total_withdrawal_amount = withdrawal
.total_withdrawal_amount
.checked_sub(&amount_to_unlock)
.ok_or(Error::BalanceUnderflow)?;

total_unlocked_amount = total_unlocked_amount
.checked_add(&amount_to_unlock)
.ok_or(Error::BalanceOverflow)?;
Expand All @@ -1023,28 +1031,46 @@ pub(crate) fn do_unlock_funds<T: Config>(
Error::UnlockPeriodNotComplete
);

let staked_hold_id = T::HoldIdentifier::staking_staked(operator_id);
let locked_amount = T::Currency::balance_on_hold(&staked_hold_id, &nominator_id);
let amount_to_release: BalanceOf<T> = {
// if the amount to release is more than currently locked,
// mint the diff and release the rest
if let Some(amount_to_mint) = total_unlocked_amount.checked_sub(&locked_amount) {
// mint any gains
mint_funds::<T>(&nominator_id, amount_to_mint)?;
locked_amount
} else {
total_unlocked_amount
}
};
// deduct the amount unlocked from total
withdrawal.total_withdrawal_amount = withdrawal
.total_withdrawal_amount
.checked_sub(&total_unlocked_amount)
.ok_or(Error::BalanceUnderflow)?;

withdrawal.total_storage_fee_withdrawal = withdrawal
.total_storage_fee_withdrawal
.checked_sub(&total_storage_fee_refund)
.ok_or(Error::BalanceUnderflow)?;

// If the amount to release is more than currently locked,
// mint the diff and release the rest
let (amount_to_mint, amount_to_release) = DepositOnHold::<T>::try_mutate(
(operator_id, nominator_id.clone()),
|deposit_on_hold| {
let amount_to_release = total_unlocked_amount.min(*deposit_on_hold);
let amount_to_mint = total_unlocked_amount.saturating_sub(*deposit_on_hold);

*deposit_on_hold = deposit_on_hold.saturating_sub(amount_to_release);

Ok((amount_to_mint, amount_to_release))
},
)?;

// Mint any gains
if !amount_to_mint.is_zero() {
mint_funds::<T>(&nominator_id, amount_to_mint)?;
}
// Release staking fund
T::Currency::release(
&staked_hold_id,
&nominator_id,
amount_to_release,
Precision::Exact,
)
.map_err(|_| Error::RemoveLock)?;
if !amount_to_release.is_zero() {
let staked_hold_id = T::HoldIdentifier::staking_staked();
T::Currency::release(
&staked_hold_id,
&nominator_id,
amount_to_release,
Precision::Exact,
)
.map_err(|_| Error::RemoveLock)?;
}

Pallet::<T>::deposit_event(Event::NominatedStakedUnlocked {
operator_id,
Expand All @@ -1053,7 +1079,7 @@ pub(crate) fn do_unlock_funds<T: Config>(
});

// Release storage fund
let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal(operator_id);
let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
T::Currency::release(
&storage_fund_hold_id,
&nominator_id,
Expand Down Expand Up @@ -1123,39 +1149,38 @@ pub(crate) fn do_unlock_nominator<T: Config>(

let share_price = SharePrice::new::<T>(total_shares, total_stake);

let staked_hold_id = T::HoldIdentifier::staking_staked(operator_id);

let mut total_storage_fee_deposit = operator.total_storage_fee_deposit;
let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::<T>(
operator_id,
total_storage_fee_deposit,
);
let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal(operator_id);
let mut deposit = Deposits::<T>::take(operator_id, nominator_id.clone())
.ok_or(Error::UnknownNominator)?;

// convert any deposits from the previous epoch to shares
do_convert_previous_epoch_deposits::<T>(operator_id, &mut deposit)?;

let current_locked_amount = T::Currency::balance_on_hold(&staked_hold_id, &nominator_id);

// if there are any withdrawals from this operator, account for them
// if the withdrawals has share price noted, then convert them to SSC
// if no share price, then it must be intitated in the epoch before operator de-registered,
// so get the shares as is and include them in the total staked shares.
let (amount_ready_to_withdraw, shares_withdrew_in_current_epoch) =
Withdrawals::<T>::take(operator_id, nominator_id.clone())
.map(|mut withdrawal| {
do_convert_previous_epoch_withdrawal::<T>(operator_id, &mut withdrawal)?;
Ok((
withdrawal.total_withdrawal_amount,
withdrawal
.withdrawal_in_shares
.map(|WithdrawalInShares { shares, .. }| shares)
.unwrap_or_default(),
))
})
.unwrap_or(Ok((Zero::zero(), Zero::zero())))?;
let (
amount_ready_to_withdraw,
total_storage_fee_withdrawal,
shares_withdrew_in_current_epoch,
) = Withdrawals::<T>::take(operator_id, nominator_id.clone())
.map(|mut withdrawal| {
do_convert_previous_epoch_withdrawal::<T>(operator_id, &mut withdrawal)?;
Ok((
withdrawal.total_withdrawal_amount,
withdrawal.total_storage_fee_withdrawal,
withdrawal
.withdrawal_in_shares
.map(|WithdrawalInShares { shares, .. }| shares)
.unwrap_or_default(),
))
})
.unwrap_or(Ok((Zero::zero(), Zero::zero(), Zero::zero())))?;

// include all the known shares and shares that were withdrawn in the current epoch
let nominator_shares = deposit
Expand All @@ -1178,20 +1203,21 @@ pub(crate) fn do_unlock_nominator<T: Config>(
.and_then(|amount| amount.checked_add(&amount_deposited_in_epoch))
.ok_or(Error::BalanceOverflow)?;

let amount_to_mint = total_amount_to_unlock
.checked_sub(&current_locked_amount)
.unwrap_or(Zero::zero());

// remove the lock and mint any gains
mint_funds::<T>(&nominator_id, amount_to_mint)?;

T::Currency::release(
&staked_hold_id,
&nominator_id,
current_locked_amount,
Precision::Exact,
)
.map_err(|_| Error::RemoveLock)?;
// Remove the lock and mint any gains
let current_locked_amount = DepositOnHold::<T>::take((operator_id, nominator_id.clone()));
if let Some(amount_to_mint) = total_amount_to_unlock.checked_sub(&current_locked_amount) {
mint_funds::<T>(&nominator_id, amount_to_mint)?;
}
if !current_locked_amount.is_zero() {
let staked_hold_id = T::HoldIdentifier::staking_staked();
T::Currency::release(
&staked_hold_id,
&nominator_id,
current_locked_amount,
Precision::Exact,
)
.map_err(|_| Error::RemoveLock)?;
}

Pallet::<T>::deposit_event(Event::NominatedStakedUnlocked {
operator_id,
Expand All @@ -1210,22 +1236,27 @@ pub(crate) fn do_unlock_nominator<T: Config>(
.checked_add(&deposit.known.storage_fee_deposit)
.ok_or(Error::BalanceOverflow)?;

bundle_storage_fund::withdraw_and_hold::<T>(
bundle_storage_fund::withdraw_to::<T>(
operator_id,
&nominator_id,
storage_fund_redeem_price.redeem(nominator_total_storage_fee_deposit),
)
.map_err(Error::BundleStorageFund)?;

// Release all storage fee that of the nominator.
let storage_fee_refund =
T::Currency::release_all(&storage_fund_hold_id, &nominator_id, Precision::Exact)
.map_err(|_| Error::RemoveLock)?;
// Release all storage fee on withdraw of the nominator
let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
T::Currency::release(
&storage_fund_hold_id,
&nominator_id,
total_storage_fee_withdrawal,
Precision::Exact,
)
.map_err(|_| Error::RemoveLock)?;

Pallet::<T>::deposit_event(Event::StorageFeeUnlocked {
operator_id,
nominator_id: nominator_id.clone(),
storage_fee: storage_fee_refund,
storage_fee: total_storage_fee_withdrawal,
});

// reduce total storage fee deposit with nominator total fee deposit
Expand Down Expand Up @@ -1490,6 +1521,7 @@ pub(crate) mod tests {
genesis_receipt_hash: Default::default(),
domain_config,
domain_runtime_info: Default::default(),
domain_instantiation_deposit: Default::default(),
};

DomainRegistry::<Test>::insert(domain_id, domain_obj);
Expand Down Expand Up @@ -1886,6 +1918,7 @@ pub(crate) mod tests {
genesis_receipt_hash: Default::default(),
domain_config,
domain_runtime_info: Default::default(),
domain_instantiation_deposit: Default::default(),
};

DomainRegistry::<Test>::insert(new_domain_id, domain_obj);
Expand Down
Loading

0 comments on commit 7ef7baf

Please sign in to comment.