From 7e7c6bc2d848df332458a96910607f37718d725f Mon Sep 17 00:00:00 2001 From: linning Date: Fri, 30 Aug 2024 04:45:46 +0800 Subject: [PATCH 1/4] Support more different types of withdraw stake Signed-off-by: linning --- crates/pallet-domains/src/benchmarking.rs | 8 +- crates/pallet-domains/src/lib.rs | 7 +- crates/pallet-domains/src/staking.rs | 108 +++++++++++++++------ crates/pallet-domains/src/staking_epoch.rs | 5 +- 4 files changed, 91 insertions(+), 37 deletions(-) diff --git a/crates/pallet-domains/src/benchmarking.rs b/crates/pallet-domains/src/benchmarking.rs index 0bcf0befa4..fb92dd58b1 100644 --- a/crates/pallet-domains/src/benchmarking.rs +++ b/crates/pallet-domains/src/benchmarking.rs @@ -8,7 +8,7 @@ use crate::bundle_storage_fund::refund_storage_fee; use crate::domain_registry::DomainConfig; use crate::staking::{ do_convert_previous_epoch_deposits, do_mark_operators_as_slashed, do_reward_operators, - OperatorConfig, OperatorStatus, + OperatorConfig, OperatorStatus, WithdrawStake, }; use crate::staking_epoch::{ do_finalize_domain_current_epoch, do_finalize_domain_epoch_staking, do_slash_operator, @@ -701,7 +701,7 @@ mod benchmarks { assert_ok!(Domains::::withdraw_stake( RawOrigin::Signed(nominator.clone()).into(), operator_id, - withdraw_amount.into(), + WithdrawStake::Share(withdraw_amount.into()), )); assert_ok!(Domains::::nominate_operator( RawOrigin::Signed(nominator.clone()).into(), @@ -715,7 +715,7 @@ mod benchmarks { _( RawOrigin::Signed(nominator.clone()), operator_id, - withdraw_amount.into(), + WithdrawStake::Share(withdraw_amount.into()), ); let operator = Operators::::get(operator_id).expect("operator must exist"); @@ -752,7 +752,7 @@ mod benchmarks { assert_ok!(Domains::::withdraw_stake( RawOrigin::Signed(nominator.clone()).into(), operator_id, - withdraw_amount, + WithdrawStake::Share(withdraw_amount), )); do_finalize_domain_epoch_staking::(domain_id) .expect("finalize domain staking should success"); diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index b1761eecd2..d112474258 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -197,7 +197,7 @@ mod pallet { do_deregister_operator, do_mark_operators_as_slashed, do_nominate_operator, do_register_operator, do_unlock_funds, do_unlock_nominator, do_withdraw_stake, Deposit, DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice, StakingSummary, - Withdrawal, + WithdrawStake, Withdrawal, }; #[cfg(not(feature = "runtime-benchmarks"))] use crate::staking_epoch::do_slash_operator; @@ -1433,11 +1433,12 @@ mod pallet { pub fn withdraw_stake( origin: OriginFor, operator_id: OperatorId, - shares: T::Share, + to_withdraw: WithdrawStake, T::Share>, ) -> DispatchResult { let who = ensure_signed(origin)?; - do_withdraw_stake::(operator_id, who.clone(), shares).map_err(Error::::from)?; + do_withdraw_stake::(operator_id, who.clone(), to_withdraw) + .map_err(Error::::from)?; Self::deposit_event(Event::WithdrewStake { operator_id, diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index 025c706bf7..8b035938b5 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -267,7 +267,7 @@ pub enum Error { PendingOperatorSwitch, InsufficientBalance, InsufficientShares, - ZeroWithdrawShares, + ZeroWithdraw, BalanceFreeze, MinimumOperatorStake, UnknownOperator, @@ -711,10 +711,62 @@ pub(crate) fn do_deregister_operator( }) } +/// Different type of withdrawal +/// +/// NOTE: the deposit happen in the current epoch may not able to withdraw +// until the current epoch end +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] +pub enum WithdrawStake { + /// Withdraw all stake + All, + /// Withdraw a given percentage of the stake + Percent(Percent), + /// Withdraw a given amount of stake, calculated by the share price at + /// this instant, it may not be accurate and may withdraw a bit more + /// stake if there is reward happen later in this epoch + Stake(Balance), + /// Withdraw a given amount of share + Share(Share), +} + +impl WithdrawStake { + pub fn is_zero(&self) -> bool { + match self { + Self::All => false, + Self::Percent(p) => p.is_zero(), + Self::Stake(s) => s.is_zero(), + Self::Share(s) => s.is_zero(), + } + } +} + +// A helper function used to calculate the share price at this instant +fn current_share_price( + operator_id: OperatorId, + operator: &Operator, T::Share, DomainBlockNumberFor>, + domain_stake_summary: &StakingSummary>, +) -> SharePrice { + // Total stake including any reward within this epoch. + let total_stake = domain_stake_summary + .current_epoch_rewards + .get(&operator_id) + .and_then(|rewards| { + let operator_tax = operator.nomination_tax.mul_floor(*rewards); + operator + .current_total_stake + .checked_add(rewards)? + // deduct operator tax + .checked_sub(&operator_tax) + }) + .unwrap_or(operator.current_total_stake); + + SharePrice::new::(operator.current_total_shares, total_stake) +} + pub(crate) fn do_withdraw_stake( operator_id: OperatorId, nominator_id: NominatorId, - shares_withdrew: T::Share, + to_withdraw: WithdrawStake, T::Share>, ) -> Result<(), Error> { Operators::::try_mutate(operator_id, |maybe_operator| { let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?; @@ -723,7 +775,7 @@ pub(crate) fn do_withdraw_stake( Error::OperatorNotRegistered ); - ensure!(!shares_withdrew.is_zero(), Error::ZeroWithdrawShares); + ensure!(!to_withdraw.is_zero(), Error::ZeroWithdraw); // If the this is the first staking request of this operator `note_pending_staking_operation` for it if operator.deposits_in_epoch.is_zero() && operator.withdrawals_in_epoch.is_zero() { @@ -739,11 +791,12 @@ pub(crate) fn do_withdraw_stake( ) .into(); - Deposits::::try_mutate(operator_id, nominator_id.clone(), |maybe_deposit| { - let deposit = maybe_deposit.as_mut().ok_or(Error::InsufficientShares)?; - do_convert_previous_epoch_deposits::(operator_id, deposit)?; - Ok(()) - })?; + let known_share = + Deposits::::try_mutate(operator_id, nominator_id.clone(), |maybe_deposit| { + let deposit = maybe_deposit.as_mut().ok_or(Error::UnknownNominator)?; + do_convert_previous_epoch_deposits::(operator_id, deposit)?; + Ok(deposit.known.shares) + })?; Withdrawals::::try_mutate(operator_id, nominator_id.clone(), |maybe_withdrawal| { if let Some(withdrawal) = maybe_withdrawal { @@ -758,6 +811,17 @@ pub(crate) fn do_withdraw_stake( let is_operator_owner = operator_owner == nominator_id; + let shares_withdrew = match to_withdraw { + WithdrawStake::All => known_share, + WithdrawStake::Percent(p) => p.mul_floor(known_share), + WithdrawStake::Stake(s) => { + let share_price = + current_share_price::(operator_id, operator, &domain_stake_summary); + share_price.stake_to_shares::(s) + } + WithdrawStake::Share(s) => s, + }; + Deposits::::try_mutate(operator_id, nominator_id.clone(), |maybe_deposit| { let deposit = maybe_deposit.as_mut().ok_or(Error::UnknownNominator)?; let known_shares = deposit.known.shares; @@ -775,23 +839,8 @@ pub(crate) fn do_withdraw_stake( (remaining_shares, shares_withdrew) } else { - // total stake including any reward within this epoch. - // used to calculate the share price at this instant. - let total_stake = domain_stake_summary - .current_epoch_rewards - .get(&operator_id) - .and_then(|rewards| { - let operator_tax = operator.nomination_tax.mul_floor(*rewards); - operator - .current_total_stake - .checked_add(rewards)? - // deduct operator tax - .checked_sub(&operator_tax) - }) - .unwrap_or(operator.current_total_stake); - let share_price = - SharePrice::new::(operator.current_total_shares, total_stake); + current_share_price::(operator_id, operator, &domain_stake_summary); let remaining_storage_fee = Perbill::from_rational(remaining_shares, known_shares) @@ -1349,6 +1398,7 @@ pub(crate) mod tests { do_convert_previous_epoch_withdrawal, do_mark_operators_as_slashed, do_nominate_operator, do_reward_operators, do_unlock_funds, do_withdraw_stake, Error as StakingError, Operator, OperatorConfig, OperatorSigningKeyProofOfOwnershipData, OperatorStatus, StakingSummary, + WithdrawStake, }; use crate::staking_epoch::{do_finalize_domain_current_epoch, do_slash_operator}; use crate::tests::{new_test_ext, ExistentialDeposit, RuntimeOrigin, Test}; @@ -1980,7 +2030,7 @@ pub(crate) mod tests { let res = Domains::withdraw_stake( RuntimeOrigin::signed(nominator_id), operator_id, - withdraw_share_amount, + WithdrawStake::Share(withdraw_share_amount), ); assert_eq!( res, @@ -2447,7 +2497,7 @@ pub(crate) mod tests { nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], operator_reward: Zero::zero(), nominator_id: 1, - withdraws: vec![(0, Err(StakingError::ZeroWithdrawShares))], + withdraws: vec![(0, Err(StakingError::ZeroWithdraw))], maybe_deposit: None, expected_withdraw: None, expected_nominator_count_reduced_by: 0, @@ -2600,7 +2650,8 @@ pub(crate) mod tests { ); for unlock in &unlocking { - do_withdraw_stake::(operator_id, unlock.0, unlock.1).unwrap(); + do_withdraw_stake::(operator_id, unlock.0, WithdrawStake::Share(unlock.1)) + .unwrap(); } do_reward_operators::(domain_id, vec![operator_id].into_iter(), 20 * SSC) @@ -2758,7 +2809,8 @@ pub(crate) mod tests { ); for unlock in &unlocking { - do_withdraw_stake::(operator_id, unlock.0, unlock.1).unwrap(); + do_withdraw_stake::(operator_id, unlock.0, WithdrawStake::Share(unlock.1)) + .unwrap(); } do_reward_operators::(domain_id, vec![operator_id].into_iter(), 20 * SSC) diff --git a/crates/pallet-domains/src/staking_epoch.rs b/crates/pallet-domains/src/staking_epoch.rs index 4010d66fb2..aef345087e 100644 --- a/crates/pallet-domains/src/staking_epoch.rs +++ b/crates/pallet-domains/src/staking_epoch.rs @@ -533,7 +533,7 @@ mod tests { use crate::staking::tests::{register_operator, Share}; use crate::staking::{ do_deregister_operator, do_nominate_operator, do_reward_operators, do_unlock_nominator, - do_withdraw_stake, + do_withdraw_stake, WithdrawStake, }; use crate::staking_epoch::{ do_finalize_domain_current_epoch, operator_take_reward_tax_and_stake, @@ -609,7 +609,8 @@ mod tests { } for (nominator_id, shares) in withdrawals { - do_withdraw_stake::(operator_id, nominator_id, shares).unwrap(); + do_withdraw_stake::(operator_id, nominator_id, WithdrawStake::Share(shares)) + .unwrap(); } if !rewards.is_zero() { From 156abd6de7e4b6a5160917aced99ba2220084a14 Mon Sep 17 00:00:00 2001 From: NingLin-P Date: Wed, 4 Sep 2024 01:16:03 +0800 Subject: [PATCH 2/4] Update crates/pallet-domains/src/staking.rs Co-authored-by: teor --- crates/pallet-domains/src/staking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index 8b035938b5..ed16f9a741 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -713,8 +713,8 @@ pub(crate) fn do_deregister_operator( /// Different type of withdrawal /// -/// NOTE: the deposit happen in the current epoch may not able to withdraw -// until the current epoch end +/// NOTE: if the deposit was made in the current epoch, the user may not be able to withdraw it +// until the current epoch ends #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] pub enum WithdrawStake { /// Withdraw all stake From 528a9591bc1ab60acfa1ba29037a2c2f6eaccba3 Mon Sep 17 00:00:00 2001 From: NingLin-P Date: Wed, 4 Sep 2024 01:16:09 +0800 Subject: [PATCH 3/4] Update crates/pallet-domains/src/staking.rs Co-authored-by: teor --- crates/pallet-domains/src/staking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index ed16f9a741..b1b6942499 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -723,7 +723,7 @@ pub enum WithdrawStake { Percent(Percent), /// Withdraw a given amount of stake, calculated by the share price at /// this instant, it may not be accurate and may withdraw a bit more - /// stake if there is reward happen later in this epoch + /// stake if a reward happens later in this epoch Stake(Balance), /// Withdraw a given amount of share Share(Share), From 13461bdf5da475a0c427671f9e5517f3eacefc49 Mon Sep 17 00:00:00 2001 From: NingLin-P Date: Wed, 4 Sep 2024 20:58:37 +0800 Subject: [PATCH 4/4] Update crates/pallet-domains/src/staking.rs Co-authored-by: Dastan <88332432+dastansam@users.noreply.github.com> --- crates/pallet-domains/src/staking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index b1b6942499..0fea28948c 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -714,7 +714,7 @@ pub(crate) fn do_deregister_operator( /// Different type of withdrawal /// /// NOTE: if the deposit was made in the current epoch, the user may not be able to withdraw it -// until the current epoch ends +/// until the current epoch ends #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] pub enum WithdrawStake { /// Withdraw all stake