@@ -21,6 +21,7 @@ use sp_runtime::traits::{CheckedAdd, CheckedSub, One, Zero};
2121use sp_runtime:: { Perbill , Percent , Saturating } ;
2222use sp_std:: collections:: btree_map:: BTreeMap ;
2323use sp_std:: collections:: btree_set:: BTreeSet ;
24+ use sp_std:: collections:: vec_deque:: VecDeque ;
2425use sp_std:: iter:: Iterator ;
2526use sp_std:: vec:: IntoIter ;
2627
@@ -48,17 +49,20 @@ impl SharePrice {
4849
4950 /// Converts stake to shares based on the share price
5051 pub ( crate ) fn stake_to_shares < T : Config > ( & self , stake : BalanceOf < T > ) -> T :: Share {
51- self . 0 . mul_floor ( stake) . into ( )
52+ if self . 0 . is_one ( ) {
53+ stake. into ( )
54+ } else {
55+ self . 0 . mul_floor ( stake) . into ( )
56+ }
5257 }
5358
5459 /// Converts shares to stake based on the share price
5560 pub ( crate ) fn shares_to_stake < T : Config > ( & self , shares : T :: Share ) -> BalanceOf < T > {
56- self . 0 . saturating_reciprocal_mul_floor ( shares. into ( ) )
57- }
58-
59- /// Returns true if the share price is one
60- pub ( crate ) fn is_one ( & self ) -> bool {
61- self . 0 . is_one ( )
61+ if self . 0 . is_one ( ) {
62+ shares. into ( )
63+ } else {
64+ self . 0 . saturating_reciprocal_mul_floor ( shares. into ( ) )
65+ }
6266 }
6367}
6468
@@ -92,11 +96,15 @@ pub(crate) struct PendingDeposit<Balance: Copy> {
9296}
9397
9498/// A nominator's withdrawal from a given operator pool.
95- #[ derive( TypeInfo , Debug , Encode , Decode , Clone , PartialEq , Eq ) ]
96- pub ( crate ) struct Withdrawal < Share , DomainBlockNumber > {
97- pub ( crate ) allowed_since_domain_epoch : DomainEpoch ,
98- pub ( crate ) unlock_at_confirmed_domain_block_number : DomainBlockNumber ,
99- pub ( crate ) shares : Share ,
99+ #[ derive( TypeInfo , Debug , Encode , Decode , Clone , PartialEq , Eq , Default ) ]
100+ pub ( crate ) struct Withdrawal < Balance , Share , DomainBlockNumber > {
101+ /// Total withdrawal amount requested by the nominator that are in unlocking state excluding withdrawal in shares.
102+ pub ( crate ) total_withdrawal_amount : Balance ,
103+ /// Individual withdrawal amounts with their unlocking block for a given domain
104+ pub ( crate ) withdrawals : VecDeque < ( DomainId , DomainBlockNumber , Balance ) > ,
105+ /// Withdrawal that was initiated by nominator and not converted to balance due to
106+ /// unfinished domain epoch.
107+ pub ( crate ) withdrawal_in_shares : Option < ( DomainEpoch , DomainBlockNumber , Share ) > ,
100108}
101109
102110#[ derive( TypeInfo , Debug , Encode , Decode , Clone , PartialEq , Eq ) ]
@@ -328,7 +336,7 @@ pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit<T:
328336) -> Result < DepositInfo < BalanceOf < T > > , Error > {
329337 Deposits :: < T > :: try_mutate ( operator_id, nominator_id, |maybe_deposit| {
330338 let mut deposit = maybe_deposit. take ( ) . unwrap_or_default ( ) ;
331- do_convert_previous_epoch_deposits :: < T > ( operator_id, & mut deposit, current_domain_epoch ) ?;
339+ do_convert_previous_epoch_deposits :: < T > ( operator_id, & mut deposit) ?;
332340
333341 // add or create new pending deposit
334342 let ( pending_deposit, deposit_info) = match deposit. pending {
@@ -374,19 +382,14 @@ pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit<T:
374382pub ( crate ) fn do_convert_previous_epoch_deposits < T : Config > (
375383 operator_id : OperatorId ,
376384 deposit : & mut Deposit < T :: Share , BalanceOf < T > > ,
377- current_domain_epoch : DomainEpoch ,
378385) -> Result < ( ) , Error > {
379386 let maybe_pending_deposit = deposit. pending . take ( ) ;
380387 // if it is one of the previous domain epoch, then calculate shares for the epoch and update known deposit
381388 deposit. pending = if let Some ( PendingDeposit {
382389 effective_domain_epoch,
383390 amount,
384- } ) = maybe_pending_deposit && effective_domain_epoch != current_domain_epoch
391+ } ) = maybe_pending_deposit && let Some ( epoch_share_price ) = OperatorEpochSharePrice :: < T > :: get ( operator_id , effective_domain_epoch )
385392 {
386- let epoch_share_price =
387- OperatorEpochSharePrice :: < T > :: get ( operator_id, effective_domain_epoch)
388- . ok_or ( Error :: MissingOperatorEpochSharePrice ) ?;
389-
390393 let new_shares = epoch_share_price. stake_to_shares :: < T > ( amount) ;
391394 deposit. known . shares = deposit
392395 . known
@@ -401,6 +404,32 @@ pub(crate) fn do_convert_previous_epoch_deposits<T: Config>(
401404 Ok ( ( ) )
402405}
403406
407+ /// Converts any epoch withdrawals into balance using the operator epoch share price.
408+ /// If there is no share price available, this will be no-op
409+ pub ( crate ) fn do_convert_previous_epoch_withdrawal < T : Config > (
410+ operator_id : OperatorId ,
411+ withdrawal : & mut Withdrawal < BalanceOf < T > , T :: Share , DomainBlockNumberFor < T > > ,
412+ ) -> Result < ( ) , Error > {
413+ let withdrawal_in_shares = withdrawal. withdrawal_in_shares . take ( ) ;
414+ withdrawal. withdrawal_in_shares =
415+ if let Some ( ( domain_epoch, domain_block_number, shares) ) = withdrawal_in_shares && let Some ( epoch_share_price) = OperatorEpochSharePrice :: < T > :: get ( operator_id, domain_epoch) {
416+ let withdrawal_amount = epoch_share_price. shares_to_stake :: < T > ( shares) ;
417+ withdrawal. total_withdrawal_amount = withdrawal
418+ . total_withdrawal_amount
419+ . checked_add ( & withdrawal_amount)
420+ . ok_or ( Error :: BalanceOverflow ) ?;
421+ let ( domain_id, _) = domain_epoch. deconstruct ( ) ;
422+ withdrawal
423+ . withdrawals
424+ . push_back ( ( domain_id, domain_block_number, withdrawal_amount) ) ;
425+ None
426+ } else {
427+ withdrawal_in_shares
428+ } ;
429+
430+ Ok ( ( ) )
431+ }
432+
404433pub ( crate ) fn do_nominate_operator < T : Config > (
405434 operator_id : OperatorId ,
406435 nominator_id : T :: AccountId ,
@@ -621,7 +650,15 @@ pub(crate) fn do_withdraw_stake<T: Config>(
621650
622651 Deposits :: < T > :: try_mutate ( operator_id, nominator_id. clone ( ) , |maybe_deposit| {
623652 let deposit = maybe_deposit. as_mut ( ) . ok_or ( Error :: InsufficientShares ) ?;
624- do_convert_previous_epoch_deposits :: < T > ( operator_id, deposit, domain_current_epoch) ?;
653+ do_convert_previous_epoch_deposits :: < T > ( operator_id, deposit) ?;
654+ Ok ( ( ) )
655+ } ) ?;
656+
657+ Withdrawals :: < T > :: try_mutate ( operator_id, nominator_id. clone ( ) , |maybe_withdrawal| {
658+ if let Some ( withdrawal) = maybe_withdrawal {
659+ do_convert_previous_epoch_withdrawal :: < T > ( operator_id, withdrawal) ?;
660+ }
661+
625662 Ok ( ( ) )
626663 } ) ?;
627664
@@ -710,21 +747,31 @@ pub(crate) fn do_withdraw_stake<T: Config>(
710747 . checked_add ( & T :: StakeWithdrawalLockingPeriod :: get ( ) )
711748 . ok_or ( Error :: BlockNumberOverflow ) ?;
712749
713- Withdrawals :: < T > :: try_mutate ( operator_id, nominator_id, |maybe_withdrawals| {
714- let mut withdrawals = maybe_withdrawals. take ( ) . unwrap_or_default ( ) ;
715- // if there is an existing withdrawal within the same epoch, then update it instead
716- if let Some ( withdrawal) = withdrawals. back_mut ( ) && withdrawal. allowed_since_domain_epoch == domain_current_epoch {
717- withdrawal. shares = withdrawal. shares . checked_add ( & shares_withdrew) . ok_or ( Error :: ShareOverflow ) ?;
718- withdrawal. unlock_at_confirmed_domain_block_number = unlock_at_confirmed_domain_block_number;
719- } else {
720- withdrawals. push_back ( Withdrawal {
721- allowed_since_domain_epoch : domain_current_epoch,
722- unlock_at_confirmed_domain_block_number,
723- shares : shares_withdrew,
724- } ) ;
725- }
750+ Withdrawals :: < T > :: try_mutate ( operator_id, nominator_id, |maybe_withdrawal| {
751+ let mut withdrawal = maybe_withdrawal. take ( ) . unwrap_or_default ( ) ;
752+ // if this is some, then the withdrawal was initiated in this current epoch due to conversion
753+ // of previous epoch withdrawals from shares to balances above. So just update it instead
754+ let withdrawals_in_shares = withdrawal. withdrawal_in_shares . take ( ) ;
755+ withdrawal. withdrawal_in_shares = Some (
756+ if let Some ( ( domain_epoch, _, shares) ) = withdrawals_in_shares {
757+ let updated_shares = shares
758+ . checked_add ( & shares_withdrew)
759+ . ok_or ( Error :: ShareOverflow ) ?;
760+ (
761+ domain_epoch,
762+ unlock_at_confirmed_domain_block_number,
763+ updated_shares,
764+ )
765+ } else {
766+ (
767+ domain_current_epoch,
768+ unlock_at_confirmed_domain_block_number,
769+ shares_withdrew,
770+ )
771+ } ,
772+ ) ;
726773
727- * maybe_withdrawals = Some ( withdrawals ) ;
774+ * maybe_withdrawal = Some ( withdrawal ) ;
728775 Ok ( ( ) )
729776 } )
730777 } )
@@ -742,35 +789,29 @@ pub(crate) fn do_unlock_funds<T: Config>(
742789 Error :: OperatorNotRegistered
743790 ) ;
744791
745- Withdrawals :: < T > :: try_mutate_exists ( operator_id, nominator_id. clone ( ) , |maybe_withdrawals | {
746- let withdrawals = maybe_withdrawals . as_mut ( ) . ok_or ( Error :: MissingWithdrawal ) ?;
747- let Withdrawal {
748- allowed_since_domain_epoch ,
749- unlock_at_confirmed_domain_block_number ,
750- shares ,
751- } = withdrawals . pop_front ( ) . ok_or ( Error :: MissingWithdrawal ) ?;
792+ Withdrawals :: < T > :: try_mutate_exists ( operator_id, nominator_id. clone ( ) , |maybe_withdrawal | {
793+ let withdrawal = maybe_withdrawal . as_mut ( ) . ok_or ( Error :: MissingWithdrawal ) ?;
794+ do_convert_previous_epoch_withdrawal :: < T > ( operator_id , withdrawal ) ? ;
795+ let ( domain_id , unlock_at_confirmed_domain_block_number , amount_to_unlock ) = withdrawal
796+ . withdrawals
797+ . pop_front ( )
798+ . ok_or ( Error :: MissingWithdrawal ) ?;
752799
753- let ( domain_id, _) = allowed_since_domain_epoch. deconstruct ( ) ;
754800 let latest_confirmed_block_number = LatestConfirmedDomainBlockNumber :: < T > :: get ( domain_id) ;
755801 ensure ! (
756802 unlock_at_confirmed_domain_block_number <= latest_confirmed_block_number,
757803 Error :: UnlockPeriodNotComplete
758804 ) ;
759805
806+ // deduct the amount unlocked from total
807+ withdrawal. total_withdrawal_amount = withdrawal
808+ . total_withdrawal_amount
809+ . checked_sub ( & amount_to_unlock)
810+ . ok_or ( Error :: BalanceUnderflow ) ?;
811+
760812 let staked_hold_id = T :: HoldIdentifier :: staking_staked ( operator_id) ;
761813 let locked_amount = T :: Currency :: balance_on_hold ( & staked_hold_id, & nominator_id) ;
762- let amount_to_unlock: BalanceOf < T > = {
763- let epoch_share_price =
764- OperatorEpochSharePrice :: < T > :: get ( operator_id, allowed_since_domain_epoch)
765- . ok_or ( Error :: MissingOperatorEpochSharePrice ) ?;
766-
767- // if the share price is one, just convert shares to ssc
768- let amount_to_unlock = if epoch_share_price. is_one ( ) {
769- shares. into ( )
770- } else {
771- epoch_share_price. shares_to_stake :: < T > ( shares)
772- } ;
773-
814+ let amount_to_release: BalanceOf < T > = {
774815 // if the amount to release is more than currently locked,
775816 // mint the diff and release the rest
776817 if let Some ( amount_to_mint) = amount_to_unlock. checked_sub ( & locked_amount) {
@@ -785,14 +826,14 @@ pub(crate) fn do_unlock_funds<T: Config>(
785826 T :: Currency :: release (
786827 & staked_hold_id,
787828 & nominator_id,
788- amount_to_unlock ,
829+ amount_to_release ,
789830 Precision :: Exact ,
790831 )
791832 . map_err ( |_| Error :: RemoveLock ) ?;
792833
793834 // if there are no withdrawals, then delete the storage as well
794- if withdrawals. is_empty ( ) {
795- * maybe_withdrawals = None ;
835+ if withdrawal . withdrawals . is_empty ( ) && withdrawal . withdrawal_in_shares . is_none ( ) {
836+ * maybe_withdrawal = None ;
796837 // if there is no deposit or pending deposits, then clean up the deposit state as well
797838 Deposits :: < T > :: mutate_exists ( operator_id, nominator_id, |maybe_deposit| {
798839 if let Some ( deposit) = maybe_deposit && deposit. known . shares . is_zero ( ) && deposit. pending . is_none ( ) {
@@ -805,54 +846,6 @@ pub(crate) fn do_unlock_funds<T: Config>(
805846 } )
806847}
807848
808- /// Converts shares to ssc based on the OPerator epoch share price
809- /// if the share price is not available, then return the shares as is
810- fn convert_shares_to_ssc < T : Config > (
811- operator_id : OperatorId ,
812- domain_epoch : DomainEpoch ,
813- shares : T :: Share ,
814- ) -> ( BalanceOf < T > , T :: Share ) {
815- match OperatorEpochSharePrice :: < T > :: get ( operator_id, domain_epoch) {
816- None => ( Zero :: zero ( ) , shares) ,
817- Some ( epoch_share_price) => {
818- // if the share price is one, just convert shares to ssc
819- (
820- if epoch_share_price. is_one ( ) {
821- shares. into ( )
822- } else {
823- epoch_share_price. shares_to_stake :: < T > ( shares)
824- } ,
825- Zero :: zero ( ) ,
826- )
827- }
828- }
829- }
830- // If there are any withdrawals for previous epoch, then calculate the SSC for those shares
831- // using the share price at which the withdraw was initiated.
832- // If there are any withdrawals during the current epoch, it share price is not calculated yet
833- // so just return the shares as is
834- pub ( crate ) fn calculate_withdraw_share_ssc < T : Config > (
835- operator_id : OperatorId ,
836- nominator_id : NominatorId < T > ,
837- ) -> ( BalanceOf < T > , T :: Share ) {
838- let withdrawals = Withdrawals :: < T > :: take ( operator_id, nominator_id. clone ( ) ) . unwrap_or_default ( ) ;
839- withdrawals. into_iter ( ) . fold (
840- ( BalanceOf :: < T > :: zero ( ) , T :: Share :: zero ( ) ) ,
841- |( total_amount, total_shares) , withdraw| {
842- let ( amount, shares) = convert_shares_to_ssc :: < T > (
843- operator_id,
844- withdraw. allowed_since_domain_epoch ,
845- withdraw. shares ,
846- ) ;
847-
848- (
849- amount. saturating_add ( total_amount) ,
850- shares. saturating_add ( total_shares) ,
851- )
852- } ,
853- )
854- }
855-
856849/// Unlocks an already de-registered operator given unlock wait period is complete.
857850pub ( crate ) fn do_unlock_operator < T : Config > ( operator_id : OperatorId ) -> Result < ( ) , Error > {
858851 Operators :: < T > :: try_mutate_exists ( operator_id, |maybe_operator| {
@@ -884,7 +877,7 @@ pub(crate) fn do_unlock_operator<T: Config>(operator_id: OperatorId) -> Result<(
884877 let staked_hold_id = T :: HoldIdentifier :: staking_staked ( operator_id) ;
885878 Deposits :: < T > :: drain_prefix ( operator_id) . try_for_each ( |( nominator_id, mut deposit) | {
886879 // convert any deposits from the previous epoch to shares
887- do_convert_previous_epoch_deposits :: < T > ( operator_id, & mut deposit, domain_epoch ) ?;
880+ do_convert_previous_epoch_deposits :: < T > ( operator_id, & mut deposit) ?;
888881
889882 let current_locked_amount =
890883 T :: Currency :: balance_on_hold ( & staked_hold_id, & nominator_id) ;
@@ -893,34 +886,37 @@ pub(crate) fn do_unlock_operator<T: Config>(operator_id: OperatorId) -> Result<(
893886 // if the withdrawals has share price noted, then convert them to SSC
894887 // if no share price, then it must be intitated in the epoch when operator was slashed,
895888 // so get the shares as is and include them in the total staked shares.
896- let ( amount_ready_withdraw, shares_withdrew_in_current_epoch) =
897- calculate_withdraw_share_ssc :: < T > ( operator_id, nominator_id. clone ( ) ) ;
889+ let ( amount_ready_to_withdraw, shares_withdrew_in_current_epoch) =
890+ Withdrawals :: < T > :: take ( operator_id, nominator_id. clone ( ) )
891+ . map ( |mut withdrawal| {
892+ do_convert_previous_epoch_withdrawal :: < T > ( operator_id, & mut withdrawal) ?;
893+ Ok ( (
894+ withdrawal. total_withdrawal_amount ,
895+ withdrawal
896+ . withdrawal_in_shares
897+ . map ( |( _, _, shares) | shares)
898+ . unwrap_or_default ( ) ,
899+ ) )
900+ } )
901+ . unwrap_or ( Ok ( ( Zero :: zero ( ) , Zero :: zero ( ) ) ) ) ?;
898902
899903 // include all the known shares and shares that were withdrawn in the current epoch
900- let nominator_shares = if shares_withdrew_in_current_epoch. is_zero ( ) {
901- deposit. known . shares
902- } else {
903- deposit
904- . known
905- . shares
906- . checked_add ( & shares_withdrew_in_current_epoch)
907- . ok_or ( Error :: ShareOverflow ) ?
908- } ;
904+ let nominator_shares = deposit
905+ . known
906+ . shares
907+ . checked_add ( & shares_withdrew_in_current_epoch)
908+ . ok_or ( Error :: ShareOverflow ) ?;
909909
910910 // current staked amount
911- let nominator_staked_amount = if share_price. is_one ( ) {
912- nominator_shares. into ( )
913- } else {
914- share_price. shares_to_stake :: < T > ( nominator_shares)
915- } ;
911+ let nominator_staked_amount = share_price. shares_to_stake :: < T > ( nominator_shares) ;
916912
917913 let amount_deposited_in_previous_epoch = deposit
918914 . pending
919915 . map ( |pending_deposit| pending_deposit. amount )
920916 . unwrap_or_default ( ) ;
921917
922918 let total_amount_to_unlock = nominator_staked_amount
923- . checked_add ( & amount_ready_withdraw )
919+ . checked_add ( & amount_ready_to_withdraw )
924920 . and_then ( |amount| amount. checked_add ( & amount_deposited_in_previous_epoch) )
925921 . ok_or ( Error :: BalanceOverflow ) ?;
926922
0 commit comments