diff --git a/pallets/multisig/src/mock.rs b/pallets/multisig/src/mock.rs index 7b018feb..f37229a8 100644 --- a/pallets/multisig/src/mock.rs +++ b/pallets/multisig/src/mock.rs @@ -178,6 +178,7 @@ parameter_types! { pub const MinDelayPeriodMoment: u64 = 2000; pub const MaxReversibleTransfers: u32 = 100; pub const MaxInterceptorAccounts: u32 = 10; + pub const MaxPendingPerAccount: u32 = 16; pub const HighSecurityVolumeFee: Permill = Permill::from_percent(1); } @@ -208,6 +209,7 @@ impl pallet_reversible_transfers::Config for Test { type Moment = Moment; type TimeProvider = MockTimestamp; type MaxInterceptorAccounts = MaxInterceptorAccounts; + type MaxPendingPerAccount = MaxPendingPerAccount; type VolumeFee = HighSecurityVolumeFee; type ProofRecorder = MockProofRecorder; } diff --git a/pallets/reversible-transfers/src/benchmarking.rs b/pallets/reversible-transfers/src/benchmarking.rs index 65d3589d..1db3b179 100644 --- a/pallets/reversible-transfers/src/benchmarking.rs +++ b/pallets/reversible-transfers/src/benchmarking.rs @@ -7,7 +7,7 @@ use frame_benchmarking::{account as benchmark_account, v2::*, BenchmarkError}; use frame_support::traits::{fungible::Mutate, Get}; use frame_system::RawOrigin; use sp_runtime::{ - traits::{BlockNumberProvider, Hash, StaticLookup}, + traits::{BlockNumberProvider, Hash, One, StaticLookup}, Saturating, }; @@ -218,19 +218,42 @@ mod benchmarks { } #[benchmark] - fn recover_funds() -> Result<(), BenchmarkError> { + fn recover_funds(n: Linear<0, 16>) -> Result<(), BenchmarkError> { + assert_eq!( + T::MaxPendingPerAccount::get(), + 16, + "Linear upper bound must match MaxPendingPerAccount" + ); + let account: T::AccountId = whitelisted_caller(); let guardian: T::AccountId = benchmark_account("guardian", 0, SEED); + let recipient: T::AccountId = benchmark_account("recipient", 0, SEED); - fund_account::(&account, BalanceOf::::from(10000u128)); + fund_account::(&account, BalanceOf::::from(1_000_000u128)); fund_account::(&guardian, BalanceOf::::from(10000u128)); let delay = T::DefaultDelay::get(); setup_high_security_account::(account.clone(), delay, guardian.clone()); + let transfer_amount: BalanceOf = 100u128.into(); + for i in 0..n { + if i > 0 && i % 8 == 0 { + let bn = frame_system::Pallet::::block_number(); + frame_system::Pallet::::set_block_number(bn + BlockNumberFor::::one()); + } + let lookup = ::Lookup::unlookup(recipient.clone()); + ReversibleTransfers::::do_schedule_transfer( + RawOrigin::Signed(account.clone()).into(), + lookup, + transfer_amount, + )?; + } + #[extrinsic_call] _(RawOrigin::Signed(guardian.clone()), account.clone()); + assert_eq!(PendingTransfersBySender::::get(&account).len(), 0); + Ok(()) } diff --git a/pallets/reversible-transfers/src/lib.rs b/pallets/reversible-transfers/src/lib.rs index fe4fbd9f..fc26df17 100644 --- a/pallets/reversible-transfers/src/lib.rs +++ b/pallets/reversible-transfers/src/lib.rs @@ -157,6 +157,10 @@ pub mod pallet { #[pallet::constant] type MaxInterceptorAccounts: Get; + /// Maximum pending reversible transactions allowed per account. + #[pallet::constant] + type MaxPendingPerAccount: Get; + /// The default delay period for reversible transactions if none is specified. /// /// NOTE: default delay is always in blocks. @@ -224,6 +228,17 @@ pub mod pallet { pub type PendingTransfers = StorageMap<_, Blake2_128Concat, T::Hash, PendingTransferOf, OptionQuery>; + /// Maps sender accounts to their list of pending transaction IDs. + #[pallet::storage] + #[pallet::getter(fn pending_transfers_by_sender)] + pub type PendingTransfersBySender = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + BoundedVec, + ValueQuery, + >; + /// Maps interceptor accounts to the list of accounts they can intercept for. /// This allows the UI to efficiently query all accounts for which a given account is an /// interceptor. @@ -285,6 +300,8 @@ pub mod pallet { PendingTxNotFound, /// The caller is not the original submitter of the transaction they are trying to cancel. NotOwner, + /// The account has reached the maximum number of pending reversible transactions. + TooManyPendingTransactions, /// The specified delay period is below the configured minimum. DelayTooShort, /// Failed to schedule the transaction execution with the scheduler pallet. @@ -469,8 +486,10 @@ pub mod pallet { /// account by transferring the entire balance to themselves. /// /// This is an emergency function for when the high security account may be compromised. + /// It cancels all pending transfers first (applying volume fees), then transfers + /// the remaining free balance to the guardian. #[pallet::call_index(7)] - #[pallet::weight(::WeightInfo::recover_funds())] + #[pallet::weight(::WeightInfo::recover_funds(T::MaxPendingPerAccount::get()))] #[allow(clippy::useless_conversion)] pub fn recover_funds( origin: OriginFor, @@ -483,17 +502,42 @@ pub mod pallet { ensure!(who == high_security_account_data.interceptor, Error::::InvalidReverser); + let mut num_cancelled: u64 = 0; + + for tx_id in PendingTransfersBySender::::take(&account).iter() { + if let Some(pending) = PendingTransfers::::take(tx_id) { + let schedule_id = Self::make_schedule_id(tx_id).ok(); + if let Some(id) = schedule_id { + let _ = T::Scheduler::cancel_named(id); + } + + if let Err(e) = Self::release_held_funds_with_fee(&pending, &who, true) { + log::warn!( + "Failed to release held funds for tx {:?} during recovery: {:?}", + tx_id, + e + ); + } + + num_cancelled = num_cancelled.saturating_add(1); + Self::deposit_event(Event::TransactionCancelled { + who: who.clone(), + tx_id: *tx_id, + }); + } + } + let call: RuntimeCallOf = pallet_balances::Call::::transfer_all { dest: T::Lookup::unlookup(who.clone()), keep_alive: false, } .into(); - let result = call.dispatch(frame_system::RawOrigin::Signed(account.clone()).into()); + call.dispatch(frame_system::RawOrigin::Signed(account.clone()).into())?; Self::deposit_event(Event::FundsRecovered { account, guardian: who }); - result + Ok(Some(::WeightInfo::recover_funds(num_cancelled as u32)).into()) } } @@ -601,6 +645,11 @@ pub mod pallet { // Remove transfer from storage PendingTransfers::::remove(tx_id); + // Remove from sender's pending list + PendingTransfersBySender::::mutate(&pending.from, |list| { + list.retain(|id| id != tx_id); + }); + let post_info = call.dispatch(frame_system::RawOrigin::Signed(pending.from.clone()).into()); @@ -689,6 +738,11 @@ pub mod pallet { // Store the pending transfer PendingTransfers::::insert(tx_id, new_pending); + // Add to sender's pending list + PendingTransfersBySender::::try_mutate(&from, |list| { + list.try_push(tx_id).map_err(|_| Error::::TooManyPendingTransactions) + })?; + let bounded_call = T::Preimages::bound(Call::::execute_transfer { tx_id }.into())?; // Schedule the `do_execute` call @@ -752,41 +806,50 @@ pub mod pallet { /// Cancels a previously scheduled transaction. Internal logic used by `cancel` extrinsic. fn cancel_transfer(who: &T::AccountId, tx_id: T::Hash) -> DispatchResult { - // Retrieve owner from storage to verify ownership let pending = PendingTransfers::::get(tx_id).ok_or(Error::::PendingTxNotFound)?; - let high_security_account_data = HighSecurityAccounts::::get(&pending.from); - // if high-security account, interceptor is third party, else it is owner - let interceptor = if let Some(ref data) = high_security_account_data { + // Determine recipient and apply fee based on account type + let (recipient, apply_fee) = if let Some(ref data) = high_security_account_data { ensure!(who == &data.interceptor, Error::::InvalidReverser); - data.interceptor.clone() + (data.interceptor.clone(), true) } else { ensure!(who == &pending.from, Error::::NotOwner); - pending.from.clone() + (pending.from.clone(), false) }; - // Remove transfer from storage + // Remove from storage PendingTransfers::::remove(tx_id); + PendingTransfersBySender::::mutate(&pending.from, |list| { + list.retain(|id| *id != tx_id); + }); + // Cancel scheduler (must succeed for normal cancel) let schedule_id = Self::make_schedule_id(&tx_id)?; - - // Cancel the scheduled task T::Scheduler::cancel_named(schedule_id).map_err(|_| Error::::CancellationFailed)?; - // Calculate volume fee only for high-security accounts - let (fee_amount, remaining_amount) = if high_security_account_data.is_some() { + // Release funds (must succeed for normal cancel) + Self::release_held_funds_with_fee(&pending, &recipient, apply_fee)?; + + Self::deposit_event(Event::TransactionCancelled { who: who.clone(), tx_id }); + Ok(()) + } + + /// Releases held funds from a pending transfer, optionally applying volume fee. + /// Burns the fee portion and transfers the remainder to the recipient. + fn release_held_funds_with_fee( + pending: &PendingTransferOf, + recipient: &T::AccountId, + apply_fee: bool, + ) -> DispatchResult { + let (fee_amount, remaining_amount) = if apply_fee { let volume_fee = T::VolumeFee::get(); - // unchecked ok because volume_fee < 1 so overflow impossible let fee = volume_fee * pending.amount; - let remaining = pending.amount.saturating_sub(fee); - (fee, remaining) + (fee, pending.amount.saturating_sub(fee)) } else { - // No fee for regular accounts (Zero::zero(), pending.amount) }; - // For assets, burn held funds (fee) and transfer remaining to interceptor - // For native balances, burn held funds (fee) and transfer remaining to interceptor + if let Ok((call, _)) = T::Preimages::realize::>(&pending.call) { if let Ok(pallet_assets::Call::transfer_keep_alive { id, .. }) = call.clone().try_into() @@ -794,8 +857,8 @@ pub mod pallet { let reason = Self::asset_hold_reason(); let asset_id = id.into(); - // Burn fee amount if fee_amount > 0 - let _ = as AssetsHold>>::burn_held( + // Burn fee amount + as AssetsHold>>::burn_held( asset_id.clone(), &reason, &pending.from, @@ -804,19 +867,18 @@ pub mod pallet { Fortitude::Polite, )?; - // Transfer remaining amount to interceptor - let _ = as AssetsHold>>::transfer_on_hold( + // Transfer remaining amount to recipient + as AssetsHold>>::transfer_on_hold( asset_id, &reason, &pending.from, - &interceptor, + recipient, remaining_amount, Precision::Exact, Restriction::Free, Fortitude::Polite, )?; - } - if let Ok(pallet_balances::Call::transfer_keep_alive { .. }) = + } else if let Ok(pallet_balances::Call::transfer_keep_alive { .. }) = call.clone().try_into() { // Burn fee amount @@ -828,11 +890,11 @@ pub mod pallet { Fortitude::Polite, )?; - // Transfer remaining amount to interceptor + // Transfer remaining amount to recipient pallet_balances::Pallet::::transfer_on_hold( &HoldReason::ScheduledTransfer.into(), &pending.from, - &interceptor, + recipient, remaining_amount, Precision::Exact, Restriction::Free, @@ -841,7 +903,6 @@ pub mod pallet { } } - Self::deposit_event(Event::TransactionCancelled { who: who.clone(), tx_id }); Ok(()) } } diff --git a/pallets/reversible-transfers/src/tests/mock.rs b/pallets/reversible-transfers/src/tests/mock.rs index b63ceef1..20271f66 100644 --- a/pallets/reversible-transfers/src/tests/mock.rs +++ b/pallets/reversible-transfers/src/tests/mock.rs @@ -195,6 +195,7 @@ parameter_types! { pub const MinDelayPeriodMoment: u64 = 2000; pub const MaxReversibleTransfers: u32 = 100; pub const MaxInterceptorAccounts: u32 = 10; + pub const MaxPendingPerAccount: u32 = 16; pub const HighSecurityVolumeFee: Permill = Permill::from_percent(1); } @@ -259,6 +260,7 @@ impl pallet_reversible_transfers::Config for Test { type Moment = Moment; type TimeProvider = MockTimestamp; type MaxInterceptorAccounts = MaxInterceptorAccounts; + type MaxPendingPerAccount = MaxPendingPerAccount; type VolumeFee = HighSecurityVolumeFee; type ProofRecorder = MockProofRecorder; } diff --git a/pallets/reversible-transfers/src/tests/test_high_security_account.rs b/pallets/reversible-transfers/src/tests/test_high_security_account.rs index c9c8aa73..7cd9b21a 100644 --- a/pallets/reversible-transfers/src/tests/test_high_security_account.rs +++ b/pallets/reversible-transfers/src/tests/test_high_security_account.rs @@ -109,3 +109,135 @@ fn guardian_can_cancel_reversible_transactions_for_hs_account() { ); }); } + +#[test] +fn recover_funds_cancels_all_pending_transfers() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let hs_user = alice(); + let guardian = bob(); + let dest = charlie(); + + let initial_hs_balance = Balances::free_balance(&hs_user); + let initial_guardian_balance = Balances::free_balance(&guardian); + let initial_total_issuance = TotalIssuance::::get(); + + // Schedule multiple transfers + let amount1 = 10_000u128; + let amount2 = 20_000u128; + + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(hs_user.clone()), + dest.clone(), + amount1 + )); + + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(hs_user.clone()), + dest.clone(), + amount2 + )); + + // Verify pending transfers exist + let pending = crate::PendingTransfersBySender::::get(&hs_user); + assert_eq!(pending.len(), 2, "Should have 2 pending transfers"); + + // Now recover all funds + assert_ok!(ReversibleTransfers::recover_funds( + RuntimeOrigin::signed(guardian.clone()), + hs_user.clone() + )); + + // Verify all pending transfers were cancelled + let pending_after = crate::PendingTransfersBySender::::get(&hs_user); + assert_eq!(pending_after.len(), 0, "All pending transfers should be cancelled"); + + // Verify hs_user is drained + assert_eq!(Balances::free_balance(&hs_user), 0, "HS user should be drained"); + + // Calculate expected amounts: + // - Volume fee (1%) is burned for each cancelled transfer + // - Remaining goes to guardian + let fee1 = amount1 / 100; // 100 + let fee2 = amount2 / 100; // 200 + let total_fees = fee1 + fee2; // 300 + let remaining_from_cancels = (amount1 - fee1) + (amount2 - fee2); // 9900 + 19800 = 29700 + let free_balance_after_holds = initial_hs_balance - amount1 - amount2; + + // Guardian receives: remaining from cancelled transfers + free balance + let expected_guardian_balance = + initial_guardian_balance + remaining_from_cancels + free_balance_after_holds; + assert_eq!( + Balances::free_balance(&guardian), + expected_guardian_balance, + "Guardian should receive all funds minus volume fees" + ); + + // Total issuance should decrease by the volume fees burned + assert_eq!( + TotalIssuance::::get(), + initial_total_issuance - total_fees, + "Volume fees should be burned" + ); + + // Verify events were emitted for each cancelled transfer + let events = System::events(); + let cancel_events: Vec<_> = events + .iter() + .filter(|e| { + matches!( + e.event, + RuntimeEvent::ReversibleTransfers(Event::TransactionCancelled { .. }) + ) + }) + .collect(); + assert_eq!( + cancel_events.len(), + 2, + "Should emit TransactionCancelled for each pending transfer" + ); + + // Verify FundsRecovered event + System::assert_has_event( + Event::FundsRecovered { account: hs_user.clone(), guardian: guardian.clone() }.into(), + ); + }); +} + +#[test] +fn too_many_pending_transactions_error() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let hs_user = alice(); + let dest = charlie(); + let amount = 100u128; + + // Schedule MaxPendingPerAccount transfers (16) + // Need to advance block number between batches to avoid scheduler max per block limit + for i in 0..16 { + // Advance block every 8 transfers to stay under scheduler's MaxScheduledPerBlock (10) + if i > 0 && i % 8 == 0 { + System::set_block_number(System::block_number() + 1); + } + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(hs_user.clone()), + dest.clone(), + amount + )); + } + + // Verify we have 16 pending + let pending = crate::PendingTransfersBySender::::get(&hs_user); + assert_eq!(pending.len(), 16, "Should have 16 pending transfers"); + + // The 17th should fail + assert_err!( + ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(hs_user.clone()), + dest.clone(), + amount + ), + crate::Error::::TooManyPendingTransactions + ); + }); +} diff --git a/pallets/reversible-transfers/src/weights.rs b/pallets/reversible-transfers/src/weights.rs index 632d3504..d657dd0e 100644 --- a/pallets/reversible-transfers/src/weights.rs +++ b/pallets/reversible-transfers/src/weights.rs @@ -18,10 +18,10 @@ //! Autogenerated weights for `pallet_reversible_transfers` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-01-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 53.0.0 +//! DATE: 2026-03-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `coldbook.local`, CPU: `` +//! HOSTNAME: `arunachala.local`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -51,7 +51,7 @@ pub trait WeightInfo { fn schedule_transfer() -> Weight; fn cancel() -> Weight; fn execute_transfer() -> Weight; - fn recover_funds() -> Weight; + fn recover_funds(n: u32, ) -> Weight; } /// Weights for `pallet_reversible_transfers` using the Substrate node and recommended hardware. @@ -63,10 +63,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `ReversibleTransfers::InterceptorIndex` (`max_values`: None, `max_size`: Some(1073), added: 3548, mode: `MaxEncodedLen`) fn set_high_security() -> Weight { // Proof Size summary in bytes: - // Measured: `152` + // Measured: `312` // Estimated: `4538` - // Minimum execution time: 41_000_000 picoseconds. - Weight::from_parts(43_000_000, 4538) + // Minimum execution time: 10_000_000 picoseconds. + Weight::from_parts(10_000_000, 4538) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -74,99 +74,103 @@ impl WeightInfo for SubstrateWeight { /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::GlobalNonce` (r:1 w:1) /// Proof: `ReversibleTransfers::GlobalNonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::AccountPendingIndex` (r:1 w:1) - /// Proof: `ReversibleTransfers::AccountPendingIndex` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::PendingTransfersByRecipient` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersByRecipient` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Lookup` (r:1 w:1) /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10718), added: 13193, mode: `MaxEncodedLen`) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10018), added: 12493, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfers` (r:0 w:1) /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) fn schedule_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `597` - // Estimated: `14183` - // Minimum execution time: 285_000_000 picoseconds. - Weight::from_parts(307_000_000, 14183) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(8_u64)) + // Measured: `693` + // Estimated: `13483` + // Minimum execution time: 290_000_000 picoseconds. + Weight::from_parts(296_000_000, 13483) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `ReversibleTransfers::PendingTransfers` (r:1 w:1) /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::AccountPendingIndex` (r:1 w:1) - /// Proof: `ReversibleTransfers::AccountPendingIndex` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::PendingTransfersByRecipient` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersByRecipient` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Lookup` (r:1 w:1) /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10718), added: 13193, mode: `MaxEncodedLen`) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10018), added: 12493, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn cancel() -> Weight { // Proof Size summary in bytes: - // Measured: `1880` - // Estimated: `14183` - // Minimum execution time: 172_000_000 picoseconds. - Weight::from_parts(195_000_000, 14183) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(8_u64)) + // Measured: `1703` + // Estimated: `13483` + // Minimum execution time: 139_000_000 picoseconds. + Weight::from_parts(142_000_000, 13483) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `ReversibleTransfers::PendingTransfers` (r:1 w:1) /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::AccountPendingIndex` (r:1 w:1) - /// Proof: `ReversibleTransfers::AccountPendingIndex` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::PendingTransfersByRecipient` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersByRecipient` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferCount` (r:1 w:1) - /// Proof: `Balances::TransferCount` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferProof` (r:0 w:1) - /// Proof: `Balances::TransferProof` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `MaxEncodedLen`) + /// Storage: `Wormhole::TransferCount` (r:1 w:1) + /// Proof: `Wormhole::TransferCount` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + /// Storage: `Wormhole::TransferProof` (r:0 w:1) + /// Proof: `Wormhole::TransferProof` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `MaxEncodedLen`) fn execute_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `1360` - // Estimated: `3834` - // Minimum execution time: 180_000_000 picoseconds. - Weight::from_parts(196_000_000, 3834) - .saturating_add(T::DbWeight::get().reads(7_u64)) - .saturating_add(T::DbWeight::get().writes(8_u64)) + // Measured: `1104` + // Estimated: `4026` + // Minimum execution time: 151_000_000 picoseconds. + Weight::from_parts(154_000_000, 4026) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) + /// Storage: `ReversibleTransfers::PendingTransfers` (r:16 w:16) + /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:16 w:16) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:2 w:2) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10018), added: 12493, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferCount` (r:1 w:1) - /// Proof: `Balances::TransferCount` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferProof` (r:0 w:1) - /// Proof: `Balances::TransferProof` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `MaxEncodedLen`) - fn recover_funds() -> Weight { + /// Storage: `Scheduler::Retries` (r:0 w:16) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 16]`. + fn recover_funds(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `477` - // Estimated: `3593` - // Minimum execution time: 87_000_000 picoseconds. - Weight::from_parts(96_000_000, 3593) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `1322 + n * (431 ±0)` + // Estimated: `9482 + n * (2766 ±32)` + // Minimum execution time: 80_000_000 picoseconds. + Weight::from_parts(89_414_112, 9482) + // Standard Error: 118_258 + .saturating_add(Weight::from_parts(90_534_027, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2766).saturating_mul(n.into())) } } @@ -178,10 +182,10 @@ impl WeightInfo for () { /// Proof: `ReversibleTransfers::InterceptorIndex` (`max_values`: None, `max_size`: Some(1073), added: 3548, mode: `MaxEncodedLen`) fn set_high_security() -> Weight { // Proof Size summary in bytes: - // Measured: `152` + // Measured: `312` // Estimated: `4538` - // Minimum execution time: 41_000_000 picoseconds. - Weight::from_parts(43_000_000, 4538) + // Minimum execution time: 10_000_000 picoseconds. + Weight::from_parts(10_000_000, 4538) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -189,98 +193,102 @@ impl WeightInfo for () { /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::GlobalNonce` (r:1 w:1) /// Proof: `ReversibleTransfers::GlobalNonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::AccountPendingIndex` (r:1 w:1) - /// Proof: `ReversibleTransfers::AccountPendingIndex` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::PendingTransfersByRecipient` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersByRecipient` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Lookup` (r:1 w:1) /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10718), added: 13193, mode: `MaxEncodedLen`) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10018), added: 12493, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfers` (r:0 w:1) /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) fn schedule_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `597` - // Estimated: `14183` - // Minimum execution time: 285_000_000 picoseconds. - Weight::from_parts(307_000_000, 14183) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(8_u64)) + // Measured: `693` + // Estimated: `13483` + // Minimum execution time: 290_000_000 picoseconds. + Weight::from_parts(296_000_000, 13483) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `ReversibleTransfers::PendingTransfers` (r:1 w:1) /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::AccountPendingIndex` (r:1 w:1) - /// Proof: `ReversibleTransfers::AccountPendingIndex` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::PendingTransfersByRecipient` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersByRecipient` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Lookup` (r:1 w:1) /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10718), added: 13193, mode: `MaxEncodedLen`) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10018), added: 12493, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn cancel() -> Weight { // Proof Size summary in bytes: - // Measured: `1880` - // Estimated: `14183` - // Minimum execution time: 172_000_000 picoseconds. - Weight::from_parts(195_000_000, 14183) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(8_u64)) + // Measured: `1703` + // Estimated: `13483` + // Minimum execution time: 139_000_000 picoseconds. + Weight::from_parts(142_000_000, 13483) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `ReversibleTransfers::PendingTransfers` (r:1 w:1) /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::AccountPendingIndex` (r:1 w:1) - /// Proof: `ReversibleTransfers::AccountPendingIndex` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) - /// Storage: `ReversibleTransfers::PendingTransfersByRecipient` (r:1 w:1) - /// Proof: `ReversibleTransfers::PendingTransfersByRecipient` (`max_values`: None, `max_size`: Some(369), added: 2844, mode: `MaxEncodedLen`) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferCount` (r:1 w:1) - /// Proof: `Balances::TransferCount` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferProof` (r:0 w:1) - /// Proof: `Balances::TransferProof` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `MaxEncodedLen`) + /// Storage: `Wormhole::TransferCount` (r:1 w:1) + /// Proof: `Wormhole::TransferCount` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`) + /// Storage: `Wormhole::TransferProof` (r:0 w:1) + /// Proof: `Wormhole::TransferProof` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `MaxEncodedLen`) fn execute_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `1360` - // Estimated: `3834` - // Minimum execution time: 180_000_000 picoseconds. - Weight::from_parts(196_000_000, 3834) - .saturating_add(RocksDbWeight::get().reads(7_u64)) - .saturating_add(RocksDbWeight::get().writes(8_u64)) + // Measured: `1104` + // Estimated: `4026` + // Minimum execution time: 151_000_000 picoseconds. + Weight::from_parts(154_000_000, 4026) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) + /// Storage: `ReversibleTransfers::PendingTransfersBySender` (r:1 w:1) + /// Proof: `ReversibleTransfers::PendingTransfersBySender` (`max_values`: None, `max_size`: Some(561), added: 3036, mode: `MaxEncodedLen`) + /// Storage: `ReversibleTransfers::PendingTransfers` (r:16 w:16) + /// Proof: `ReversibleTransfers::PendingTransfers` (`max_values`: None, `max_size`: Some(291), added: 2766, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:16 w:16) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(53), added: 2528, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:2 w:2) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(10018), added: 12493, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferCount` (r:1 w:1) - /// Proof: `Balances::TransferCount` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) - /// Storage: `Balances::TransferProof` (r:0 w:1) - /// Proof: `Balances::TransferProof` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `MaxEncodedLen`) - fn recover_funds() -> Weight { + /// Storage: `Scheduler::Retries` (r:0 w:16) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 16]`. + fn recover_funds(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `477` - // Estimated: `3593` - // Minimum execution time: 87_000_000 picoseconds. - Weight::from_parts(96_000_000, 3593) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Measured: `1322 + n * (431 ±0)` + // Estimated: `9482 + n * (2766 ±32)` + // Minimum execution time: 80_000_000 picoseconds. + Weight::from_parts(89_414_112, 9482) + // Standard Error: 118_258 + .saturating_add(Weight::from_parts(90_534_027, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2766).saturating_mul(n.into())) } } diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index 5abfa19c..b4f383d7 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -462,6 +462,7 @@ parameter_types! { pub const DefaultDelay: BlockNumberOrTimestamp = BlockNumberOrTimestamp::BlockNumber(DAYS); pub const MinDelayPeriodBlocks: BlockNumber = 2; pub const MaxInterceptorAccounts: u32 = 32; + pub const MaxPendingPerAccount: u32 = 16; /// Volume fee for reversed transactions from high-security accounts only (1% fee is burned) pub const HighSecurityVolumeFee: Permill = Permill::from_percent(1); } @@ -480,6 +481,7 @@ impl pallet_reversible_transfers::Config for Runtime { type Moment = Moment; type TimeProvider = Timestamp; type MaxInterceptorAccounts = MaxInterceptorAccounts; + type MaxPendingPerAccount = MaxPendingPerAccount; type VolumeFee = HighSecurityVolumeFee; type ProofRecorder = Wormhole; }