diff --git a/pallets/multisig/src/benchmarking.rs b/pallets/multisig/src/benchmarking.rs index 2ed1ff71..dfbc9d91 100644 --- a/pallets/multisig/src/benchmarking.rs +++ b/pallets/multisig/src/benchmarking.rs @@ -60,9 +60,7 @@ mod benchmarks { #[benchmark] fn propose( c: Linear<0, { T::MaxCallSize::get().saturating_sub(100) }>, - e: Linear<0, { T::MaxTotalProposalsInStorage::get() }>, // expired proposals to cleanup ) -> Result<(), BenchmarkError> { - // Setup: Create a multisig first let caller: T::AccountId = whitelisted_caller(); fund_account::(&caller, BalanceOf2::::from(100000u128)); @@ -75,46 +73,21 @@ mod benchmarks { let threshold = 2u32; signers.sort(); - // Create multisig directly in storage let multisig_address = Multisig::::derive_multisig_address(&signers, 0); let bounded_signers: BoundedSignersOf = signers.clone().try_into().unwrap(); let multisig_data = MultisigDataOf:: { signers: bounded_signers, threshold, nonce: 0, - proposal_nonce: e, // We'll insert e expired proposals + proposal_nonce: 0, creator: caller.clone(), deposit: T::MultisigDeposit::get(), last_activity: frame_system::Pallet::::block_number(), - active_proposals: e, + active_proposals: 0, proposals_per_signer: BoundedBTreeMap::new(), }; Multisigs::::insert(&multisig_address, multisig_data); - // Insert e expired proposals (worst case for auto-cleanup) - let expired_block = 10u32.into(); - for i in 0..e { - let system_call = frame_system::Call::::remark { remark: vec![i as u8; 10] }; - let call = ::RuntimeCall::from(system_call); - let encoded_call = call.encode(); - let bounded_call: BoundedCallOf = encoded_call.try_into().unwrap(); - let bounded_approvals: BoundedApprovalsOf = vec![caller.clone()].try_into().unwrap(); - - let proposal_data = ProposalDataOf:: { - proposer: caller.clone(), - call: bounded_call, - expiry: expired_block, - approvals: bounded_approvals, - deposit: 10u32.into(), - status: ProposalStatus::Active, - }; - Proposals::::insert(&multisig_address, i, proposal_data); - } - - // Move past expiry so proposals are expired - frame_system::Pallet::::set_block_number(100u32.into()); - - // Create a new proposal (will auto-cleanup all e expired proposals) let system_call = frame_system::Call::::remark { remark: vec![99u8; c as usize] }; let call = ::RuntimeCall::from(system_call); let encoded_call = call.encode(); @@ -123,9 +96,8 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), multisig_address.clone(), encoded_call, expiry); - // Verify new proposal was created and expired ones were cleaned let multisig = Multisigs::::get(&multisig_address).unwrap(); - assert_eq!(multisig.active_proposals, 1); // Only new proposal remains + assert_eq!(multisig.active_proposals, 1); Ok(()) } @@ -133,24 +105,7 @@ mod benchmarks { #[benchmark] fn propose_high_security( c: Linear<0, { T::MaxCallSize::get().saturating_sub(100) }>, - e: Linear<0, { T::MaxTotalProposalsInStorage::get() }>, // expired proposals to cleanup ) -> Result<(), BenchmarkError> { - // Benchmarks propose() for high-security multisigs (includes decode + whitelist check) - // This is more expensive than normal propose due to: - // 1. is_high_security() check (1 DB read from ReversibleTransfers::HighSecurityAccounts) - // 2. RuntimeCall decode (O(c) overhead - scales with call size) - // 3. is_whitelisted() pattern matching - // - // NOTE: This benchmark measures the OVERHEAD of high-security checks, - // not the functionality. The actual HighSecurity implementation is runtime-specific. - // Mock implementation in tests would need to recognize this multisig as HS, - // but for weight measurement, we're benchmarking the worst-case: full decode path. - // - // In production, the runtime's HighSecurityConfig will check: - // - pallet_reversible_transfers::HighSecurityAccounts storage - // - Pattern match against RuntimeCall variants - - // Setup: Create a high-security multisig let caller: T::AccountId = whitelisted_caller(); fund_account::(&caller, BalanceOf2::::from(100000u128)); @@ -163,24 +118,21 @@ mod benchmarks { let threshold = 2u32; signers.sort(); - // Create multisig directly in storage let multisig_address = Multisig::::derive_multisig_address(&signers, 0); let bounded_signers: BoundedSignersOf = signers.clone().try_into().unwrap(); let multisig_data = MultisigDataOf:: { signers: bounded_signers, threshold, nonce: 0, - proposal_nonce: e, + proposal_nonce: 0, creator: caller.clone(), deposit: T::MultisigDeposit::get(), last_activity: frame_system::Pallet::::block_number(), - active_proposals: e, + active_proposals: 0, proposals_per_signer: BoundedBTreeMap::new(), }; Multisigs::::insert(&multisig_address, multisig_data); - // IMPORTANT: Set this multisig as high-security for benchmarking - // This ensures we measure the actual HS code path #[cfg(feature = "runtime-benchmarks")] { use pallet_reversible_transfers::{ @@ -192,49 +144,17 @@ mod benchmarks { interceptor: multisig_address.clone(), delay: BlockNumberOrTimestamp::BlockNumber(100u32.into()), }; - // Use helper that accepts T: pallet_reversible_transfers::Config insert_hs_account_for_benchmark::(multisig_address.clone(), hs_data); } - // Insert e expired proposals (worst case for auto-cleanup) - let expired_block = 10u32.into(); - for i in 0..e { - let system_call = frame_system::Call::::remark { remark: vec![i as u8; 10] }; - let call = ::RuntimeCall::from(system_call); - let encoded_call = call.encode(); - let bounded_call: BoundedCallOf = encoded_call.try_into().unwrap(); - let bounded_approvals: BoundedApprovalsOf = vec![caller.clone()].try_into().unwrap(); - - let proposal_data = ProposalDataOf:: { - proposer: caller.clone(), - call: bounded_call, - expiry: expired_block, - approvals: bounded_approvals, - deposit: 10u32.into(), - status: ProposalStatus::Active, - }; - Proposals::::insert(&multisig_address, i, proposal_data); - } - - // Move past expiry so proposals are expired - frame_system::Pallet::::set_block_number(100u32.into()); - - // Create a whitelisted call for HS multisig - // Using system::remark with variable size to measure decode cost O(c) - // NOTE: system::remark is whitelisted ONLY in runtime-benchmarks mode let system_call = frame_system::Call::::remark { remark: vec![99u8; c as usize] }; let call = ::RuntimeCall::from(system_call); let encoded_call = call.encode(); - - // Verify we're testing with actual variable size - assert!(encoded_call.len() >= c as usize, "Call size should scale with parameter c"); - let expiry = frame_system::Pallet::::block_number() + 1000u32.into(); #[extrinsic_call] propose(RawOrigin::Signed(caller.clone()), multisig_address.clone(), encoded_call, expiry); - // Verify new proposal was created and expired ones were cleaned let multisig = Multisigs::::get(&multisig_address).unwrap(); assert_eq!(multisig.active_proposals, 1); @@ -244,10 +164,7 @@ mod benchmarks { #[benchmark] fn approve( c: Linear<0, { T::MaxCallSize::get().saturating_sub(100) }>, - e: Linear<0, { T::MaxTotalProposalsInStorage::get() }>, // expired proposals to cleanup ) -> Result<(), BenchmarkError> { - // Setup: Create multisig and proposal directly in storage - // Threshold is 3, so adding one more approval won't trigger execution let caller: T::AccountId = whitelisted_caller(); fund_account::(&caller, BalanceOf2::::from(100000u128)); @@ -259,52 +176,24 @@ mod benchmarks { fund_account::(&signer3, BalanceOf2::::from(100000u128)); let mut signers = vec![caller.clone(), signer1.clone(), signer2.clone(), signer3.clone()]; - let threshold = 3u32; // Need 3 approvals - - // Sort signers to match create_multisig behavior + let threshold = 3u32; signers.sort(); - // Directly insert multisig into storage let multisig_address = Multisig::::derive_multisig_address(&signers, 0); let bounded_signers: BoundedSignersOf = signers.clone().try_into().unwrap(); let multisig_data = MultisigDataOf:: { signers: bounded_signers, threshold, nonce: 0, - proposal_nonce: e + 1, // We'll insert e expired proposals + 1 active + proposal_nonce: 1, creator: caller.clone(), deposit: T::MultisigDeposit::get(), last_activity: frame_system::Pallet::::block_number(), - active_proposals: e + 1, + active_proposals: 1, proposals_per_signer: BoundedBTreeMap::new(), }; Multisigs::::insert(&multisig_address, multisig_data); - // Insert e expired proposals (worst case for auto-cleanup) - let expired_block = 10u32.into(); - for i in 0..e { - let system_call = frame_system::Call::::remark { remark: vec![i as u8; 10] }; - let call = ::RuntimeCall::from(system_call); - let encoded_call = call.encode(); - let bounded_call: BoundedCallOf = encoded_call.try_into().unwrap(); - let bounded_approvals: BoundedApprovalsOf = vec![caller.clone()].try_into().unwrap(); - - let proposal_data = ProposalDataOf:: { - proposer: caller.clone(), - call: bounded_call, - expiry: expired_block, - approvals: bounded_approvals, - deposit: 10u32.into(), - status: ProposalStatus::Active, - }; - Proposals::::insert(&multisig_address, i, proposal_data); - } - - // Move past expiry so proposals are expired - frame_system::Pallet::::set_block_number(100u32.into()); - - // Directly insert active proposal into storage with 1 approval - // Create a remark call where the remark itself is c bytes let system_call = frame_system::Call::::remark { remark: vec![1u8; c as usize] }; let call = ::RuntimeCall::from(system_call); let encoded_call = call.encode(); @@ -321,13 +210,12 @@ mod benchmarks { status: ProposalStatus::Active, }; - let proposal_id = e; // Active proposal after expired ones + let proposal_id = 0u32; Proposals::::insert(&multisig_address, proposal_id, proposal_data); #[extrinsic_call] _(RawOrigin::Signed(signer1.clone()), multisig_address.clone(), proposal_id); - // Verify approval was added (now 2/3, not executed yet) let proposal = Proposals::::get(&multisig_address, proposal_id).unwrap(); assert!(proposal.approvals.contains(&signer1)); assert_eq!(proposal.approvals.len(), 2); @@ -404,11 +292,7 @@ mod benchmarks { } #[benchmark] - fn cancel( - c: Linear<0, { T::MaxCallSize::get().saturating_sub(100) }>, - e: Linear<0, { T::MaxTotalProposalsInStorage::get() }>, // expired proposals to cleanup - ) -> Result<(), BenchmarkError> { - // Setup: Create multisig and proposal directly in storage + fn cancel() -> Result<(), BenchmarkError> { let caller: T::AccountId = whitelisted_caller(); fund_account::(&caller, BalanceOf2::::from(100000u128)); @@ -419,52 +303,24 @@ mod benchmarks { let mut signers = vec![caller.clone(), signer1.clone(), signer2.clone()]; let threshold = 2u32; - - // Sort signers to match create_multisig behavior signers.sort(); - // Directly insert multisig into storage let multisig_address = Multisig::::derive_multisig_address(&signers, 0); let bounded_signers: BoundedSignersOf = signers.clone().try_into().unwrap(); let multisig_data = MultisigDataOf:: { signers: bounded_signers, threshold, nonce: 0, - proposal_nonce: e + 1, // We'll insert e expired proposals + 1 active + proposal_nonce: 1, creator: caller.clone(), deposit: T::MultisigDeposit::get(), last_activity: frame_system::Pallet::::block_number(), - active_proposals: e + 1, + active_proposals: 1, proposals_per_signer: BoundedBTreeMap::new(), }; Multisigs::::insert(&multisig_address, multisig_data); - // Insert e expired proposals (worst case for auto-cleanup) - let expired_block = 10u32.into(); - for i in 0..e { - let system_call = frame_system::Call::::remark { remark: vec![i as u8; 10] }; - let call = ::RuntimeCall::from(system_call); - let encoded_call = call.encode(); - let bounded_call: BoundedCallOf = encoded_call.try_into().unwrap(); - let bounded_approvals: BoundedApprovalsOf = vec![caller.clone()].try_into().unwrap(); - - let proposal_data = ProposalDataOf:: { - proposer: caller.clone(), - call: bounded_call, - expiry: expired_block, - approvals: bounded_approvals, - deposit: 10u32.into(), - status: ProposalStatus::Active, - }; - Proposals::::insert(&multisig_address, i, proposal_data); - } - - // Move past expiry so proposals are expired - frame_system::Pallet::::set_block_number(100u32.into()); - - // Directly insert active proposal into storage - // Create a remark call where the remark itself is c bytes - let system_call = frame_system::Call::::remark { remark: vec![1u8; c as usize] }; + let system_call = frame_system::Call::::remark { remark: vec![1u8; 32] }; let call = ::RuntimeCall::from(system_call); let encoded_call = call.encode(); let expiry = frame_system::Pallet::::block_number() + 1000u32.into(); @@ -480,13 +336,12 @@ mod benchmarks { status: ProposalStatus::Active, }; - let proposal_id = e; // Active proposal after expired ones + let proposal_id = 0u32; Proposals::::insert(&multisig_address, proposal_id, proposal_data); #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), multisig_address.clone(), proposal_id); - // Verify proposal was removed from storage (auto-deleted after cancellation) assert!(!Proposals::::contains_key(&multisig_address, proposal_id)); Ok(()) @@ -630,7 +485,6 @@ mod benchmarks { #[benchmark] fn dissolve_multisig() -> Result<(), BenchmarkError> { - // Setup: Create a clean multisig (no proposals, zero balance) let caller: T::AccountId = whitelisted_caller(); fund_account::(&caller, BalanceOf2::::from(10000u128)); @@ -639,16 +493,12 @@ mod benchmarks { let mut signers = vec![caller.clone(), signer1.clone(), signer2.clone()]; let threshold = 2u32; - - // Sort signers to match create_multisig behavior signers.sort(); - // Directly insert multisig into storage let multisig_address = Multisig::::derive_multisig_address(&signers, 0); let bounded_signers: BoundedSignersOf = signers.clone().try_into().unwrap(); let deposit = T::MultisigDeposit::get(); - // Reserve deposit from caller ::Currency::reserve(&caller, deposit)?; let multisig_data = MultisigDataOf:: { @@ -659,22 +509,78 @@ mod benchmarks { creator: caller.clone(), deposit, last_activity: frame_system::Pallet::::block_number(), - active_proposals: 0, // No proposals + active_proposals: 0, proposals_per_signer: BoundedBTreeMap::new(), }; Multisigs::::insert(&multisig_address, multisig_data); - // Ensure multisig address has zero balance (required for dissolution) - // Don't fund it at all - #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), multisig_address.clone()); - // Verify multisig was removed assert!(!Multisigs::::contains_key(&multisig_address)); Ok(()) } + #[benchmark] + fn cleanup_expired_proposals( + e: Linear<0, { T::MaxTotalProposalsInStorage::get() }>, + ) -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + fund_account::(&caller, BalanceOf2::::from(100000u128)); + + let signer1: T::AccountId = benchmark_account("signer1", 0, SEED); + let signer2: T::AccountId = benchmark_account("signer2", 1, SEED); + fund_account::(&signer1, BalanceOf2::::from(100000u128)); + fund_account::(&signer2, BalanceOf2::::from(100000u128)); + + let mut signers = vec![caller.clone(), signer1.clone(), signer2.clone()]; + let threshold = 2u32; + signers.sort(); + + let multisig_address = Multisig::::derive_multisig_address(&signers, 0); + let bounded_signers: BoundedSignersOf = signers.clone().try_into().unwrap(); + let multisig_data = MultisigDataOf:: { + signers: bounded_signers, + threshold, + nonce: 0, + proposal_nonce: e, + creator: caller.clone(), + deposit: T::MultisigDeposit::get(), + last_activity: frame_system::Pallet::::block_number(), + active_proposals: e, + proposals_per_signer: BoundedBTreeMap::new(), + }; + Multisigs::::insert(&multisig_address, multisig_data); + + let expired_block = 10u32.into(); + for i in 0..e { + let system_call = frame_system::Call::::remark { remark: vec![i as u8; 10] }; + let call = ::RuntimeCall::from(system_call); + let encoded_call = call.encode(); + let bounded_call: BoundedCallOf = encoded_call.try_into().unwrap(); + let bounded_approvals: BoundedApprovalsOf = vec![caller.clone()].try_into().unwrap(); + + let proposal_data = ProposalDataOf:: { + proposer: caller.clone(), + call: bounded_call, + expiry: expired_block, + approvals: bounded_approvals, + deposit: 10u32.into(), + status: ProposalStatus::Active, + }; + Proposals::::insert(&multisig_address, i, proposal_data); + } + + frame_system::Pallet::::set_block_number(100u32.into()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), multisig_address.clone()); + + assert_eq!(Proposals::::iter_key_prefix(&multisig_address).count(), 0); + + Ok(()) + } + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/multisig/src/lib.rs b/pallets/multisig/src/lib.rs index 061e7e0f..fe663cba 100644 --- a/pallets/multisig/src/lib.rs +++ b/pallets/multisig/src/lib.rs @@ -494,19 +494,12 @@ pub mod pallet { /// - A deposit (refundable - returned immediately on execution/cancellation) /// - A fee (non-refundable, burned immediately) /// - /// **Auto-cleanup:** Before creating a new proposal, ALL expired proposals are - /// automatically removed and deposits returned to original proposers. This is the primary - /// cleanup mechanism. - /// /// **For threshold=1:** If the multisig threshold is 1, the proposal executes immediately. /// /// **Weight:** Charged based on whether multisig is high-security or not. /// High-security multisigs incur additional cost for decode + whitelist check. #[pallet::call_index(1)] - #[pallet::weight(::WeightInfo::propose_high_security( - call.len() as u32, - T::MaxTotalProposalsInStorage::get() - ))] + #[pallet::weight(::WeightInfo::propose_high_security(call.len() as u32))] #[allow(clippy::useless_conversion)] pub fn propose( origin: OriginFor, @@ -538,14 +531,6 @@ pub mod pallet { ); } - // Auto-cleanup expired proposals before creating new one - // This is the primary cleanup mechanism for active multisigs - let iterated_count = Self::auto_cleanup_expired_proposals(&multisig_address, &proposer); - - // Reload multisig data after potential cleanup - let multisig_data = - Multisigs::::get(&multisig_address).ok_or(Error::::MultisigNotFound)?; - let current_block = frame_system::Pallet::::block_number(); // Get signers count (used for multiple checks below) @@ -679,10 +664,10 @@ pub mod pallet { // Calculate actual weight and refund if not high-security let actual_weight = if is_high_security { // Used high-security path (decode + whitelist check) - ::WeightInfo::propose_high_security(call_size, iterated_count) + ::WeightInfo::propose_high_security(call_size) } else { // Used normal path (no decode overhead) - ::WeightInfo::propose(call_size, iterated_count) + ::WeightInfo::propose(call_size) }; Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) @@ -693,19 +678,11 @@ pub mod pallet { /// If this approval brings the total approvals to or above the threshold, /// the transaction will be automatically executed. /// - /// **Auto-cleanup:** Before processing the approval, ALL expired proposals are - /// automatically removed and deposits returned to original proposers. - /// /// Parameters: /// - `multisig_address`: The multisig account /// - `proposal_id`: ID (nonce) of the proposal to approve - /// - /// Weight: Charges for MAX call size and MAX expired proposals, refunds based on actual #[pallet::call_index(2)] - #[pallet::weight(::WeightInfo::approve( - T::MaxCallSize::get(), - T::MaxTotalProposalsInStorage::get() - ))] + #[pallet::weight(::WeightInfo::approve(T::MaxCallSize::get()))] #[allow(clippy::useless_conversion)] pub fn approve( origin: OriginFor, @@ -717,21 +694,14 @@ pub mod pallet { // Check if approver is a signer let multisig_data = Self::ensure_is_signer(&multisig_address, &approver)?; - // Auto-cleanup expired proposals on any multisig activity - // Returns count of proposals in storage (which determines iteration cost) - let iterated_count = Self::auto_cleanup_expired_proposals(&multisig_address, &approver); - - // Get proposal let mut proposal = Proposals::::get(&multisig_address, proposal_id) .ok_or(Error::::ProposalNotFound)?; // Calculate actual weight based on real call size and actual storage size // We charge for worst-case (e=Max), but refund based on actual storage size let actual_call_size = proposal.call.len() as u32; - let actual_weight = - ::WeightInfo::approve(actual_call_size, iterated_count); + let actual_weight = ::WeightInfo::approve(actual_call_size); - // Check if not expired let current_block = frame_system::Pallet::::block_number(); ensure!(current_block <= proposal.expiry, Error::::ProposalExpired); @@ -776,42 +746,21 @@ pub mod pallet { /// Cancel a proposed transaction (only by proposer) /// - /// **Auto-cleanup:** Before processing the cancellation, ALL expired proposals are - /// automatically removed and deposits returned to original proposers. - /// /// Parameters: /// - `multisig_address`: The multisig account /// - `proposal_id`: ID (nonce) of the proposal to cancel - /// - /// Weight: Charges for MAX call size and MAX expired proposals, refunds based on actual #[pallet::call_index(3)] - #[pallet::weight(::WeightInfo::cancel( - T::MaxCallSize::get(), - T::MaxTotalProposalsInStorage::get() - ))] - #[allow(clippy::useless_conversion)] + #[pallet::weight(::WeightInfo::cancel())] pub fn cancel( origin: OriginFor, multisig_address: T::AccountId, proposal_id: u32, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let canceller = ensure_signed(origin)?; - // Auto-cleanup expired proposals on any multisig activity - // Returns count of proposals in storage (which determines iteration cost) - let iterated_count = - Self::auto_cleanup_expired_proposals(&multisig_address, &canceller); - - // Get proposal let proposal = Proposals::::get(&multisig_address, proposal_id) .ok_or(Error::::ProposalNotFound)?; - // Calculate actual weight based on real call size and actual storage size - // We charge for worst-case (e=Max), but refund based on actual storage size - let actual_call_size = proposal.call.len() as u32; - let actual_weight = ::WeightInfo::cancel(actual_call_size, iterated_count); - - // Check if caller is the proposer ensure!(canceller == proposal.proposer, Error::::NotProposer); // Check if proposal is still active @@ -832,8 +781,7 @@ pub mod pallet { proposal_id, }); - // Return actual weight (refund overpayment) - Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) + Ok(()) } /// Remove expired proposals and return deposits to proposers @@ -1012,6 +960,58 @@ pub mod pallet { Ok(()) } + + /// Clean up expired proposals for a multisig + /// + /// Removes all Active proposals that have expired and returns deposits to proposers. + /// Can be called by anyone - the wallet should call this when it detects expired proposals. + /// + /// Parameters: + /// - `multisig_address`: The multisig account to clean up + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::cleanup_expired_proposals( + T::MaxTotalProposalsInStorage::get() + ))] + pub fn cleanup_expired_proposals( + origin: OriginFor, + multisig_address: T::AccountId, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + + ensure!(Multisigs::::contains_key(&multisig_address), Error::::MultisigNotFound); + + let current_block = frame_system::Pallet::::block_number(); + let mut iterated_count = 0u32; + let mut expired_proposals: Vec<(u32, T::AccountId, BalanceOf)> = Vec::new(); + + for (id, proposal) in Proposals::::iter_prefix(&multisig_address) { + iterated_count += 1; + if proposal.status == ProposalStatus::Active && current_block > proposal.expiry { + expired_proposals.push((id, proposal.proposer, proposal.deposit)); + } + } + + for (id, expired_proposer, deposit) in expired_proposals.iter() { + Self::remove_proposal_and_return_deposit( + &multisig_address, + *id, + expired_proposer, + *deposit, + ); + + Self::deposit_event(Event::ProposalRemoved { + multisig_address: multisig_address.clone(), + proposal_id: *id, + proposer: expired_proposer.clone(), + removed_by: caller.clone(), + }); + } + + let actual_weight = + ::WeightInfo::cleanup_expired_proposals(iterated_count); + + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) + } } impl Pallet { @@ -1057,48 +1057,6 @@ pub mod pallet { Ok(multisig_data) } - /// Auto-cleanup expired proposals at the start of any multisig activity - /// This is the primary cleanup mechanism for active multisigs - /// Returns deposits to original proposers and emits cleanup events - fn auto_cleanup_expired_proposals( - multisig_address: &T::AccountId, - caller: &T::AccountId, - ) -> u32 { - let current_block = frame_system::Pallet::::block_number(); - let mut iterated_count = 0u32; - let mut expired_proposals: Vec<(u32, T::AccountId, BalanceOf)> = Vec::new(); - - // Iterate through all proposals to count them AND identify expired ones - for (id, proposal) in Proposals::::iter_prefix(multisig_address) { - iterated_count += 1; - if proposal.status == ProposalStatus::Active && current_block > proposal.expiry { - expired_proposals.push((id, proposal.proposer, proposal.deposit)); - } - } - - // Remove expired proposals and return deposits - for (id, expired_proposer, deposit) in expired_proposals.iter() { - Self::remove_proposal_and_return_deposit( - multisig_address, - *id, - expired_proposer, - *deposit, - ); - - // Emit event for each removed proposal - Self::deposit_event(Event::ProposalRemoved { - multisig_address: multisig_address.clone(), - proposal_id: *id, - proposer: expired_proposer.clone(), - removed_by: caller.clone(), - }); - } - - // Return total number of proposals iterated (not cleaned) - // This reflects the actual storage read cost - iterated_count - } - /// Decrement proposal counters (active_proposals and per-signer counter) /// Used when removing proposals from storage fn decrement_proposal_counters(multisig_address: &T::AccountId, proposer: &T::AccountId) { diff --git a/pallets/multisig/src/tests.rs b/pallets/multisig/src/tests.rs index 16b28ace..529a6c64 100644 --- a/pallets/multisig/src/tests.rs +++ b/pallets/multisig/src/tests.rs @@ -821,7 +821,7 @@ fn only_active_proposals_remain_in_storage() { } #[test] -fn auto_cleanup_allows_new_proposals() { +fn cleanup_expired_proposals_allows_new_proposals() { new_test_ext().execute_with(|| { System::set_block_number(1); @@ -843,7 +843,6 @@ fn auto_cleanup_allows_new_proposals() { 100 )); } - // Bob: 10 Active (at per-signer limit) // Bob cannot create more (at limit) assert_noop!( @@ -859,7 +858,28 @@ fn auto_cleanup_allows_new_proposals() { // Move past expiry System::set_block_number(101); - // Now Bob can create new - propose() auto-cleans expired + // Bob still cannot create new - must call cleanup first + assert_noop!( + Multisig::propose( + RuntimeOrigin::signed(bob()), + multisig_address.clone(), + make_call(vec![99]), + 200 + ), + Error::::TooManyProposalsPerSigner + ); + + // Call cleanup_expired_proposals + assert_ok!(Multisig::cleanup_expired_proposals( + RuntimeOrigin::signed(bob()), + multisig_address.clone() + )); + + // Verify old proposals were removed + let count = crate::Proposals::::iter_prefix(&multisig_address).count(); + assert_eq!(count, 0); + + // Now Bob can create new assert_ok!(Multisig::propose( RuntimeOrigin::signed(bob()), multisig_address.clone(), @@ -867,9 +887,8 @@ fn auto_cleanup_allows_new_proposals() { 200 )); - // Verify old proposals were removed let count = crate::Proposals::::iter_prefix(&multisig_address).count(); - assert_eq!(count, 1); // Only the new one remains + assert_eq!(count, 1); }); } @@ -1271,15 +1290,14 @@ fn propose_with_threshold_two_waits_for_approval() { } #[test] -fn auto_cleanup_on_approve_and_cancel() { +fn cleanup_expired_proposals_works() { new_test_ext().execute_with(|| { System::set_block_number(1); let creator = alice(); let signers = vec![alice(), bob(), charlie()]; - let threshold = 3; // Need all 3 signers - prevents auto-execution during test + let threshold = 3; - // Create multisig assert_ok!(Multisig::create_multisig( RuntimeOrigin::signed(creator.clone()), signers.clone(), @@ -1288,63 +1306,61 @@ fn auto_cleanup_on_approve_and_cancel() { let multisig_address = Multisig::derive_multisig_address(&signers, 0); - // Create two proposals + // Create proposals with different expiries assert_ok!(Multisig::propose( RuntimeOrigin::signed(alice()), multisig_address.clone(), make_call(vec![1]), - 100 // expires at block 100 + 100 )); assert_ok!(Multisig::propose( RuntimeOrigin::signed(bob()), multisig_address.clone(), make_call(vec![2]), - 200 // expires at block 200 + 200 )); - // Verify both proposals exist + assert_ok!(Multisig::propose( + RuntimeOrigin::signed(charlie()), + multisig_address.clone(), + make_call(vec![3]), + 300 + )); + + // Verify all proposals exist assert!(Proposals::::get(&multisig_address, 0).is_some()); assert!(Proposals::::get(&multisig_address, 1).is_some()); + assert!(Proposals::::get(&multisig_address, 2).is_some()); - // Move time forward past first proposal expiry + // Move past first proposal expiry System::set_block_number(101); - // Charlie approves proposal #1 (should trigger auto-cleanup of proposal #0) - // Note: Bob is the proposer of #1, so Charlie must approve - assert_ok!(Multisig::approve( - RuntimeOrigin::signed(charlie()), - multisig_address.clone(), - 1 + // Call cleanup - should remove only expired proposal #0 + assert_ok!(Multisig::cleanup_expired_proposals( + RuntimeOrigin::signed(dave()), + multisig_address.clone() )); - // Verify proposal #0 was auto-cleaned assert!(Proposals::::get(&multisig_address, 0).is_none()); - // Proposal #1 still exists (not expired, waiting for approval) assert!(Proposals::::get(&multisig_address, 1).is_some()); + assert!(Proposals::::get(&multisig_address, 2).is_some()); - // Create another proposal that will expire - assert_ok!(Multisig::propose( - RuntimeOrigin::signed(alice()), - multisig_address.clone(), - make_call(vec![3]), - 150 // expires at block 150 - )); - - // Move time forward past proposal #2 expiry - System::set_block_number(151); + // Move past second proposal expiry + System::set_block_number(201); - // Charlie cancels proposal #1 (should trigger auto-cleanup of proposal #2) - assert_ok!(Multisig::cancel(RuntimeOrigin::signed(bob()), multisig_address.clone(), 1)); + // Call cleanup - should remove proposal #1 + assert_ok!(Multisig::cleanup_expired_proposals( + RuntimeOrigin::signed(dave()), + multisig_address.clone() + )); - // Verify proposal #2 was auto-cleaned - assert!(Proposals::::get(&multisig_address, 2).is_none()); - // Proposal #1 was cancelled assert!(Proposals::::get(&multisig_address, 1).is_none()); + assert!(Proposals::::get(&multisig_address, 2).is_some()); - // Verify active_proposals counter is correct (should be 0) + // Verify active_proposals counter is correct let multisig_data = Multisigs::::get(&multisig_address).unwrap(); - assert_eq!(multisig_data.active_proposals, 0); + assert_eq!(multisig_data.active_proposals, 1); }); } diff --git a/pallets/multisig/src/weights.rs b/pallets/multisig/src/weights.rs index 13baa5ba..7f581f31 100644 --- a/pallets/multisig/src/weights.rs +++ b/pallets/multisig/src/weights.rs @@ -19,9 +19,9 @@ //! Autogenerated weights for `pallet_multisig` //! //! 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: `[]` +//! DATE: 2026-02-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `coldbook.local`, CPU: `` +//! HOSTNAME: `Macintosh.local`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -48,14 +48,15 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_multisig`. pub trait WeightInfo { fn create_multisig() -> Weight; - fn propose(c: u32, e: u32, ) -> Weight; - fn propose_high_security(c: u32, e: u32, ) -> Weight; - fn approve(c: u32, e: u32, ) -> Weight; + fn propose(c: u32, ) -> Weight; + fn propose_high_security(c: u32, ) -> Weight; + fn approve(c: u32, ) -> Weight; fn approve_and_execute(c: u32, ) -> Weight; - fn cancel(c: u32, e: u32, ) -> Weight; + fn cancel() -> Weight; fn remove_expired() -> Weight; fn claim_deposits(p: u32, ) -> Weight; fn dissolve_multisig() -> Weight; + fn cleanup_expired_proposals(e: u32, ) -> Weight; } /// Weights for `pallet_multisig` using the Substrate node and recommended hardware. @@ -69,8 +70,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `152` // Estimated: `10389` - // Minimum execution time: 190_000_000 picoseconds. - Weight::from_parts(196_000_000, 10389) + // Minimum execution time: 289_000_000 picoseconds. + Weight::from_parts(295_000_000, 10389) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -78,107 +79,82 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:201 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn propose(c: u32, e: u32, ) -> Weight { + fn propose(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `610 + e * (215 ±0)` - // Estimated: `17022 + e * (16032 ±0)` - // Minimum execution time: 77_000_000 picoseconds. - Weight::from_parts(61_728_409, 17022) - // Standard Error: 508 - .saturating_add(Weight::from_parts(3_081, 0).saturating_mul(c.into())) - // Standard Error: 25_716 - .saturating_add(Weight::from_parts(14_354_502, 0).saturating_mul(e.into())) + // Measured: `690` + // Estimated: `17022` + // Minimum execution time: 103_000_000 picoseconds. + Weight::from_parts(112_844_691, 17022) + // Standard Error: 104 + .saturating_add(Weight::from_parts(469, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(e.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:201 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn propose_high_security(c: u32, e: u32, ) -> Weight { + fn propose_high_security(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `770 + e * (215 ±0)` - // Estimated: `17022 + e * (16032 ±0)` - // Minimum execution time: 46_000_000 picoseconds. - Weight::from_parts(46_636_454, 17022) - // Standard Error: 504 - .saturating_add(Weight::from_parts(282, 0).saturating_mul(c.into())) - // Standard Error: 25_536 - .saturating_add(Weight::from_parts(14_620_974, 0).saturating_mul(e.into())) + // Measured: `850` + // Estimated: `17022` + // Minimum execution time: 52_000_000 picoseconds. + Weight::from_parts(66_739_684, 17022) + // Standard Error: 112 + .saturating_add(Weight::from_parts(248, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(e.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:202 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn approve(_c: u32, e: u32, ) -> Weight { + fn approve(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `657 + c * (1 ±0) + e * (215 ±0)` - // Estimated: `33054 + e * (16032 ±0)` - // Minimum execution time: 25_000_000 picoseconds. - Weight::from_parts(49_516_534, 33054) - // Standard Error: 26_699 - .saturating_add(Weight::from_parts(14_041_478, 0).saturating_mul(e.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(e.into()))) + // Measured: `766 + c * (1 ±0)` + // Estimated: `17022` + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(23_622_062, 17022) + // Standard Error: 50 + .saturating_add(Weight::from_parts(653, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:2 w:1) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. fn approve_and_execute(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `790 + c * (1 ±0)` - // Estimated: `33054` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(35_686_901, 33054) - // Standard Error: 95 - .saturating_add(Weight::from_parts(1_060, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `734 + c * (1 ±0)` + // Estimated: `17022` + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(43_143_716, 17022) + // Standard Error: 103 + .saturating_add(Weight::from_parts(1_055, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: `Multisig::Proposals` (r:202 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) - /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn cancel(c: u32, e: u32, ) -> Weight { + fn cancel() -> Weight { // Proof Size summary in bytes: - // Measured: `625 + c * (1 ±0) + e * (215 ±0)` - // Estimated: `33054 + e * (16032 ±0)` - // Minimum execution time: 28_000_000 picoseconds. - Weight::from_parts(33_000_000, 33054) - // Standard Error: 229 - .saturating_add(Weight::from_parts(3_462, 0).saturating_mul(c.into())) - // Standard Error: 11_632 - .saturating_add(Weight::from_parts(13_752_458, 0).saturating_mul(e.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(e.into()))) + // Measured: `764` + // Estimated: `17022` + // Minimum execution time: 26_000_000 picoseconds. + Weight::from_parts(32_000_000, 17022) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) @@ -188,8 +164,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `764` // Estimated: `17022` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(26_000_000, 17022) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 17022) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -202,10 +178,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `625 + p * (237 ±0)` // Estimated: `17022 + p * (16032 ±0)` - // Minimum execution time: 23_000_000 picoseconds. - Weight::from_parts(27_178_826, 17022) - // Standard Error: 23_441 - .saturating_add(Weight::from_parts(13_739_383, 0).saturating_mul(p.into())) + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(18_223_716, 17022) + // Standard Error: 42_377 + .saturating_add(Weight::from_parts(19_045_633, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) @@ -222,11 +198,30 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `538` // Estimated: `17022` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(26_000_000, 17022) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(32_000_000, 17022) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) + /// Storage: `Multisig::Proposals` (r:201 w:200) + /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) + /// The range of component `e` is `[0, 200]`. + fn cleanup_expired_proposals(e: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `639 + e * (215 ±0)` + // Estimated: `17022 + e * (16032 ±0)` + // Minimum execution time: 10_000_000 picoseconds. + Weight::from_parts(20_533_771, 17022) + // Standard Error: 45_440 + .saturating_add(Weight::from_parts(19_038_306, 0).saturating_mul(e.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(e.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(e.into()))) + .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) + } } // For backwards compatibility and tests. @@ -239,8 +234,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `152` // Estimated: `10389` - // Minimum execution time: 190_000_000 picoseconds. - Weight::from_parts(196_000_000, 10389) + // Minimum execution time: 289_000_000 picoseconds. + Weight::from_parts(295_000_000, 10389) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -248,107 +243,82 @@ impl WeightInfo for () { /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:201 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn propose(c: u32, e: u32, ) -> Weight { + fn propose(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `610 + e * (215 ±0)` - // Estimated: `17022 + e * (16032 ±0)` - // Minimum execution time: 77_000_000 picoseconds. - Weight::from_parts(61_728_409, 17022) - // Standard Error: 508 - .saturating_add(Weight::from_parts(3_081, 0).saturating_mul(c.into())) - // Standard Error: 25_716 - .saturating_add(Weight::from_parts(14_354_502, 0).saturating_mul(e.into())) + // Measured: `690` + // Estimated: `17022` + // Minimum execution time: 103_000_000 picoseconds. + Weight::from_parts(112_844_691, 17022) + // Standard Error: 104 + .saturating_add(Weight::from_parts(469, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(e.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) /// Storage: `ReversibleTransfers::HighSecurityAccounts` (r:1 w:0) /// Proof: `ReversibleTransfers::HighSecurityAccounts` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:201 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn propose_high_security(c: u32, e: u32, ) -> Weight { + fn propose_high_security(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `770 + e * (215 ±0)` - // Estimated: `17022 + e * (16032 ±0)` - // Minimum execution time: 46_000_000 picoseconds. - Weight::from_parts(46_636_454, 17022) - // Standard Error: 504 - .saturating_add(Weight::from_parts(282, 0).saturating_mul(c.into())) - // Standard Error: 25_536 - .saturating_add(Weight::from_parts(14_620_974, 0).saturating_mul(e.into())) + // Measured: `850` + // Estimated: `17022` + // Minimum execution time: 52_000_000 picoseconds. + Weight::from_parts(66_739_684, 17022) + // Standard Error: 112 + .saturating_add(Weight::from_parts(248, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(e.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:202 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn approve(_c: u32, e: u32, ) -> Weight { + fn approve(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `657 + c * (1 ±0) + e * (215 ±0)` - // Estimated: `33054 + e * (16032 ±0)` - // Minimum execution time: 25_000_000 picoseconds. - Weight::from_parts(49_516_534, 33054) - // Standard Error: 26_699 - .saturating_add(Weight::from_parts(14_041_478, 0).saturating_mul(e.into())) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(e.into()))) + // Measured: `766 + c * (1 ±0)` + // Estimated: `17022` + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(23_622_062, 17022) + // Standard Error: 50 + .saturating_add(Weight::from_parts(653, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) - /// Storage: `Multisig::Proposals` (r:2 w:1) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// The range of component `c` is `[0, 10140]`. fn approve_and_execute(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `790 + c * (1 ±0)` - // Estimated: `33054` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(35_686_901, 33054) - // Standard Error: 95 - .saturating_add(Weight::from_parts(1_060, 0).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Measured: `734 + c * (1 ±0)` + // Estimated: `17022` + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(43_143_716, 17022) + // Standard Error: 103 + .saturating_add(Weight::from_parts(1_055, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: `Multisig::Proposals` (r:202 w:201) + /// Storage: `Multisig::Proposals` (r:1 w:1) /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) - /// The range of component `c` is `[0, 10140]`. - /// The range of component `e` is `[0, 200]`. - fn cancel(c: u32, e: u32, ) -> Weight { + fn cancel() -> Weight { // Proof Size summary in bytes: - // Measured: `625 + c * (1 ±0) + e * (215 ±0)` - // Estimated: `33054 + e * (16032 ±0)` - // Minimum execution time: 28_000_000 picoseconds. - Weight::from_parts(33_000_000, 33054) - // Standard Error: 229 - .saturating_add(Weight::from_parts(3_462, 0).saturating_mul(c.into())) - // Standard Error: 11_632 - .saturating_add(Weight::from_parts(13_752_458, 0).saturating_mul(e.into())) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(e.into()))) + // Measured: `764` + // Estimated: `17022` + // Minimum execution time: 26_000_000 picoseconds. + Weight::from_parts(32_000_000, 17022) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(e.into()))) - .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) @@ -358,8 +328,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `764` // Estimated: `17022` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(26_000_000, 17022) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 17022) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -372,10 +342,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `625 + p * (237 ±0)` // Estimated: `17022 + p * (16032 ±0)` - // Minimum execution time: 23_000_000 picoseconds. - Weight::from_parts(27_178_826, 17022) - // Standard Error: 23_441 - .saturating_add(Weight::from_parts(13_739_383, 0).saturating_mul(p.into())) + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(18_223_716, 17022) + // Standard Error: 42_377 + .saturating_add(Weight::from_parts(19_045_633, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) @@ -392,9 +362,28 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `538` // Estimated: `17022` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(26_000_000, 17022) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(32_000_000, 17022) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(6924), added: 9399, mode: `MaxEncodedLen`) + /// Storage: `Multisig::Proposals` (r:201 w:200) + /// Proof: `Multisig::Proposals` (`max_values`: None, `max_size`: Some(13557), added: 16032, mode: `MaxEncodedLen`) + /// The range of component `e` is `[0, 200]`. + fn cleanup_expired_proposals(e: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `639 + e * (215 ±0)` + // Estimated: `17022 + e * (16032 ±0)` + // Minimum execution time: 10_000_000 picoseconds. + Weight::from_parts(20_533_771, 17022) + // Standard Error: 45_440 + .saturating_add(Weight::from_parts(19_038_306, 0).saturating_mul(e.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(e.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(e.into()))) + .saturating_add(Weight::from_parts(0, 16032).saturating_mul(e.into())) + } }