@@ -99,6 +99,10 @@ pub mod pallet {
9999 /// Minimum amount an account has to lock in dApp staking in order to participate.
100100 #[ pallet:: constant]
101101 type MinimumLockedAmount : Get < BalanceOf < Self > > ;
102+
103+ /// Amount of blocks that need to pass before unlocking chunks can be claimed by the owner.
104+ #[ pallet:: constant]
105+ type UnlockingPeriod : Get < BlockNumberFor < Self > > ;
102106 }
103107
104108 #[ pallet:: event]
@@ -130,6 +134,12 @@ pub mod pallet {
130134 account : T :: AccountId ,
131135 amount : BalanceOf < T > ,
132136 } ,
137+ // TODO: do we also add unlocking block info to the event?
138+ /// Account has started the unlocking process for some amount.
139+ Unlocking {
140+ account : T :: AccountId ,
141+ amount : BalanceOf < T > ,
142+ } ,
133143 }
134144
135145 #[ pallet:: error]
@@ -155,6 +165,10 @@ pub mod pallet {
155165 LockedAmountBelowThreshold ,
156166 /// Cannot add additional locked balance chunks due to size limit.
157167 TooManyLockedBalanceChunks ,
168+ /// Cannot add additional unlocking chunks due to size limit
169+ TooManyUnlockingChunks ,
170+ /// Remaining stake prevents entire balance of starting the unlocking process.
171+ RemainingStakePreventsFullUnlock ,
158172 }
159173
160174 /// General information about dApp staking protocol state.
@@ -388,7 +402,7 @@ pub mod pallet {
388402
389403 // Calculate & check amount available for locking
390404 let available_balance =
391- T :: Currency :: free_balance ( & account) . saturating_sub ( ledger. locked_amount ( ) ) ;
405+ T :: Currency :: free_balance ( & account) . saturating_sub ( ledger. active_locked_amount ( ) ) ;
392406 let amount_to_lock = available_balance. min ( amount) ;
393407 ensure ! ( !amount_to_lock. is_zero( ) , Error :: <T >:: ZeroAmount ) ;
394408
@@ -398,13 +412,13 @@ pub mod pallet {
398412 . add_lock_amount ( amount_to_lock, lock_era)
399413 . map_err ( |_| Error :: < T > :: TooManyLockedBalanceChunks ) ?;
400414 ensure ! (
401- ledger. locked_amount ( ) >= T :: MinimumLockedAmount :: get( ) ,
415+ ledger. active_locked_amount ( ) >= T :: MinimumLockedAmount :: get( ) ,
402416 Error :: <T >:: LockedAmountBelowThreshold
403417 ) ;
404418
405419 Self :: update_ledger ( & account, ledger) ;
406420 CurrentEraInfo :: < T > :: mutate ( |era_info| {
407- era_info. total_locked . saturating_accrue ( amount_to_lock) ;
421+ era_info. add_locked ( amount_to_lock) ;
408422 } ) ;
409423
410424 Self :: deposit_event ( Event :: < T > :: Locked {
@@ -414,6 +428,68 @@ pub mod pallet {
414428
415429 Ok ( ( ) )
416430 }
431+
432+ /// Attempts to start the unlocking process for the specified amount.
433+ ///
434+ /// Only the amount that isn't actively used for staking can be unlocked.
435+ /// If the amount is greater than the available amount for unlocking, everything is unlocked.
436+ /// If the remaining locked amount would take the account below the minimum locked amount, everything is unlocked.
437+ #[ pallet:: call_index( 6 ) ]
438+ #[ pallet:: weight( Weight :: zero( ) ) ]
439+ pub fn unlock (
440+ origin : OriginFor < T > ,
441+ #[ pallet:: compact] amount : BalanceOf < T > ,
442+ ) -> DispatchResult {
443+ Self :: ensure_pallet_enabled ( ) ?;
444+ let account = ensure_signed ( origin) ?;
445+
446+ let state = ActiveProtocolState :: < T > :: get ( ) ;
447+ let mut ledger = Ledger :: < T > :: get ( & account) ;
448+
449+ let available_for_unlocking = ledger. unlockable_amount ( state. period ) ;
450+ let amount_to_unlock = available_for_unlocking. min ( amount) ;
451+
452+ // Ensure we unlock everything if remaining amount is below threshold.
453+ let remaining_amount = ledger
454+ . active_locked_amount ( )
455+ . saturating_sub ( amount_to_unlock) ;
456+ let amount_to_unlock = if remaining_amount < T :: MinimumLockedAmount :: get ( ) {
457+ ensure ! (
458+ ledger. active_stake( state. period) . is_zero( ) ,
459+ Error :: <T >:: RemainingStakePreventsFullUnlock
460+ ) ;
461+ ledger. active_locked_amount ( )
462+ } else {
463+ amount_to_unlock
464+ } ;
465+
466+ // Sanity check
467+ ensure ! ( !amount. is_zero( ) , Error :: <T >:: ZeroAmount ) ;
468+
469+ // Update ledger with new lock and unlocking amounts
470+ ledger
471+ . subtract_lock_amount ( amount_to_unlock, state. era )
472+ . map_err ( |_| Error :: < T > :: TooManyLockedBalanceChunks ) ?;
473+
474+ let current_block = frame_system:: Pallet :: < T > :: block_number ( ) ;
475+ let unlock_block = current_block. saturating_add ( T :: UnlockingPeriod :: get ( ) ) ;
476+ ledger
477+ . add_unlocking_chunk ( amount_to_unlock, unlock_block)
478+ . map_err ( |_| Error :: < T > :: TooManyUnlockingChunks ) ?;
479+
480+ // Update storage
481+ Self :: update_ledger ( & account, ledger) ;
482+ CurrentEraInfo :: < T > :: mutate ( |era_info| {
483+ era_info. unlocking_started ( amount_to_unlock) ;
484+ } ) ;
485+
486+ Self :: deposit_event ( Event :: < T > :: Unlocking {
487+ account,
488+ amount : amount_to_unlock,
489+ } ) ;
490+
491+ Ok ( ( ) )
492+ }
417493 }
418494
419495 impl < T : Config > Pallet < T > {
@@ -450,7 +526,7 @@ pub mod pallet {
450526 T :: Currency :: set_lock (
451527 STAKING_ID ,
452528 account,
453- ledger. locked_amount ( ) ,
529+ ledger. active_locked_amount ( ) ,
454530 WithdrawReasons :: all ( ) ,
455531 ) ;
456532 Ledger :: < T > :: insert ( account, ledger) ;
0 commit comments