@@ -22,20 +22,21 @@ pub mod weights;
2222pub use weights:: WeightInfo ;
2323
2424use alloc:: vec:: Vec ;
25+ use frame_support:: traits:: tokens:: { Fortitude , Restriction } ;
2526use frame_support:: { pallet_prelude:: * , traits:: schedule:: DispatchTime } ;
2627use frame_system:: pallet_prelude:: * ;
2728use sp_runtime:: traits:: StaticLookup ;
2829
2930/// How to delay transactions
30- /// - `Explicit`: Only delay transactions explicitly using `schedule_dispatch `.
31+ /// - `Explicit`: Only delay transactions explicitly using `schedule_transfer `.
3132/// - `Intercept`: Intercept and delay transactions at the `TransactionExtension` level.
3233///
3334/// For example, for a reversible account with `DelayPolicy::Intercept`, the transaction
34- /// will be delayed even if the user doesn't explicitly call `schedule_dispatch `. And for `DelayPolicy::Explicit`,
35- /// the transaction will be delayed only if the user explicitly calls this pallet's `schedule_dispatch ` extrinsic.
35+ /// will be delayed even if the user doesn't explicitly call `schedule_transfer `. And for `DelayPolicy::Explicit`,
36+ /// the transaction will be delayed only if the user explicitly calls this pallet's `schedule_transfer ` extrinsic.
3637#[ derive( Encode , Decode , MaxEncodedLen , Clone , Default , TypeInfo , Debug , PartialEq , Eq ) ]
3738pub enum DelayPolicy {
38- /// Only explicitly delay transactions using `schedule_dispatch ` call
39+ /// Only explicitly delay transactions using `schedule_transfer ` call
3940 #[ default]
4041 Explicit ,
4142 /// Intercept and delay transactions at `TransactionExtension` level. This is not UX friendly
@@ -46,6 +47,17 @@ pub enum DelayPolicy {
4647 Intercept ,
4748}
4849
50+ /// Reversible account details
51+ #[ derive( Encode , Decode , MaxEncodedLen , Clone , Default , TypeInfo , Debug , PartialEq , Eq ) ]
52+ pub struct ReversibleAccountData < AccountId , BlockNumber > {
53+ /// The account that can reverse the transaction. `None` means the account itself.
54+ pub explicit_reverser : Option < AccountId > ,
55+ /// The delay period for the account
56+ pub delay : BlockNumber ,
57+ /// The policy for the account
58+ pub policy : DelayPolicy ,
59+ }
60+
4961/// Pending transfer details
5062#[ derive( Encode , Decode , MaxEncodedLen , Clone , Default , TypeInfo , Debug , PartialEq , Eq ) ]
5163pub struct PendingTransfer < AccountId , Balance , Call > {
@@ -142,7 +154,7 @@ pub mod pallet {
142154 _ ,
143155 Blake2_128Concat ,
144156 T :: AccountId ,
145- ( BlockNumberFor < T > , DelayPolicy ) ,
157+ ReversibleAccountData < T :: AccountId , BlockNumberFor < T > > ,
146158 OptionQuery ,
147159 > ;
148160
@@ -172,8 +184,7 @@ pub mod pallet {
172184 /// [who, maybe_delay: None means disabled]
173185 ReversibilitySet {
174186 who : T :: AccountId ,
175- delay : BlockNumberFor < T > ,
176- policy : DelayPolicy ,
187+ data : ReversibleAccountData < T :: AccountId , BlockNumberFor < T > > ,
177188 } ,
178189 /// A transaction has been intercepted and scheduled for delayed execution.
179190 /// [who, tx_id, execute_at_moment]
@@ -199,6 +210,8 @@ pub mod pallet {
199210 AccountAlreadyReversible ,
200211 /// The account attempting the action is not marked as reversible.
201212 AccountNotReversible ,
213+ /// Reverser can not be the account itself, because it is redundant.
214+ ExplicitReverserCanNotBeSelf ,
202215 /// The specified pending transaction ID was not found.
203216 PendingTxNotFound ,
204217 /// The caller is not the original submitter of the transaction they are trying to cancel.
@@ -217,6 +230,8 @@ pub mod pallet {
217230 InvalidCall ,
218231 /// Invalid scheduler origin
219232 InvalidSchedulerOrigin ,
233+ /// Reverser is invalid
234+ InvalidReverser ,
220235 }
221236
222237 #[ pallet:: call]
@@ -235,19 +250,34 @@ pub mod pallet {
235250 origin : OriginFor < T > ,
236251 delay : Option < BlockNumberFor < T > > ,
237252 policy : DelayPolicy ,
253+ reverser : Option < T :: AccountId > ,
238254 ) -> DispatchResult {
239255 let who = ensure_signed ( origin) ?;
240256
241257 ensure ! (
242258 !ReversibleAccounts :: <T >:: contains_key( & who) ,
243259 Error :: <T >:: AccountAlreadyReversible
244260 ) ;
261+ ensure ! (
262+ reverser != Some ( who. clone( ) ) ,
263+ Error :: <T >:: ExplicitReverserCanNotBeSelf
264+ ) ;
265+
245266 let delay = delay. unwrap_or ( T :: DefaultDelay :: get ( ) ) ;
246267
247268 ensure ! ( delay >= T :: MinDelayPeriod :: get( ) , Error :: <T >:: DelayTooShort ) ;
248269
249- ReversibleAccounts :: < T > :: insert ( & who, ( delay, policy. clone ( ) ) ) ;
250- Self :: deposit_event ( Event :: ReversibilitySet { who, delay, policy } ) ;
270+ let reversible_account_data = ReversibleAccountData {
271+ explicit_reverser : reverser,
272+ delay,
273+ policy : policy. clone ( ) ,
274+ } ;
275+
276+ ReversibleAccounts :: < T > :: insert ( & who, reversible_account_data. clone ( ) ) ;
277+ Self :: deposit_event ( Event :: ReversibilitySet {
278+ who,
279+ data : reversible_account_data,
280+ } ) ;
251281
252282 Ok ( ( ) )
253283 }
@@ -320,7 +350,9 @@ pub mod pallet {
320350 T : pallet_balances:: Config < RuntimeHoldReason = <T as Config >:: RuntimeHoldReason > ,
321351 {
322352 /// Check if an account has reversibility enabled and return its delay.
323- pub fn is_reversible ( who : & T :: AccountId ) -> Option < ( BlockNumberFor < T > , DelayPolicy ) > {
353+ pub fn is_reversible (
354+ who : & T :: AccountId ,
355+ ) -> Option < ReversibleAccountData < T :: AccountId , BlockNumberFor < T > > > {
324356 ReversibleAccounts :: < T > :: get ( who)
325357 }
326358
@@ -398,8 +430,18 @@ pub mod pallet {
398430 amount : BalanceOf < T > ,
399431 ) -> DispatchResult {
400432 let who = ensure_signed ( origin) ?;
401- let ( delay, _) =
402- Self :: reversible_accounts ( & who) . ok_or ( Error :: < T > :: AccountNotReversible ) ?;
433+ let ReversibleAccountData {
434+ delay,
435+ explicit_reverser,
436+ policy : _,
437+ } = Self :: reversible_accounts ( & who) . ok_or ( Error :: < T > :: AccountNotReversible ) ?;
438+
439+ match explicit_reverser {
440+ Some ( reverser) => {
441+ ensure ! ( who != reverser, Error :: <T >:: InvalidReverser ) ;
442+ }
443+ None => { }
444+ } ;
403445
404446 let transfer_call: T :: RuntimeCall = pallet_balances:: Call :: < T > :: transfer_keep_alive {
405447 dest : dest. clone ( ) ,
@@ -480,23 +522,33 @@ pub mod pallet {
480522 /// Cancels a previously scheduled transaction. Internal logic used by `cancel` extrinsic.
481523 fn cancel_transfer ( who : & T :: AccountId , tx_id : T :: Hash ) -> DispatchResult {
482524 // Retrieve owner from storage to verify ownership
483- let pending =
484- PendingTransfers :: < T > :: get ( tx_id) . ok_or ( Error :: < T > :: PendingTxNotFound ) ?;
525+ let pending = PendingTransfers :: < T > :: get ( tx_id) . ok_or ( Error :: < T > :: PendingTxNotFound ) ?;
485526
486- ensure ! ( & pending. who == who, Error :: <T >:: NotOwner ) ;
527+ let reversible_account_data = ReversibleAccounts :: < T > :: get ( & pending. who )
528+ . ok_or ( Error :: < T > :: AccountNotReversible ) ?;
487529
488- // Remove from main storage
489- PendingTransfers :: < T > :: mutate ( tx_id, |pending_opt| {
490- if let Some ( pending) = pending_opt {
491- if pending. count > 1 {
492- // If there are more than one identical transactions, decrement the count
493- pending. count = pending. count . saturating_sub ( 1 ) ;
494- } else {
495- // Otherwise, remove the transaction from storage
496- * pending_opt = None ;
497- }
498- }
499- } ) ;
530+ if let Some ( explicit_reverser) = & reversible_account_data. explicit_reverser {
531+ // If the reverser is set, ensure the caller is the reverser
532+ ensure ! ( who == explicit_reverser, Error :: <T >:: InvalidReverser ) ;
533+ } else {
534+ ensure ! ( & pending. who == who, Error :: <T >:: NotOwner ) ;
535+ }
536+
537+ if pending. count > 1 {
538+ // If there are more than one identical transactions, decrement the count
539+ PendingTransfers :: < T > :: insert (
540+ & tx_id,
541+ PendingTransfer {
542+ who : pending. who . clone ( ) ,
543+ call : pending. call ,
544+ amount : pending. amount ,
545+ count : pending. count . saturating_sub ( 1 ) ,
546+ } ,
547+ ) ;
548+ } else {
549+ // Otherwise, remove the transaction from storage
550+ PendingTransfers :: < T > :: remove ( & tx_id) ;
551+ }
500552
501553 // Decrement account index
502554 AccountPendingIndex :: < T > :: mutate ( & pending. who , |current_count| {
@@ -509,14 +561,25 @@ pub mod pallet {
509561 // Cancel the scheduled task
510562 T :: Scheduler :: cancel_named ( schedule_id) . map_err ( |_| Error :: < T > :: CancellationFailed ) ?;
511563
512- // Release the funds
513- pallet_balances:: Pallet :: < T > :: release (
514- & HoldReason :: ScheduledTransfer . into ( ) ,
515- & pending. who ,
516- pending. amount ,
517- Precision :: Exact ,
518- ) ?;
519-
564+ if let Some ( reverser) = & reversible_account_data. explicit_reverser {
565+ pallet_balances:: Pallet :: < T > :: transfer_on_hold (
566+ & HoldReason :: ScheduledTransfer . into ( ) ,
567+ & pending. who ,
568+ reverser,
569+ pending. amount ,
570+ Precision :: Exact ,
571+ Restriction :: Free ,
572+ Fortitude :: Polite ,
573+ ) ?;
574+ } else {
575+ // Release the funds
576+ pallet_balances:: Pallet :: < T > :: release (
577+ & HoldReason :: ScheduledTransfer . into ( ) ,
578+ & pending. who ,
579+ pending. amount ,
580+ Precision :: Exact ,
581+ ) ?;
582+ }
520583 Self :: deposit_event ( Event :: TransactionCancelled {
521584 who : who. clone ( ) ,
522585 tx_id,
@@ -538,7 +601,14 @@ pub mod pallet {
538601 for ( who, delay) in & self . initial_reversible_accounts {
539602 // Basic validation, ensure delay is reasonable if needed
540603 if * delay >= T :: MinDelayPeriod :: get ( ) {
541- ReversibleAccounts :: < T > :: insert ( who, ( delay, DelayPolicy :: Explicit ) ) ;
604+ ReversibleAccounts :: < T > :: insert (
605+ who,
606+ ReversibleAccountData {
607+ explicit_reverser : None ,
608+ delay : * delay,
609+ policy : DelayPolicy :: Explicit ,
610+ } ,
611+ ) ;
542612 } else {
543613 // Optionally log a warning during genesis build
544614 log:: warn!(
0 commit comments