Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support shared hold id to have constant number of hold id #3013

Merged
merged 8 commits into from
Sep 25, 2024
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 @@ -545,6 +545,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
157 changes: 95 additions & 62 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 @@ -660,7 +663,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 @@ -997,28 +1011,40 @@ pub(crate) fn do_unlock_funds<T: Config>(
.checked_sub(&amount_to_unlock)
.ok_or(Error::BalanceUnderflow)?;

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) = amount_to_unlock.checked_sub(&locked_amount) {
// mint any gains
mint_funds::<T>(&nominator_id, amount_to_mint)?;
locked_amount
} else {
amount_to_unlock
}
};
withdrawal.total_storage_fee_withdrawal = withdrawal
.total_storage_fee_withdrawal
.checked_sub(&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 = amount_to_unlock.min(*deposit_on_hold);
let amount_to_mint = amount_to_unlock.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 @@ -1027,7 +1053,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 @@ -1097,39 +1123,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 @@ -1152,20 +1177,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 @@ -1184,22 +1210,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 @@ -1464,6 +1495,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 @@ -1860,6 +1892,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
Loading