diff --git a/Cargo.lock b/Cargo.lock index edab4cd165..18ee1cd90f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6919,7 +6919,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-preimage", - "pallet-proxy 38.0.0", + "pallet-proxy 40.1.0", "pallet-registry", "pallet-safe-mode", "pallet-scheduler", @@ -6935,7 +6935,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", - "pallet-utility 38.0.0", + "pallet-utility 40.0.0", "parity-scale-codec", "polkadot-runtime-common", "precompile-utils", @@ -7952,18 +7952,13 @@ dependencies = [ [[package]] name = "pallet-proxy" -version = "38.0.0" +version = "40.1.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", "pallet-balances", - "pallet-utility 38.0.0", + "pallet-utility 40.0.0", "parity-scale-codec", + "polkadot-sdk-frame", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", "subtensor-macros", ] @@ -8018,8 +8013,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "pallet-proxy 40.1.0", - "pallet-utility 40.0.0", + "pallet-proxy 40.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2503-6)", + "pallet-utility 40.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2503-6)", "parity-scale-codec", "scale-info", "sp-arithmetic", @@ -8139,7 +8134,7 @@ dependencies = [ "pallet-preimage", "pallet-scheduler", "pallet-subtensor-swap", - "pallet-utility 38.0.0", + "pallet-utility 40.0.0", "parity-scale-codec", "parity-util-mem", "polkadot-runtime-common", @@ -8319,7 +8314,7 @@ dependencies = [ [[package]] name = "pallet-utility" -version = "38.0.0" +version = "40.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -13718,7 +13713,7 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", - "pallet-proxy 38.0.0", + "pallet-proxy 40.1.0", "pallet-subtensor", "pallet-subtensor-swap", "precompile-utils", diff --git a/Cargo.toml b/Cargo.toml index 3415b8624d..2fb9ea9644 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,7 @@ expander = "2" ahash = { version = "0.8", default-features = false } regex = { version = "1.11.1", default-features = false } +frame = { package = "polkadot-sdk-frame", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false } frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false } frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false } frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false } diff --git a/pallets/proxy/Cargo.toml b/pallets/proxy/Cargo.toml index 4f5dddfed1..ffca8ad8a7 100644 --- a/pallets/proxy/Cargo.toml +++ b/pallets/proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-proxy" -version = "38.0.0" +version = "40.1.0" authors = ["Bittensor Nucleus Team"] edition.workspace = true license = "Apache-2.0" @@ -15,43 +15,29 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { workspace = true, features = ["derive", "max-encoded-len"] } +codec = { workspace = true, features = ["max-encoded-len"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { workspace = true, features = ["derive"] } -frame-benchmarking = { workspace = true, optional = true } -frame-support.workspace = true -frame-system.workspace = true -sp-io.workspace = true -sp-runtime.workspace = true subtensor-macros.workspace = true [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +pallet-balances = { default-features = true, workspace = true } +pallet-utility = { default-features = true, workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "scale-info/std", - "sp-io/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "frame/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-utility/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", "pallet-balances/try-runtime", "pallet-utility/try-runtime", ] diff --git a/pallets/proxy/src/benchmarking.rs b/pallets/proxy/src/benchmarking.rs index 0e0d89f03e..ff881912d1 100644 --- a/pallets/proxy/src/benchmarking.rs +++ b/pallets/proxy/src/benchmarking.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// + // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 -// + // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0/ +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -18,13 +18,14 @@ // Benchmarks for Proxy Pallet #![cfg(feature = "runtime-benchmarks")] +#![allow(clippy::arithmetic_side_effects)] use super::*; use crate::Pallet as Proxy; use alloc::{boxed::Box, vec}; -use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; -use frame_system::{RawOrigin, pallet_prelude::BlockNumberFor}; -use sp_runtime::traits::{Bounded, CheckedDiv}; +use frame::benchmarking::prelude::{ + BenchmarkError, RawOrigin, account, benchmarks, impl_test_function, whitelisted_caller, +}; const SEED: u32 = 0; @@ -32,15 +33,13 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } -fn half_max_balance() -> BalanceOf { - BalanceOf::::max_value() - .checked_div(&BalanceOf::::from(2_u32)) - .unwrap_or_else(BalanceOf::::max_value) +fn assert_has_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_has_event(generic_event.into()); } fn add_proxies(n: u32, maybe_who: Option) -> Result<(), &'static str> { let caller = maybe_who.unwrap_or_else(whitelisted_caller); - T::Currency::make_free_balance_be(&caller, half_max_balance::()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); for i in 0..n { let real = T::Lookup::unlookup(account("target", i, SEED)); @@ -61,12 +60,12 @@ fn add_announcements( ) -> Result<(), &'static str> { let caller = maybe_who.unwrap_or_else(|| account("caller", 0, SEED)); let caller_lookup = T::Lookup::unlookup(caller.clone()); - T::Currency::make_free_balance_be(&caller, half_max_balance::()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); let real = if let Some(real) = maybe_real { real } else { let real = account("real", 0, SEED); - T::Currency::make_free_balance_be(&real, half_max_balance::()); + T::Currency::make_free_balance_be(&real, BalanceOf::::max_value() / 2u32.into()); Proxy::::add_proxy( RawOrigin::Signed(real.clone()).into(), caller_lookup, @@ -86,157 +85,256 @@ fn add_announcements( Ok(()) } -benchmarks! { - proxy { - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; - let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); - T::Currency::make_free_balance_be(&caller, half_max_balance::()); +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn proxy(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); + // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); let real_lookup = T::Lookup::unlookup(real); - let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); - }: _(RawOrigin::Signed(caller), real_lookup, Some(T::ProxyType::default()), Box::new(call)) - verify { - assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()) + let call: ::RuntimeCall = + frame_system::Call::::remark { remark: vec![] }.into(); + + #[extrinsic_call] + _( + RawOrigin::Signed(caller), + real_lookup, + Some(T::ProxyType::default()), + Box::new(call), + ); + + assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()); + + Ok(()) } - proxy_announced { - let a in 0 .. T::MaxPending::get().saturating_sub(1); - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + #[benchmark] + fn proxy_announced( + a: Linear<0, { T::MaxPending::get() - 1 }>, + p: Linear<1, { T::MaxProxies::get() - 1 }>, + ) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; + // In this case the caller is the "target" proxy let caller: T::AccountId = account("pure", 0, SEED); - let delegate: T::AccountId = account("target", p.saturating_sub(1), SEED); + let delegate: T::AccountId = account("target", p - 1, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - T::Currency::make_free_balance_be(&delegate, half_max_balance::()); + T::Currency::make_free_balance_be(&delegate, BalanceOf::::max_value() / 2u32.into()); + // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); let real_lookup = T::Lookup::unlookup(real); - let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + let call: ::RuntimeCall = + frame_system::Call::::remark { remark: vec![] }.into(); Proxy::::announce( RawOrigin::Signed(delegate.clone()).into(), real_lookup.clone(), T::CallHasher::hash_of(&call), )?; add_announcements::(a, Some(delegate.clone()), None)?; - }: _(RawOrigin::Signed(caller), delegate_lookup, real_lookup, Some(T::ProxyType::default()), Box::new(call)) - verify { - assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()) + + #[extrinsic_call] + _( + RawOrigin::Signed(caller), + delegate_lookup, + real_lookup, + Some(T::ProxyType::default()), + Box::new(call), + ); + + assert_last_event::(Event::ProxyExecuted { result: Ok(()) }.into()); + + Ok(()) } - remove_announcement { - let a in 0 .. T::MaxPending::get().saturating_sub(1); - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; - let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); - T::Currency::make_free_balance_be(&caller, half_max_balance::()); + #[benchmark] + fn remove_announcement( + a: Linear<0, { T::MaxPending::get() - 1 }>, + p: Linear<1, { T::MaxProxies::get() - 1 }>, + ) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); + // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); let real_lookup = T::Lookup::unlookup(real); - let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + let call: ::RuntimeCall = + frame_system::Call::::remark { remark: vec![] }.into(); Proxy::::announce( RawOrigin::Signed(caller.clone()).into(), real_lookup.clone(), T::CallHasher::hash_of(&call), )?; add_announcements::(a, Some(caller.clone()), None)?; - }: _(RawOrigin::Signed(caller.clone()), real_lookup, T::CallHasher::hash_of(&call)) - verify { + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + real_lookup, + T::CallHasher::hash_of(&call), + ); + let (announcements, _) = Announcements::::get(&caller); assert_eq!(announcements.len() as u32, a); + + Ok(()) } - reject_announcement { - let a in 0 .. T::MaxPending::get().saturating_sub(1); - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; - let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); + #[benchmark] + fn reject_announcement( + a: Linear<0, { T::MaxPending::get() - 1 }>, + p: Linear<1, { T::MaxProxies::get() - 1 }>, + ) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("target", p - 1, SEED); let caller_lookup = T::Lookup::unlookup(caller.clone()); - T::Currency::make_free_balance_be(&caller, half_max_balance::()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); + // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); let real_lookup = T::Lookup::unlookup(real.clone()); - let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + let call: ::RuntimeCall = + frame_system::Call::::remark { remark: vec![] }.into(); Proxy::::announce( RawOrigin::Signed(caller.clone()).into(), real_lookup, T::CallHasher::hash_of(&call), )?; add_announcements::(a, Some(caller.clone()), None)?; - }: _(RawOrigin::Signed(real), caller_lookup, T::CallHasher::hash_of(&call)) - verify { + + #[extrinsic_call] + _( + RawOrigin::Signed(real), + caller_lookup, + T::CallHasher::hash_of(&call), + ); + let (announcements, _) = Announcements::::get(&caller); assert_eq!(announcements.len() as u32, a); + + Ok(()) } - announce { - let a in 0 .. T::MaxPending::get().saturating_sub(1); - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; - let caller: T::AccountId = account("target", p.saturating_sub(1), SEED); - T::Currency::make_free_balance_be(&caller, half_max_balance::()); + #[benchmark] + fn announce( + a: Linear<0, { T::MaxPending::get() - 1 }>, + p: Linear<1, { T::MaxProxies::get() - 1 }>, + ) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; + // In this case the caller is the "target" proxy + let caller: T::AccountId = account("target", p - 1, SEED); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); + // ... and "real" is the traditional caller. This is not a typo. let real: T::AccountId = whitelisted_caller(); let real_lookup = T::Lookup::unlookup(real.clone()); add_announcements::(a, Some(caller.clone()), None)?; - let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); + let call: ::RuntimeCall = + frame_system::Call::::remark { remark: vec![] }.into(); let call_hash = T::CallHasher::hash_of(&call); - }: _(RawOrigin::Signed(caller.clone()), real_lookup, call_hash) - verify { - assert_last_event::(Event::Announced { real, proxy: caller, call_hash }.into()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), real_lookup, call_hash); + + assert_last_event::( + Event::Announced { + real, + proxy: caller, + call_hash, + } + .into(), + ); + + Ok(()) } - add_proxy { - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + #[benchmark] + fn add_proxy(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); let real = T::Lookup::unlookup(account("target", T::MaxProxies::get(), SEED)); - }: _( - RawOrigin::Signed(caller.clone()), - real, - T::ProxyType::default(), - BlockNumberFor::::zero() - ) - verify { + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + real, + T::ProxyType::default(), + BlockNumberFor::::zero(), + ); + let (proxies, _) = Proxies::::get(caller); - assert_eq!(proxies.len() as u32, p.saturating_add(1)); + assert_eq!(proxies.len() as u32, p + 1); + + Ok(()) } - remove_proxy { - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + #[benchmark] + fn remove_proxy(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); let delegate = T::Lookup::unlookup(account("target", 0, SEED)); - }: _( - RawOrigin::Signed(caller.clone()), - delegate, - T::ProxyType::default(), - BlockNumberFor::::zero() - ) - verify { + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + delegate, + T::ProxyType::default(), + BlockNumberFor::::zero(), + ); + let (proxies, _) = Proxies::::get(caller); - assert_eq!(proxies.len() as u32, p.saturating_sub(1)); + assert_eq!(proxies.len() as u32, p - 1); + + Ok(()) } - remove_proxies { - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + #[benchmark] + fn remove_proxies(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller.clone())) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + let (proxies, _) = Proxies::::get(caller); assert_eq!(proxies.len() as u32, 0); + + Ok(()) } - create_pure { - let p in 1 .. (T::MaxProxies::get().saturating_sub(1)) => add_proxies::(p, None)?; + #[benchmark] + fn create_pure(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> { + add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); - }: _( - RawOrigin::Signed(caller.clone()), - T::ProxyType::default(), - BlockNumberFor::::zero(), - 0 - ) - verify { + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + T::ProxyType::default(), + BlockNumberFor::::zero(), + 0, + ); + let pure_account = Pallet::::pure_account(&caller, &T::ProxyType::default(), 0, None); - assert_last_event::(Event::PureCreated { - pure: pure_account, - who: caller, - proxy_type: T::ProxyType::default(), - disambiguation_index: 0, - }.into()); - } + assert_last_event::( + Event::PureCreated { + pure: pure_account, + who: caller, + proxy_type: T::ProxyType::default(), + disambiguation_index: 0, + } + .into(), + ); - kill_pure { - let p in 0 .. (T::MaxProxies::get().saturating_sub(2)); + Ok(()) + } + #[benchmark] + fn kill_pure(p: Linear<0, { T::MaxProxies::get() - 2 }>) -> Result<(), BenchmarkError> { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); @@ -244,17 +342,149 @@ benchmarks! { RawOrigin::Signed(whitelisted_caller()).into(), T::ProxyType::default(), BlockNumberFor::::zero(), - 0 + 0, )?; - let height = system::Pallet::::block_number(); - let ext_index = system::Pallet::::extrinsic_index().unwrap_or(0); + let height = T::BlockNumberProvider::current_block_number(); + let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or(0); let pure_account = Pallet::::pure_account(&caller, &T::ProxyType::default(), 0, None); add_proxies::(p, Some(pure_account.clone()))?; - ensure!(Proxies::::contains_key(&pure_account), "pure proxy not created"); - }: _(RawOrigin::Signed(pure_account.clone()), caller_lookup, T::ProxyType::default(), 0, height, ext_index) - verify { + ensure!( + Proxies::::contains_key(&pure_account), + "pure proxy not created" + ); + + #[extrinsic_call] + _( + RawOrigin::Signed(pure_account.clone()), + caller_lookup, + T::ProxyType::default(), + 0, + height, + ext_index, + ); + assert!(!Proxies::::contains_key(&pure_account)); + + Ok(()) + } + + #[benchmark] + fn poke_deposit() -> Result<(), BenchmarkError> { + // Create accounts using the same pattern as other benchmarks + let account_1: T::AccountId = account("account", 1, SEED); + let account_2: T::AccountId = account("account", 2, SEED); + let account_3: T::AccountId = account("account", 3, SEED); + + // Fund accounts + T::Currency::make_free_balance_be(&account_1, BalanceOf::::max_value() / 100u8.into()); + T::Currency::make_free_balance_be(&account_2, BalanceOf::::max_value() / 100u8.into()); + T::Currency::make_free_balance_be(&account_3, BalanceOf::::max_value() / 100u8.into()); + + // Add proxy relationships + Proxy::::add_proxy( + RawOrigin::Signed(account_1.clone()).into(), + T::Lookup::unlookup(account_2.clone()), + T::ProxyType::default(), + BlockNumberFor::::zero(), + )?; + Proxy::::add_proxy( + RawOrigin::Signed(account_2.clone()).into(), + T::Lookup::unlookup(account_3.clone()), + T::ProxyType::default(), + BlockNumberFor::::zero(), + )?; + let (proxies, initial_proxy_deposit) = Proxies::::get(&account_2); + assert!(!initial_proxy_deposit.is_zero()); + assert_eq!( + initial_proxy_deposit, + T::Currency::reserved_balance(&account_2) + ); + + // Create announcement + Proxy::::announce( + RawOrigin::Signed(account_2.clone()).into(), + T::Lookup::unlookup(account_1.clone()), + T::CallHasher::hash_of(&("add_announcement", 1)), + )?; + let (announcements, initial_announcement_deposit) = Announcements::::get(&account_2); + assert!(!initial_announcement_deposit.is_zero()); + assert_eq!( + initial_announcement_deposit.saturating_add(initial_proxy_deposit), + T::Currency::reserved_balance(&account_2) + ); + + // Artificially inflate deposits and reserve the extra amount + let extra_proxy_deposit = initial_proxy_deposit; // Double the deposit + let extra_announcement_deposit = initial_announcement_deposit; // Double the deposit + let total = extra_proxy_deposit.saturating_add(extra_announcement_deposit); + + T::Currency::reserve(&account_2, total)?; + + let initial_reserved = T::Currency::reserved_balance(&account_2); + assert_eq!(initial_reserved, total.saturating_add(total)); // Double + + // Update storage with increased deposits + Proxies::::insert( + &account_2, + ( + proxies, + initial_proxy_deposit.saturating_add(extra_proxy_deposit), + ), + ); + Announcements::::insert( + &account_2, + ( + announcements, + initial_announcement_deposit.saturating_add(extra_announcement_deposit), + ), + ); + + // Verify artificial state + let (_, inflated_proxy_deposit) = Proxies::::get(&account_2); + let (_, inflated_announcement_deposit) = Announcements::::get(&account_2); + assert_eq!( + inflated_proxy_deposit, + initial_proxy_deposit.saturating_add(extra_proxy_deposit) + ); + assert_eq!( + inflated_announcement_deposit, + initial_announcement_deposit.saturating_add(extra_announcement_deposit) + ); + + #[extrinsic_call] + _(RawOrigin::Signed(account_2.clone())); + + // Verify results + let (_, final_proxy_deposit) = Proxies::::get(&account_2); + let (_, final_announcement_deposit) = Announcements::::get(&account_2); + assert_eq!(final_proxy_deposit, initial_proxy_deposit); + assert_eq!(final_announcement_deposit, initial_announcement_deposit); + + let final_reserved = T::Currency::reserved_balance(&account_2); + assert_eq!(final_reserved, initial_reserved.saturating_sub(total)); + + // Verify events + assert_has_event::( + Event::DepositPoked { + who: account_2.clone(), + kind: DepositKind::Proxies, + old_deposit: inflated_proxy_deposit, + new_deposit: final_proxy_deposit, + } + .into(), + ); + assert_last_event::( + Event::DepositPoked { + who: account_2, + kind: DepositKind::Announcements, + old_deposit: inflated_announcement_deposit, + new_deposit: final_announcement_deposit, + } + .into(), + ); + + Ok(()) } impl_benchmark_test_suite!(Proxy, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/pallets/proxy/src/lib.rs b/pallets/proxy/src/lib.rs index 9a7aab857a..a4325bd099 100644 --- a/pallets/proxy/src/lib.rs +++ b/pallets/proxy/src/lib.rs @@ -1,13 +1,13 @@ // This file is part of Substrate. -// + // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 -// + // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0/ +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -34,24 +34,12 @@ mod tests; pub mod weights; extern crate alloc; - use alloc::{boxed::Box, vec}; -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::pallet_prelude::{Pays, Weight}; -use frame_support::{ - BoundedVec, - dispatch::GetDispatchInfo, - ensure, - traits::{Currency, Get, InstanceFilter, IsSubType, IsType, OriginTrait, ReservableCurrency}, +use frame::{ + prelude::*, + traits::{Currency, InstanceFilter, ReservableCurrency}, }; -use frame_system::{self as system, ensure_signed, pallet_prelude::BlockNumberFor}; pub use pallet::*; -use scale_info::{TypeInfo, prelude::cmp::Ordering}; -use sp_io::hashing::blake2_256; -use sp_runtime::{ - DispatchError, DispatchResult, RuntimeDebug, - traits::{Dispatchable, Hash, Saturating, StaticLookup, TrailingZeroInput, Zero}, -}; use subtensor_macros::freeze_struct; pub use weights::WeightInfo; @@ -60,6 +48,9 @@ type CallHashOf = <::CallHasher as Hash>::Output; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub type BlockNumberFor = + <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; /// The parameters under which a particular account has a proxy relationship with some other @@ -100,11 +91,29 @@ pub struct Announcement { height: BlockNumber, } -#[frame_support::pallet] +/// The type of deposit +#[derive( + Encode, + Decode, + Clone, + Copy, + Eq, + PartialEq, + RuntimeDebug, + MaxEncodedLen, + TypeInfo, + DecodeWithMemTracking, +)] +pub enum DepositKind { + /// Proxy registration deposit + Proxies, + /// Announcement deposit + Announcements, +} + +#[frame::pallet] pub mod pallet { - use super::{DispatchResult, *}; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; + use super::*; #[pallet::pallet] pub struct Pallet(_); @@ -134,7 +143,7 @@ pub mod pallet { + Member + Ord + PartialOrd - + InstanceFilter<::RuntimeCall> + + frame::traits::InstanceFilter<::RuntimeCall> + Default + MaxEncodedLen; @@ -180,6 +189,30 @@ pub mod pallet { /// into a pre-existing storage value. #[pallet::constant] type AnnouncementDepositFactor: Get>; + + /// Query the current block number. + /// + /// Must return monotonically increasing values when called from consecutive blocks. + /// Can be configured to return either: + /// - the local block number of the runtime via `frame_system::Pallet` + /// - a remote block number, eg from the relay chain through `RelaychainDataProvider` + /// - an arbitrary value through a custom implementation of the trait + /// + /// There is currently no migration provided to "hot-swap" block number providers and it may + /// result in undefined behavior when doing so. Parachains are therefore best off setting + /// this to their local block number provider if they have the pallet already deployed. + /// + /// Suggested values: + /// - Solo- and Relay-chains: `frame_system::Pallet` + /// - Parachains that may produce blocks sparingly or only when needed (on-demand): + /// - already have the pallet deployed: `frame_system::Pallet` + /// - are freshly deploying this pallet: `RelaychainDataProvider` + /// - Parachains with a reliably block production rate (PLO or bulk-coretime): + /// - already have the pallet deployed: `frame_system::Pallet` + /// - are freshly deploying this pallet: no strong recommendation. Both local and remote + /// providers can be used. Relay provider can be a bit better in cases where the + /// parachain is lagging its block production to avoid clock skew. + type BlockNumberProvider: BlockNumberProvider; } #[pallet::call] @@ -196,13 +229,11 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight({ let di = call.get_dispatch_info(); - let inner_call_weight = match di.pays_fee { - Pays::Yes => di.call_weight, - Pays::No => Weight::zero(), - }; - let base_weight = T::WeightInfo::proxy(T::MaxProxies::get()) - .saturating_add(T::DbWeight::get().reads_writes(1, 1)); - (base_weight.saturating_add(inner_call_weight), di.class) + (T::WeightInfo::proxy(T::MaxProxies::get()) + // AccountData for inner call origin accountdata. + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.call_weight), + di.class, di.pays_fee) })] pub fn proxy( origin: OriginFor, @@ -283,12 +314,12 @@ pub mod pallet { /// /// - `proxy_type`: The type of the proxy that the sender will be registered as over the /// new account. This will almost always be the most permissive `ProxyType` possible to - /// allow for maximum flexibility. + /// allow for maximum flexibility. /// - `index`: A disambiguation index, in case this is called multiple times in the same - /// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just - /// want to use `0`. + /// transaction (e.g. with `utility::batch`). Unless you're using `batch` you probably just + /// want to use `0`. /// - `delay`: The announcement period required of the initial proxy. Will generally be - /// zero. + /// zero. /// /// Fails with `Duplicate` if this has already been called in this transaction, from the /// same sender, with the same parameters. @@ -409,7 +440,7 @@ pub mod pallet { let announcement = Announcement { real: real.clone(), call_hash, - height: system::Pallet::::block_number(), + height: T::BlockNumberProvider::current_block_number(), }; Announcements::::try_mutate(&who, |(pending, deposit)| { @@ -526,7 +557,7 @@ pub mod pallet { let def = Self::find_proxy(&real, &delegate, force_proxy_type)?; let call_hash = T::CallHasher::hash_of(&call); - let now = system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); Self::edit_announcements(&delegate, |ann| { ann.real != real || ann.call_hash != call_hash @@ -538,6 +569,109 @@ pub mod pallet { Ok(()) } + + /// Poke / Adjust deposits made for proxies and announcements based on current values. + /// This can be used by accounts to possibly lower their locked amount. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// The transaction fee is waived if the deposit amount has changed. + /// + /// Emits `DepositPoked` if successful. + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::poke_deposit())] + pub fn poke_deposit(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let mut deposit_updated = false; + + // Check and update proxy deposits + Proxies::::try_mutate_exists(&who, |maybe_proxies| -> DispatchResult { + let (proxies, old_deposit) = maybe_proxies.take().unwrap_or_default(); + let maybe_new_deposit = Self::rejig_deposit( + &who, + old_deposit, + T::ProxyDepositBase::get(), + T::ProxyDepositFactor::get(), + proxies.len(), + )?; + + match maybe_new_deposit { + Some(new_deposit) if new_deposit != old_deposit => { + *maybe_proxies = Some((proxies, new_deposit)); + deposit_updated = true; + Self::deposit_event(Event::DepositPoked { + who: who.clone(), + kind: DepositKind::Proxies, + old_deposit, + new_deposit, + }); + } + Some(_) => { + *maybe_proxies = Some((proxies, old_deposit)); + } + None => { + *maybe_proxies = None; + if !old_deposit.is_zero() { + deposit_updated = true; + Self::deposit_event(Event::DepositPoked { + who: who.clone(), + kind: DepositKind::Proxies, + old_deposit, + new_deposit: BalanceOf::::zero(), + }); + } + } + } + Ok(()) + })?; + + // Check and update announcement deposits + Announcements::::try_mutate_exists(&who, |maybe_announcements| -> DispatchResult { + let (announcements, old_deposit) = maybe_announcements.take().unwrap_or_default(); + let maybe_new_deposit = Self::rejig_deposit( + &who, + old_deposit, + T::AnnouncementDepositBase::get(), + T::AnnouncementDepositFactor::get(), + announcements.len(), + )?; + + match maybe_new_deposit { + Some(new_deposit) if new_deposit != old_deposit => { + *maybe_announcements = Some((announcements, new_deposit)); + deposit_updated = true; + Self::deposit_event(Event::DepositPoked { + who: who.clone(), + kind: DepositKind::Announcements, + old_deposit, + new_deposit, + }); + } + Some(_) => { + *maybe_announcements = Some((announcements, old_deposit)); + } + None => { + *maybe_announcements = None; + if !old_deposit.is_zero() { + deposit_updated = true; + Self::deposit_event(Event::DepositPoked { + who: who.clone(), + kind: DepositKind::Announcements, + old_deposit, + new_deposit: BalanceOf::::zero(), + }); + } + } + } + Ok(()) + })?; + + Ok(if deposit_updated { + Pays::No.into() + } else { + Pays::Yes.into() + }) + } } #[pallet::event] @@ -584,6 +718,13 @@ pub mod pallet { // The index originally passed to `create_pure` when this pure proxy was created. disambiguation_index: u16, }, + /// A deposit stored for proxies or announcements was poked / updated. + DepositPoked { + who: T::AccountId, + kind: DepositKind, + old_deposit: BalanceOf, + new_deposit: BalanceOf, + }, } #[pallet::error] @@ -635,6 +776,22 @@ pub mod pallet { ), ValueQuery, >; + + #[pallet::view_functions_experimental] + impl Pallet { + /// Check if a `RuntimeCall` is allowed for a given `ProxyType`. + pub fn check_permissions( + call: ::RuntimeCall, + proxy_type: T::ProxyType, + ) -> bool { + proxy_type.filter(&call) + } + + /// Check if one `ProxyType` is a subset of another `ProxyType`. + pub fn is_superset(to_check: T::ProxyType, against: T::ProxyType) -> bool { + to_check.is_superset(&against) + } + } } impl Pallet { @@ -677,8 +834,8 @@ impl Pallet { ) -> T::AccountId { let (height, ext_index) = maybe_when.unwrap_or_else(|| { ( - system::Pallet::::block_number(), - system::Pallet::::extrinsic_index().unwrap_or_default(), + T::BlockNumberProvider::current_block_number(), + frame_system::Pallet::::extrinsic_index().unwrap_or_default(), ) }); let entropy = ( @@ -723,14 +880,10 @@ impl Pallet { .try_insert(i, proxy_def) .map_err(|_| Error::::TooMany)?; let new_deposit = Self::deposit(proxies.len() as u32); - match new_deposit.cmp(deposit) { - Ordering::Greater => { - T::Currency::reserve(delegator, new_deposit.saturating_sub(*deposit))?; - } - Ordering::Less => { - T::Currency::unreserve(delegator, deposit.saturating_sub(new_deposit)); - } - Ordering::Equal => (), + if new_deposit > *deposit { + T::Currency::reserve(delegator, new_deposit.saturating_sub(*deposit))?; + } else if new_deposit < *deposit { + T::Currency::unreserve(delegator, (*deposit).saturating_sub(new_deposit)); } *deposit = new_deposit; Self::deposit_event(Event::::ProxyAdded { @@ -770,14 +923,10 @@ impl Pallet { .ok_or(Error::::NotFound)?; proxies.remove(i); let new_deposit = Self::deposit(proxies.len() as u32); - match new_deposit.cmp(&old_deposit) { - Ordering::Greater => { - T::Currency::reserve(delegator, new_deposit.saturating_sub(old_deposit))?; - } - Ordering::Less => { - T::Currency::unreserve(delegator, old_deposit.saturating_sub(new_deposit)); - } - Ordering::Equal => (), + if new_deposit > old_deposit { + T::Currency::reserve(delegator, new_deposit.saturating_sub(old_deposit))?; + } else if new_deposit < old_deposit { + T::Currency::unreserve(delegator, old_deposit.saturating_sub(new_deposit)); } if !proxies.is_empty() { *x = Some((proxies, new_deposit)) @@ -813,14 +962,17 @@ impl Pallet { } else { base.saturating_add(factor.saturating_mul((len as u32).into())) }; - match new_deposit.cmp(&old_deposit) { - Ordering::Greater => { - T::Currency::reserve(who, new_deposit.saturating_sub(old_deposit))?; - } - Ordering::Less => { - T::Currency::unreserve(who, old_deposit.saturating_sub(new_deposit)); + if new_deposit > old_deposit { + T::Currency::reserve(who, new_deposit.saturating_sub(old_deposit))?; + } else if new_deposit < old_deposit { + let excess = old_deposit.saturating_sub(new_deposit); + let remaining_unreserved = T::Currency::unreserve(who, excess); + if !remaining_unreserved.is_zero() { + defensive!( + "Failed to unreserve full amount. (Requested, Actual)", + (excess, excess.saturating_sub(remaining_unreserved)) + ); } - Ordering::Equal => (), } Ok(if len == 0 { None } else { Some(new_deposit) }) } @@ -829,12 +981,12 @@ impl Pallet { F: FnMut(&Announcement, BlockNumberFor>) -> bool, >( delegate: &T::AccountId, - mut f: F, + f: F, ) -> DispatchResult { Announcements::::try_mutate_exists(delegate, |x| { let (mut pending, old_deposit) = x.take().ok_or(Error::::NotFound)?; let orig_pending_len = pending.len(); - pending.retain(&mut f); + pending.retain(f); ensure!(orig_pending_len > pending.len(), Error::::NotFound); *x = Self::rejig_deposit( delegate, @@ -868,6 +1020,7 @@ impl Pallet { real: T::AccountId, call: ::RuntimeCall, ) { + use frame::traits::{InstanceFilter as _, OriginTrait as _}; // This is a freshly authenticated new account, the origin restrictions doesn't apply. let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real).into(); origin.add_filter(move |c: &::RuntimeCall| { diff --git a/pallets/proxy/src/tests.rs b/pallets/proxy/src/tests.rs index e350386164..4e5e4722c3 100644 --- a/pallets/proxy/src/tests.rs +++ b/pallets/proxy/src/tests.rs @@ -18,24 +18,21 @@ // Tests for Proxy Pallet #![cfg(test)] +#![allow( + clippy::arithmetic_side_effects, + clippy::unwrap_used, + clippy::indexing_slicing +)] use super::*; - use crate as proxy; use alloc::{vec, vec::Vec}; -use codec::{Decode, DecodeWithMemTracking, Encode}; -use frame_support::{ - assert_noop, assert_ok, derive_impl, - traits::{ConstU32, ConstU64, Contains}, -}; -use sp_core::H256; -use sp_runtime::{BuildStorage, DispatchError, RuntimeDebug, traits::BlakeTwo256}; +use frame::testing_prelude::*; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( - pub enum Test - { +construct_runtime!( + pub enum Test { System: frame_system = 1, Balances: pallet_balances = 2, Proxy: proxy = 3, @@ -87,7 +84,7 @@ impl Default for ProxyType { Self::Any } } -impl InstanceFilter for ProxyType { +impl frame::traits::InstanceFilter for ProxyType { fn filter(&self, c: &RuntimeCall) -> bool { match self { ProxyType::Any => true, @@ -115,45 +112,54 @@ impl Contains for BaseFilter { } } } + +parameter_types! { + pub static ProxyDepositBase: u64 = 1; + pub static ProxyDepositFactor: u64 = 1; + pub static AnnouncementDepositBase: u64 = 1; + pub static AnnouncementDepositFactor: u64 = 1; +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type Currency = Balances; type ProxyType = ProxyType; - type ProxyDepositBase = ConstU64<1>; - type ProxyDepositFactor = ConstU64<1>; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; type MaxProxies = ConstU32<4>; type WeightInfo = (); type CallHasher = BlakeTwo256; type MaxPending = ConstU32<2>; - type AnnouncementDepositBase = ConstU64<1>; - type AnnouncementDepositFactor = ConstU64<1>; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } use super::{Call as ProxyCall, Event as ProxyEvent}; use frame_system::Call as SystemCall; -use pallet_balances::{Call as BalancesCall, Event as BalancesEvent}; +use pallet_balances::{Call as BalancesCall, Error as BalancesError, Event as BalancesEvent}; use pallet_utility::{Call as UtilityCall, Event as UtilityEvent}; type SystemError = frame_system::Error; -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::::default() .build_storage() - .expect("Expected to not panic"); + .unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)], - dev_accounts: None, + ..Default::default() } .assimilate_storage(&mut t) - .expect("Expected to not panic"); - let mut ext = sp_io::TestExternalities::new(t); + .unwrap(); + let mut ext = TestState::new(t); ext.execute_with(|| System::set_block_number(1)); ext } fn last_events(n: usize) -> Vec { - system::Pallet::::events() + frame_system::Pallet::::events() .into_iter() .rev() .take(n) @@ -380,7 +386,7 @@ fn delayed_requires_pre_announcement() { ); let call_hash = BlakeTwo256::hash_of(&call); assert_ok!(Proxy::announce(RuntimeOrigin::signed(2), 1, call_hash)); - system::Pallet::::set_block_number(2); + frame_system::Pallet::::set_block_number(2); assert_ok!(Proxy::proxy_announced( RuntimeOrigin::signed(0), 2, @@ -417,7 +423,7 @@ fn proxy_announced_removes_announcement_and_returns_deposit() { e ); - system::Pallet::::set_block_number(2); + frame_system::Pallet::::set_block_number(2); assert_ok!(Proxy::proxy_announced( RuntimeOrigin::signed(0), 3, @@ -928,7 +934,6 @@ fn pure_works() { anon, 5 )); - assert_eq!(Balances::free_balance(6), 0); assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call)); System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); assert_eq!(Balances::free_balance(6), 1); @@ -946,7 +951,7 @@ fn pure_works() { None, call.clone() )); - let de: DispatchError = DispatchError::from(Error::::NoPermission).stripped(); + let de = DispatchError::from(Error::::NoPermission).stripped(); System::assert_last_event(ProxyEvent::ProxyExecuted { result: Err(de) }.into()); assert_noop!( Proxy::kill_pure(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0, 1, 0), @@ -964,24 +969,286 @@ fn pure_works() { Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()), Error::::NotProxy ); + }); +} - // Actually kill the pure proxy. - assert_ok!(Proxy::kill_pure( - RuntimeOrigin::signed(anon), - 1, +#[test] +fn poke_deposit_works_for_proxy_deposits() { + new_test_ext().execute_with(|| { + // Add a proxy and check initial deposit + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, ProxyType::Any, - 0, - 1, 0 )); + assert_eq!(Balances::reserved_balance(1), 2); // Base(1) + Factor(1) * 1 + + // Change the proxy deposit base to trigger deposit update + ProxyDepositBase::set(2); + let result = Proxy::poke_deposit(RuntimeOrigin::signed(1)); + assert_ok!(result.as_ref()); + assert_eq!(result.unwrap().pays_fee, Pays::No); + assert_eq!(Balances::reserved_balance(1), 3); // New Base(2) + Factor(1) * 1 System::assert_last_event( - ProxyEvent::PureKilled { - pure: anon, - spawner: 1, - proxy_type: ProxyType::Any, - disambiguation_index: 0, + ProxyEvent::DepositPoked { + who: 1, + kind: DepositKind::Proxies, + old_deposit: 2, + new_deposit: 3, + } + .into(), + ); + assert!(System::events().iter().any(|record| matches!( + record.event, + RuntimeEvent::Proxy(Event::DepositPoked { .. }) + ))); + }); +} + +#[test] +fn poke_deposit_works_for_announcement_deposits() { + new_test_ext().execute_with(|| { + // Setup proxy and make announcement + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 1 + )); + assert_eq!(Balances::reserved_balance(1), 2); // Base(1) + Factor(1) * 1 + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); + let announcements = Announcements::::get(3); + assert_eq!( + announcements.0, + vec![Announcement { + real: 1, + call_hash: [1; 32].into(), + height: 1 + }] + ); + assert_eq!(Balances::reserved_balance(3), announcements.1); + let initial_deposit = Balances::reserved_balance(3); + + // Change announcement deposit base to trigger update + AnnouncementDepositBase::set(2); + let result = Proxy::poke_deposit(RuntimeOrigin::signed(3)); + assert_ok!(result.as_ref()); + assert_eq!(result.unwrap().pays_fee, Pays::No); + let new_deposit = initial_deposit.saturating_add(1); // Base increased by 1 + assert_eq!(Balances::reserved_balance(3), new_deposit); + System::assert_last_event( + ProxyEvent::DepositPoked { + who: 3, + kind: DepositKind::Announcements, + old_deposit: initial_deposit, + new_deposit, } .into(), ); + assert!(System::events().iter().any(|record| matches!( + record.event, + RuntimeEvent::Proxy(Event::DepositPoked { .. }) + ))); + }); +} + +#[test] +fn poke_deposit_charges_fee_when_deposit_unchanged() { + new_test_ext().execute_with(|| { + // Add a proxy and check initial deposit + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 3, + ProxyType::Any, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 2); // Base(1) + Factor(1) * 1 + + // Poke the deposit without changing deposit required and check fee + let result = Proxy::poke_deposit(RuntimeOrigin::signed(1)); + assert_ok!(result.as_ref()); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); // Pays fee + assert_eq!(Balances::reserved_balance(1), 2); // No change + + // No event emitted + assert!(!System::events().iter().any(|record| matches!( + record.event, + RuntimeEvent::Proxy(Event::DepositPoked { .. }) + ))); + + // Add an announcement and check initial deposit + assert_ok!(Proxy::announce(RuntimeOrigin::signed(3), 1, [1; 32].into())); + let announcements = Announcements::::get(3); + assert_eq!( + announcements.0, + vec![Announcement { + real: 1, + call_hash: [1; 32].into(), + height: 1 + }] + ); + assert_eq!(Balances::reserved_balance(3), announcements.1); + let initial_deposit = Balances::reserved_balance(3); + + // Poke the deposit without changing deposit required and check fee + let result = Proxy::poke_deposit(RuntimeOrigin::signed(3)); + assert_ok!(result.as_ref()); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); // Pays fee + assert_eq!(Balances::reserved_balance(3), initial_deposit); // No change + + // No event emitted + assert!(!System::events().iter().any(|record| matches!( + record.event, + RuntimeEvent::Proxy(Event::DepositPoked { .. }) + ))); + }); +} + +#[test] +fn poke_deposit_handles_insufficient_balance() { + new_test_ext().execute_with(|| { + // Setup with account that has minimal balance + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(5), + 3, + ProxyType::Any, + 0 + )); + let initial_deposit = Balances::reserved_balance(5); + + // Change deposit base to require more than available balance + ProxyDepositBase::set(10); + + // Poking should fail due to insufficient balance + assert_noop!( + Proxy::poke_deposit(RuntimeOrigin::signed(5)), + BalancesError::::InsufficientBalance, + ); + + // Original deposit should remain unchanged + assert_eq!(Balances::reserved_balance(5), initial_deposit); + }); +} + +#[test] +fn poke_deposit_updates_both_proxy_and_announcement_deposits() { + new_test_ext().execute_with(|| { + // Setup both proxy and announcement for the same account + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::Any, + 0 + )); + assert_eq!(Balances::reserved_balance(1), 2); // Base(1) + Factor(1) * 1 + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(2), + 3, + ProxyType::Any, + 1 + )); + assert_eq!(Balances::reserved_balance(2), 2); // Base(1) + Factor(1) * 1 + assert_ok!(Proxy::announce(RuntimeOrigin::signed(2), 1, [1; 32].into())); + let announcements = Announcements::::get(2); + assert_eq!( + announcements.0, + vec![Announcement { + real: 1, + call_hash: [1; 32].into(), + height: 1 + }] + ); + assert_eq!(announcements.1, 2); // Base(1) + Factor(1) * 1 + + // Record initial deposits + let initial_proxy_deposit = Proxies::::get(2).1; + let initial_announcement_deposit = Announcements::::get(2).1; + + // Total reserved = deposit for proxy + deposit for announcement + assert_eq!( + Balances::reserved_balance(2), + initial_proxy_deposit.saturating_add(initial_announcement_deposit) + ); + + // Change both deposit requirements + ProxyDepositBase::set(2); + AnnouncementDepositBase::set(2); + + // Poke deposits - should update both deposits and emit two events + let result = Proxy::poke_deposit(RuntimeOrigin::signed(2)); + assert_ok!(result.as_ref()); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + // Check both deposits were updated + let (_, new_proxy_deposit) = Proxies::::get(2); + let (_, new_announcement_deposit) = Announcements::::get(2); + assert_eq!(new_proxy_deposit, 3); // Base(2) + Factor(1) * 1 + assert_eq!(new_announcement_deposit, 3); // Base(2) + Factor(1) * 1 + assert_eq!( + Balances::reserved_balance(2), + new_proxy_deposit.saturating_add(new_announcement_deposit) + ); + + // Verify both events were emitted in the correct order + let events = System::events(); + let relevant_events: Vec<_> = events + .iter() + .filter(|record| { + matches!( + record.event, + RuntimeEvent::Proxy(ProxyEvent::DepositPoked { .. }) + ) + }) + .collect(); + + assert_eq!(relevant_events.len(), 2); + + // First event should be for Proxies + assert_eq!( + relevant_events[0].event, + ProxyEvent::DepositPoked { + who: 2, + kind: DepositKind::Proxies, + old_deposit: initial_proxy_deposit, + new_deposit: new_proxy_deposit, + } + .into() + ); + + // Second event should be for Announcements + assert_eq!( + relevant_events[1].event, + ProxyEvent::DepositPoked { + who: 2, + kind: DepositKind::Announcements, + old_deposit: initial_announcement_deposit, + new_deposit: new_announcement_deposit, + } + .into() + ); + + // Poking again should charge fee as nothing changes + let result = Proxy::poke_deposit(RuntimeOrigin::signed(2)); + assert_ok!(result.as_ref()); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + + // Verify deposits remained the same + assert_eq!(Proxies::::get(2).1, new_proxy_deposit); + assert_eq!(Announcements::::get(2).1, new_announcement_deposit); + assert_eq!( + Balances::reserved_balance(2), + new_proxy_deposit.saturating_add(new_announcement_deposit) + ); + }); +} + +#[test] +fn poke_deposit_fails_for_unsigned_origin() { + new_test_ext().execute_with(|| { + assert_noop!( + Proxy::poke_deposit(RuntimeOrigin::none()), + DispatchError::BadOrigin, + ); }); } diff --git a/pallets/proxy/src/weights.rs b/pallets/proxy/src/weights.rs index 3093298e3e..bb51872b2e 100644 --- a/pallets/proxy/src/weights.rs +++ b/pallets/proxy/src/weights.rs @@ -18,36 +18,38 @@ //! Autogenerated weights for `pallet_proxy` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-03-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `99fc4dfa9c86`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=dev +// --extrinsic=* +// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm +// --pallet=pallet_proxy +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/proxy/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_proxy +// --heap-pages=4096 +// --template=substrate/.maintain/frame-umbrella-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/proxy/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage,pallet_election_provider_multi_block,pallet_election_provider_multi_block::signed,pallet_election_provider_multi_block::unsigned,pallet_election_provider_multi_block::verifier #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] +#![allow(dead_code)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; +use frame::weights_prelude::*; /// Weight functions needed for `pallet_proxy`. pub trait WeightInfo { @@ -61,6 +63,7 @@ pub trait WeightInfo { fn remove_proxies(p: u32, ) -> Weight; fn create_pure(p: u32, ) -> Weight; fn kill_pure(p: u32, ) -> Weight; + fn poke_deposit() -> Weight; } /// Weights for `pallet_proxy` using the Substrate node and recommended hardware. @@ -75,12 +78,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + p * (37 ±0)` + // Measured: `339 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 18_280_000 picoseconds. - Weight::from_parts(19_655_145, 4706) - // Standard Error: 2_345 - .saturating_add(Weight::from_parts(36_306, 0).saturating_mul(p.into())) + // Minimum execution time: 23_353_000 picoseconds. + Weight::from_parts(25_084_085, 4706) + // Standard Error: 2_569 + .saturating_add(Weight::from_parts(33_574, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Proxy::Proxies` (r:1 w:0) @@ -97,14 +100,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `633 + a * (68 ±0) + p * (37 ±0)` + // Measured: `666 + a * (68 ±0) + p * (37 ±0)` // Estimated: `5698` - // Minimum execution time: 41_789_000 picoseconds. - Weight::from_parts(41_812_078, 5698) - // Standard Error: 3_694 - .saturating_add(Weight::from_parts(163_029, 0).saturating_mul(a.into())) - // Standard Error: 3_817 - .saturating_add(Weight::from_parts(79_539, 0).saturating_mul(p.into())) + // Minimum execution time: 47_196_000 picoseconds. + Weight::from_parts(48_686_812, 5698) + // Standard Error: 3_711 + .saturating_add(Weight::from_parts(171_107, 0).saturating_mul(a.into())) + // Standard Error: 3_834 + .saturating_add(Weight::from_parts(34_523, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -116,14 +119,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn remove_announcement(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403 + a * (68 ±0)` + // Measured: `436 + a * (68 ±0)` // Estimated: `5698` - // Minimum execution time: 22_475_000 picoseconds. - Weight::from_parts(22_666_821, 5698) - // Standard Error: 1_797 - .saturating_add(Weight::from_parts(170_629, 0).saturating_mul(a.into())) - // Standard Error: 1_857 - .saturating_add(Weight::from_parts(18_799, 0).saturating_mul(p.into())) + // Minimum execution time: 29_341_000 picoseconds. + Weight::from_parts(30_320_504, 5698) + // Standard Error: 1_821 + .saturating_add(Weight::from_parts(158_572, 0).saturating_mul(a.into())) + // Standard Error: 1_881 + .saturating_add(Weight::from_parts(8_433, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -135,14 +138,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn reject_announcement(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403 + a * (68 ±0)` + // Measured: `436 + a * (68 ±0)` // Estimated: `5698` - // Minimum execution time: 22_326_000 picoseconds. - Weight::from_parts(22_654_227, 5698) - // Standard Error: 1_859 - .saturating_add(Weight::from_parts(168_822, 0).saturating_mul(a.into())) - // Standard Error: 1_921 - .saturating_add(Weight::from_parts(21_839, 0).saturating_mul(p.into())) + // Minimum execution time: 28_422_000 picoseconds. + Weight::from_parts(29_754_384, 5698) + // Standard Error: 1_840 + .saturating_add(Weight::from_parts(176_827, 0).saturating_mul(a.into())) + // Standard Error: 1_901 + .saturating_add(Weight::from_parts(9_607, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -156,14 +159,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `420 + a * (68 ±0) + p * (37 ±0)` + // Measured: `453 + a * (68 ±0) + p * (37 ±0)` // Estimated: `5698` - // Minimum execution time: 31_551_000 picoseconds. - Weight::from_parts(32_205_445, 5698) - // Standard Error: 4_089 - .saturating_add(Weight::from_parts(167_596, 0).saturating_mul(a.into())) - // Standard Error: 4_225 - .saturating_add(Weight::from_parts(67_833, 0).saturating_mul(p.into())) + // Minimum execution time: 36_885_000 picoseconds. + Weight::from_parts(38_080_636, 5698) + // Standard Error: 2_642 + .saturating_add(Weight::from_parts(157_335, 0).saturating_mul(a.into())) + // Standard Error: 2_730 + .saturating_add(Weight::from_parts(28_872, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -172,12 +175,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `161 + p * (37 ±0)` + // Measured: `194 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 21_495_000 picoseconds. - Weight::from_parts(22_358_457, 4706) - // Standard Error: 1_606 - .saturating_add(Weight::from_parts(64_322, 0).saturating_mul(p.into())) + // Minimum execution time: 27_016_000 picoseconds. + Weight::from_parts(28_296_216, 4706) + // Standard Error: 1_643 + .saturating_add(Weight::from_parts(50_271, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -186,12 +189,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `161 + p * (37 ±0)` + // Measured: `194 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 21_495_000 picoseconds. - Weight::from_parts(22_579_308, 4706) - // Standard Error: 2_571 - .saturating_add(Weight::from_parts(62_404, 0).saturating_mul(p.into())) + // Minimum execution time: 26_955_000 picoseconds. + Weight::from_parts(28_379_566, 4706) + // Standard Error: 1_547 + .saturating_add(Weight::from_parts(45_784, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -200,12 +203,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `161 + p * (37 ±0)` + // Measured: `194 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 20_541_000 picoseconds. - Weight::from_parts(21_456_750, 4706) - // Standard Error: 1_697 - .saturating_add(Weight::from_parts(45_387, 0).saturating_mul(p.into())) + // Minimum execution time: 24_656_000 picoseconds. + Weight::from_parts(25_821_878, 4706) + // Standard Error: 2_300 + .saturating_add(Weight::from_parts(33_972, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -214,12 +217,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[1, 31]`. fn create_pure(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `173` + // Measured: `206` // Estimated: `4706` - // Minimum execution time: 22_809_000 picoseconds. - Weight::from_parts(23_878_644, 4706) - // Standard Error: 1_600 - .saturating_add(Weight::from_parts(10_149, 0).saturating_mul(p.into())) + // Minimum execution time: 28_416_000 picoseconds. + Weight::from_parts(29_662_728, 4706) + // Standard Error: 1_851 + .saturating_add(Weight::from_parts(29_928, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -228,15 +231,30 @@ impl WeightInfo for SubstrateWeight { /// The range of component `p` is `[0, 30]`. fn kill_pure(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `198 + p * (37 ±0)` + // Measured: `231 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 20_993_000 picoseconds. - Weight::from_parts(22_067_418, 4706) - // Standard Error: 1_673 - .saturating_add(Weight::from_parts(52_703, 0).saturating_mul(p.into())) + // Minimum execution time: 25_505_000 picoseconds. + Weight::from_parts(26_780_627, 4706) + // Standard Error: 1_581 + .saturating_add(Weight::from_parts(33_085, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + fn poke_deposit() -> Weight { + // Proof Size summary in bytes: + // Measured: `519` + // Estimated: `5698` + // Minimum execution time: 46_733_000 picoseconds. + Weight::from_parts(47_972_000, 5698) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } } // For backwards compatibility and tests. @@ -250,12 +268,12 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + p * (37 ±0)` + // Measured: `339 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 18_280_000 picoseconds. - Weight::from_parts(19_655_145, 4706) - // Standard Error: 2_345 - .saturating_add(Weight::from_parts(36_306, 0).saturating_mul(p.into())) + // Minimum execution time: 23_353_000 picoseconds. + Weight::from_parts(25_084_085, 4706) + // Standard Error: 2_569 + .saturating_add(Weight::from_parts(33_574, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Proxy::Proxies` (r:1 w:0) @@ -272,14 +290,14 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `633 + a * (68 ±0) + p * (37 ±0)` + // Measured: `666 + a * (68 ±0) + p * (37 ±0)` // Estimated: `5698` - // Minimum execution time: 41_789_000 picoseconds. - Weight::from_parts(41_812_078, 5698) - // Standard Error: 3_694 - .saturating_add(Weight::from_parts(163_029, 0).saturating_mul(a.into())) - // Standard Error: 3_817 - .saturating_add(Weight::from_parts(79_539, 0).saturating_mul(p.into())) + // Minimum execution time: 47_196_000 picoseconds. + Weight::from_parts(48_686_812, 5698) + // Standard Error: 3_711 + .saturating_add(Weight::from_parts(171_107, 0).saturating_mul(a.into())) + // Standard Error: 3_834 + .saturating_add(Weight::from_parts(34_523, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -291,14 +309,14 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn remove_announcement(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403 + a * (68 ±0)` + // Measured: `436 + a * (68 ±0)` // Estimated: `5698` - // Minimum execution time: 22_475_000 picoseconds. - Weight::from_parts(22_666_821, 5698) - // Standard Error: 1_797 - .saturating_add(Weight::from_parts(170_629, 0).saturating_mul(a.into())) - // Standard Error: 1_857 - .saturating_add(Weight::from_parts(18_799, 0).saturating_mul(p.into())) + // Minimum execution time: 29_341_000 picoseconds. + Weight::from_parts(30_320_504, 5698) + // Standard Error: 1_821 + .saturating_add(Weight::from_parts(158_572, 0).saturating_mul(a.into())) + // Standard Error: 1_881 + .saturating_add(Weight::from_parts(8_433, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -310,14 +328,14 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn reject_announcement(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403 + a * (68 ±0)` + // Measured: `436 + a * (68 ±0)` // Estimated: `5698` - // Minimum execution time: 22_326_000 picoseconds. - Weight::from_parts(22_654_227, 5698) - // Standard Error: 1_859 - .saturating_add(Weight::from_parts(168_822, 0).saturating_mul(a.into())) - // Standard Error: 1_921 - .saturating_add(Weight::from_parts(21_839, 0).saturating_mul(p.into())) + // Minimum execution time: 28_422_000 picoseconds. + Weight::from_parts(29_754_384, 5698) + // Standard Error: 1_840 + .saturating_add(Weight::from_parts(176_827, 0).saturating_mul(a.into())) + // Standard Error: 1_901 + .saturating_add(Weight::from_parts(9_607, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -331,14 +349,14 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `420 + a * (68 ±0) + p * (37 ±0)` + // Measured: `453 + a * (68 ±0) + p * (37 ±0)` // Estimated: `5698` - // Minimum execution time: 31_551_000 picoseconds. - Weight::from_parts(32_205_445, 5698) - // Standard Error: 4_089 - .saturating_add(Weight::from_parts(167_596, 0).saturating_mul(a.into())) - // Standard Error: 4_225 - .saturating_add(Weight::from_parts(67_833, 0).saturating_mul(p.into())) + // Minimum execution time: 36_885_000 picoseconds. + Weight::from_parts(38_080_636, 5698) + // Standard Error: 2_642 + .saturating_add(Weight::from_parts(157_335, 0).saturating_mul(a.into())) + // Standard Error: 2_730 + .saturating_add(Weight::from_parts(28_872, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -347,12 +365,12 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `161 + p * (37 ±0)` + // Measured: `194 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 21_495_000 picoseconds. - Weight::from_parts(22_358_457, 4706) - // Standard Error: 1_606 - .saturating_add(Weight::from_parts(64_322, 0).saturating_mul(p.into())) + // Minimum execution time: 27_016_000 picoseconds. + Weight::from_parts(28_296_216, 4706) + // Standard Error: 1_643 + .saturating_add(Weight::from_parts(50_271, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -361,12 +379,12 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `161 + p * (37 ±0)` + // Measured: `194 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 21_495_000 picoseconds. - Weight::from_parts(22_579_308, 4706) - // Standard Error: 2_571 - .saturating_add(Weight::from_parts(62_404, 0).saturating_mul(p.into())) + // Minimum execution time: 26_955_000 picoseconds. + Weight::from_parts(28_379_566, 4706) + // Standard Error: 1_547 + .saturating_add(Weight::from_parts(45_784, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -375,12 +393,12 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `161 + p * (37 ±0)` + // Measured: `194 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 20_541_000 picoseconds. - Weight::from_parts(21_456_750, 4706) - // Standard Error: 1_697 - .saturating_add(Weight::from_parts(45_387, 0).saturating_mul(p.into())) + // Minimum execution time: 24_656_000 picoseconds. + Weight::from_parts(25_821_878, 4706) + // Standard Error: 2_300 + .saturating_add(Weight::from_parts(33_972, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -389,12 +407,12 @@ impl WeightInfo for () { /// The range of component `p` is `[1, 31]`. fn create_pure(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `173` + // Measured: `206` // Estimated: `4706` - // Minimum execution time: 22_809_000 picoseconds. - Weight::from_parts(23_878_644, 4706) - // Standard Error: 1_600 - .saturating_add(Weight::from_parts(10_149, 0).saturating_mul(p.into())) + // Minimum execution time: 28_416_000 picoseconds. + Weight::from_parts(29_662_728, 4706) + // Standard Error: 1_851 + .saturating_add(Weight::from_parts(29_928, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -403,13 +421,28 @@ impl WeightInfo for () { /// The range of component `p` is `[0, 30]`. fn kill_pure(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `198 + p * (37 ±0)` + // Measured: `231 + p * (37 ±0)` // Estimated: `4706` - // Minimum execution time: 20_993_000 picoseconds. - Weight::from_parts(22_067_418, 4706) - // Standard Error: 1_673 - .saturating_add(Weight::from_parts(52_703, 0).saturating_mul(p.into())) + // Minimum execution time: 25_505_000 picoseconds. + Weight::from_parts(26_780_627, 4706) + // Standard Error: 1_581 + .saturating_add(Weight::from_parts(33_085, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + fn poke_deposit() -> Weight { + // Proof Size summary in bytes: + // Measured: `519` + // Estimated: `5698` + // Minimum execution time: 46_733_000 picoseconds. + Weight::from_parts(47_972_000, 5698) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } } diff --git a/pallets/utility/Cargo.toml b/pallets/utility/Cargo.toml index 01ecd42166..08df4734c0 100644 --- a/pallets/utility/Cargo.toml +++ b/pallets/utility/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-utility" -version = "38.0.0" +version = "40.0.0" edition.workspace = true license = "Apache-2.0" description = "FRAME utilities pallet" @@ -13,20 +13,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -frame-benchmarking = { workspace = true, optional = true } +codec = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support.workspace = true frame-system.workspace = true +scale-info = { features = ["derive"], workspace = true } sp-core.workspace = true sp-io.workspace = true sp-runtime.workspace = true -codec = { workspace = true, features = ["derive"] } -scale-info = { workspace = true, features = ["derive"] } subtensor-macros.workspace = true [dev-dependencies] -pallet-collective.workspace = true -pallet-root-testing.workspace = true pallet-balances = { workspace = true, default-features = true } +pallet-collective = { workspace = true, default-features = true } +pallet-root-testing = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -41,24 +41,22 @@ std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", - "pallet-collective/std", - "pallet-root-testing/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collective/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "sp-runtime/try-runtime", "pallet-balances/try-runtime", "pallet-collective/try-runtime", "pallet-root-testing/try-runtime", "pallet-timestamp/try-runtime", + "sp-runtime/try-runtime", ] diff --git a/pallets/utility/src/benchmarking.rs b/pallets/utility/src/benchmarking.rs index 6980552c36..4a9e0ca306 100644 --- a/pallets/utility/src/benchmarking.rs +++ b/pallets/utility/src/benchmarking.rs @@ -19,73 +19,105 @@ #![cfg(feature = "runtime-benchmarks")] -use super::*; -use alloc::{vec, vec::Vec}; -use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; +use alloc::vec; +use frame_benchmarking::{benchmarking::add_to_whitelist, v2::*}; use frame_system::RawOrigin; +use crate::*; + const SEED: u32 = 0; fn assert_last_event(generic_event: ::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } -benchmarks! { - where_clause { where ::PalletsOrigin: Clone } - batch { - let c in 0 .. 1000; - let mut calls: Vec<::RuntimeCall> = Vec::new(); - for i in 0 .. c { - let call = frame_system::Call::remark { remark: vec![] }.into(); - calls.push(call); - } +#[benchmarks] +mod benchmark { + use super::*; + + #[benchmark] + fn batch(c: Linear<0, 1000>) { + let calls = vec![frame_system::Call::remark { remark: vec![] }.into(); c as usize]; let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), calls) - verify { - assert_last_event::(Event::BatchCompleted.into()) + + #[extrinsic_call] + _(RawOrigin::Signed(caller), calls); + + assert_last_event::(Event::BatchCompleted.into()); } - as_derivative { + #[benchmark] + fn as_derivative() { let caller = account("caller", SEED, SEED); let call = Box::new(frame_system::Call::remark { remark: vec![] }.into()); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); - frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); - }: _(RawOrigin::Signed(caller), SEED as u16, call) - - batch_all { - let c in 0 .. 1000; - let mut calls: Vec<::RuntimeCall> = Vec::new(); - for i in 0 .. c { - let call = frame_system::Call::remark { remark: vec![] }.into(); - calls.push(call); - } + add_to_whitelist(caller_key.into()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), SEED as u16, call); + } + + #[benchmark] + fn batch_all(c: Linear<0, 1000>) { + let calls = vec![frame_system::Call::remark { remark: vec![] }.into(); c as usize]; + let caller = whitelisted_caller(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), calls); + + assert_last_event::(Event::BatchCompleted.into()); + } + + #[benchmark] + fn dispatch_as() { + let caller = account("caller", SEED, SEED); + let call = Box::new(frame_system::Call::remark { remark: vec![] }.into()); + let origin = T::RuntimeOrigin::from(RawOrigin::Signed(caller)); + let pallets_origin = origin.caller().clone(); + let pallets_origin = T::PalletsOrigin::from(pallets_origin); + + #[extrinsic_call] + _(RawOrigin::Root, Box::new(pallets_origin), call); + } + + #[benchmark] + fn force_batch(c: Linear<0, 1000>) { + let calls = vec![frame_system::Call::remark { remark: vec![] }.into(); c as usize]; let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), calls) - verify { - assert_last_event::(Event::BatchCompleted.into()) + + #[extrinsic_call] + _(RawOrigin::Signed(caller), calls); + + assert_last_event::(Event::BatchCompleted.into()); } - dispatch_as { + #[benchmark] + fn dispatch_as_fallible() { let caller = account("caller", SEED, SEED); let call = Box::new(frame_system::Call::remark { remark: vec![] }.into()); let origin: T::RuntimeOrigin = RawOrigin::Signed(caller).into(); - let pallets_origin: ::PalletsOrigin = origin.caller().clone(); - let pallets_origin = Into::::into(pallets_origin); - }: _(RawOrigin::Root, Box::new(pallets_origin), call) - - force_batch { - let c in 0 .. 1000; - let mut calls: Vec<::RuntimeCall> = Vec::new(); - for i in 0 .. c { - let call = frame_system::Call::remark { remark: vec![] }.into(); - calls.push(call); - } + let pallets_origin = origin.caller().clone(); + let pallets_origin = T::PalletsOrigin::from(pallets_origin); + + #[extrinsic_call] + _(RawOrigin::Root, Box::new(pallets_origin), call); + } + + #[benchmark] + fn if_else() { + // Failing main call. + let main_call = Box::new(frame_system::Call::set_code { code: vec![1] }.into()); + let fallback_call = Box::new(frame_system::Call::remark { remark: vec![1] }.into()); let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), calls) - verify { - assert_last_event::(Event::BatchCompleted.into()) + + #[extrinsic_call] + _(RawOrigin::Signed(caller), main_call, fallback_call); } - impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite! { + Pallet, + tests::new_test_ext(), + tests::Test + } } diff --git a/pallets/utility/src/lib.rs b/pallets/utility/src/lib.rs index 8ee888889e..bafb7ce9d9 100644 --- a/pallets/utility/src/lib.rs +++ b/pallets/utility/src/lib.rs @@ -61,7 +61,10 @@ extern crate alloc; use alloc::{boxed::Box, vec::Vec}; use codec::{Decode, Encode}; use frame_support::{ - dispatch::{GetDispatchInfo, PostDispatchInfo, extract_actual_weight}, + dispatch::{ + DispatchClass::{Normal, Operational}, + GetDispatchInfo, PostDispatchInfo, extract_actual_weight, + }, traits::{IsSubType, OriginTrait, UnfilteredDispatchable}, }; use sp_core::TypeId; @@ -122,6 +125,10 @@ pub mod pallet { ItemFailed { error: DispatchError }, /// A call was dispatched. DispatchedAs { result: DispatchResult }, + /// Main call was dispatched. + IfElseMainSuccess, + /// The fallback call was dispatched. + IfElseFallbackCalled { main_error: DispatchError }, } // Align the call size to 1KB. As we are currently compiling the runtime for native/wasm @@ -135,19 +142,16 @@ pub mod pallet { /// The limit on the number of batched calls. fn batched_calls_limit() -> u32 { let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION; - let size = core::mem::size_of::<::RuntimeCall>() as u32; - - let align_up = size.saturating_add(CALL_ALIGN.saturating_sub(1)); - let call_size = align_up - .checked_div(CALL_ALIGN) - .unwrap_or(0) - .saturating_mul(CALL_ALIGN); - - let margin_factor: u32 = 3; - - let after_margin = allocator_limit.checked_div(margin_factor).unwrap_or(0); - - after_margin.checked_div(call_size).unwrap_or(0) + let call_size = (core::mem::size_of::<::RuntimeCall>() as u32) + .div_ceil(CALL_ALIGN) + .checked_mul(CALL_ALIGN) + .unwrap_or(u32::MAX); + // The margin to take into account vec doubling capacity. + let margin_factor = 3; + + allocator_limit + .checked_div(margin_factor) + .map_or(0, |x| x.checked_div(call_size).unwrap_or(0)) } } @@ -190,9 +194,9 @@ pub mod pallet { /// event is deposited. #[pallet::call_index(0)] #[pallet::weight({ - let dispatch_weight = Pallet::::weight(calls); + let (dispatch_weight, dispatch_class, pays) = Pallet::::weight_and_dispatch_class(calls); let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch(calls.len() as u32)); - (dispatch_weight, DispatchClass::Normal) + (dispatch_weight, dispatch_class, pays) })] pub fn batch( origin: OriginFor, @@ -302,9 +306,9 @@ pub mod pallet { /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(2)] #[pallet::weight({ - let dispatch_weight = Pallet::::weight(calls); + let (dispatch_weight, dispatch_class, pays) = Pallet::::weight_and_dispatch_class(calls); let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32)); - (dispatch_weight, DispatchClass::Normal) + (dispatch_weight, dispatch_class, pays) })] pub fn batch_all( origin: OriginFor, @@ -401,9 +405,9 @@ pub mod pallet { /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(4)] #[pallet::weight({ - let dispatch_weight = Pallet::::weight(calls); + let (dispatch_weight, dispatch_class, pays) = Pallet::::weight_and_dispatch_class(calls); let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::force_batch(calls.len() as u32)); - (dispatch_weight, DispatchClass::Normal) + (dispatch_weight, dispatch_class, pays) })] pub fn force_batch( origin: OriginFor, @@ -470,19 +474,158 @@ pub mod pallet { let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); res.map(|_| ()).map_err(|e| e.error) } + + /// Dispatch a fallback call in the event the main call fails to execute. + /// May be called from any origin except `None`. + /// + /// This function first attempts to dispatch the `main` call. + /// If the `main` call fails, the `fallback` is attemted. + /// if the fallback is successfully dispatched, the weights of both calls + /// are accumulated and an event containing the main call error is deposited. + /// + /// In the event of a fallback failure the whole call fails + /// with the weights returned. + /// + /// - `main`: The main call to be dispatched. This is the primary action to execute. + /// - `fallback`: The fallback call to be dispatched in case the `main` call fails. + /// + /// ## Dispatch Logic + /// - If the origin is `root`, both the main and fallback calls are executed without + /// applying any origin filters. + /// - If the origin is not `root`, the origin filter is applied to both the `main` and + /// `fallback` calls. + /// + /// ## Use Case + /// - Some use cases might involve submitting a `batch` type call in either main, fallback + /// or both. + #[pallet::call_index(6)] + #[pallet::weight({ + let main = main.get_dispatch_info(); + let fallback = fallback.get_dispatch_info(); + ( + T::WeightInfo::if_else() + .saturating_add(main.call_weight) + .saturating_add(fallback.call_weight), + if main.class == Operational && fallback.class == Operational { Operational } else { Normal }, + ) + })] + pub fn if_else( + origin: OriginFor, + main: Box<::RuntimeCall>, + fallback: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + // Do not allow the `None` origin. + if ensure_none(origin.clone()).is_ok() { + return Err(BadOrigin.into()); + } + + let is_root = ensure_root(origin.clone()).is_ok(); + + // Track the weights + let mut weight = T::WeightInfo::if_else(); + + let main_info = main.get_dispatch_info(); + + // Execute the main call first + let main_result = if is_root { + main.dispatch_bypass_filter(origin.clone()) + } else { + main.dispatch(origin.clone()) + }; + + // Add weight of the main call + weight = weight.saturating_add(extract_actual_weight(&main_result, &main_info)); + + let Err(main_error) = main_result else { + // If the main result is Ok, we skip the fallback logic entirely + Self::deposit_event(Event::IfElseMainSuccess); + return Ok(Some(weight).into()); + }; + + // If the main call failed, execute the fallback call + let fallback_info = fallback.get_dispatch_info(); + + let fallback_result = if is_root { + fallback.dispatch_bypass_filter(origin.clone()) + } else { + fallback.dispatch(origin) + }; + + // Add weight of the fallback call + weight = weight.saturating_add(extract_actual_weight(&fallback_result, &fallback_info)); + + let Err(fallback_error) = fallback_result else { + // Fallback succeeded. + Self::deposit_event(Event::IfElseFallbackCalled { + main_error: main_error.error, + }); + return Ok(Some(weight).into()); + }; + + // Both calls have failed, return fallback error + Err(sp_runtime::DispatchErrorWithPostInfo { + error: fallback_error.error, + post_info: Some(weight).into(), + }) + } + + /// Dispatches a function call with a provided origin. + /// + /// Almost the same as [`Pallet::dispatch_as`] but forwards any error of the inner call. + /// + /// The dispatch origin for this call must be _Root_. + #[pallet::call_index(7)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + ( + T::WeightInfo::dispatch_as_fallible() + .saturating_add(dispatch_info.call_weight), + dispatch_info.class, + ) + })] + pub fn dispatch_as_fallible( + origin: OriginFor, + as_origin: Box, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + ensure_root(origin)?; + + call.dispatch_bypass_filter((*as_origin).into()) + .map_err(|e| e.error)?; + + Self::deposit_event(Event::DispatchedAs { result: Ok(()) }); + + Ok(()) + } } impl Pallet { /// Get the accumulated `weight` and the dispatch class for the given `calls`. - fn weight(calls: &[::RuntimeCall]) -> Weight { + fn weight_and_dispatch_class( + calls: &[::RuntimeCall], + ) -> (Weight, DispatchClass, Pays) { let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()); - dispatch_infos.fold(Weight::zero(), |total_weight, di| { - if di.pays_fee == Pays::Yes { - total_weight.saturating_add(di.call_weight) - } else { - total_weight - } - }) + let pays = if dispatch_infos.clone().any(|di| di.pays_fee == Pays::No) { + Pays::No + } else { + Pays::Yes + }; + let (dispatch_weight, dispatch_class) = dispatch_infos.fold( + (Weight::zero(), DispatchClass::Operational), + |(total_weight, dispatch_class): (Weight, DispatchClass), di| { + ( + total_weight.saturating_add(di.call_weight), + // If not all are `Operational`, we want to use `DispatchClass::Normal`. + if di.class == DispatchClass::Normal { + di.class + } else { + dispatch_class + }, + ) + }, + ); + + (dispatch_weight, dispatch_class, pays) } } } diff --git a/pallets/utility/src/tests.rs b/pallets/utility/src/tests.rs index a883f1b690..17b5cd96ca 100644 --- a/pallets/utility/src/tests.rs +++ b/pallets/utility/src/tests.rs @@ -18,7 +18,7 @@ // Tests for Utility Pallet #![cfg(test)] -#![allow(clippy::arithmetic_side_effects)] +#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] use super::*; @@ -257,20 +257,20 @@ use pallet_timestamp::Call as TimestampCall; pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default() .build_storage() - .expect("Failed to build storage for test"); + .unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], - dev_accounts: None, + ..Default::default() } .assimilate_storage(&mut t) - .expect("Failed to build storage for test"); + .unwrap(); pallet_collective::GenesisConfig:: { members: vec![1, 2, 3], phantom: Default::default(), } .assimilate_storage(&mut t) - .expect("Failed to build storage for test"); + .unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); @@ -289,6 +289,20 @@ fn call_foobar(err: bool, start_weight: Weight, end_weight: Option) -> R }) } +fn utility_events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let RuntimeEvent::Utility(inner) = e { + Some(inner) + } else { + None + } + }) + .collect() +} + #[test] fn as_derivative_works() { new_test_ext().execute_with(|| { @@ -708,7 +722,7 @@ fn batch_all_handles_weight_refund() { assert_eq!( extract_actual_weight(&result, &info), // Real weight is 2 calls at end_weight - ::WeightInfo::batch_all(2).saturating_add(end_weight.saturating_mul(2)), + ::WeightInfo::batch_all(2) + end_weight * 2, ); }); } @@ -1006,3 +1020,177 @@ fn with_weight_works() { ); }) } + +#[test] +fn dispatch_as_works() { + new_test_ext().execute_with(|| { + Balances::force_set_balance(RuntimeOrigin::root(), 666, 100).unwrap(); + assert_eq!(Balances::free_balance(666), 100); + assert_eq!(Balances::free_balance(777), 0); + assert_ok!(Utility::dispatch_as( + RuntimeOrigin::root(), + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed(666))), + Box::new(call_transfer(777, 100)) + )); + assert_eq!(Balances::free_balance(666), 0); + assert_eq!(Balances::free_balance(777), 100); + + System::reset_events(); + assert_ok!(Utility::dispatch_as( + RuntimeOrigin::root(), + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed(777))), + Box::new(RuntimeCall::Timestamp(TimestampCall::set { now: 0 })) + )); + assert_eq!( + utility_events(), + vec![Event::DispatchedAs { + result: Err(DispatchError::BadOrigin) + }] + ); + }) +} + +#[test] +fn if_else_with_root_works() { + new_test_ext().execute_with(|| { + let k = b"a".to_vec(); + let call = RuntimeCall::System(frame_system::Call::set_storage { + items: vec![(k.clone(), k.clone())], + }); + assert!(!TestBaseCallFilter::contains(&call)); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::if_else( + RuntimeOrigin::root(), + RuntimeCall::Balances(BalancesCall::force_transfer { + source: 1, + dest: 2, + value: 11 + }) + .into(), + call.into(), + )); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_eq!(storage::unhashed::get_raw(&k), Some(k)); + System::assert_last_event( + utility::Event::IfElseFallbackCalled { + main_error: TokenError::FundsUnavailable.into(), + } + .into(), + ); + }); +} + +#[test] +fn if_else_with_signed_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::if_else( + RuntimeOrigin::signed(1), + call_transfer(2, 11).into(), + call_transfer(2, 5).into() + )); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::free_balance(2), 15); + + System::assert_last_event( + utility::Event::IfElseFallbackCalled { + main_error: TokenError::FundsUnavailable.into(), + } + .into(), + ); + }); +} + +#[test] +fn if_else_successful_main_call() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::if_else( + RuntimeOrigin::signed(1), + call_transfer(2, 9).into(), + call_transfer(2, 1).into() + )); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::free_balance(2), 19); + + System::assert_last_event(utility::Event::IfElseMainSuccess.into()); + }) +} + +#[test] +fn dispatch_as_fallible_works() { + new_test_ext().execute_with(|| { + Balances::force_set_balance(RuntimeOrigin::root(), 666, 100).unwrap(); + assert_eq!(Balances::free_balance(666), 100); + assert_eq!(Balances::free_balance(777), 0); + assert_ok!(Utility::dispatch_as_fallible( + RuntimeOrigin::root(), + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed(666))), + Box::new(call_transfer(777, 100)) + )); + assert_eq!(Balances::free_balance(666), 0); + assert_eq!(Balances::free_balance(777), 100); + + assert_noop!( + Utility::dispatch_as_fallible( + RuntimeOrigin::root(), + Box::new(OriginCaller::system(frame_system::RawOrigin::Signed(777))), + Box::new(RuntimeCall::Timestamp(TimestampCall::set { now: 0 })) + ), + DispatchError::BadOrigin, + ); + }) +} + +#[test] +fn if_else_failing_fallback_call() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_err_ignore_postinfo!( + Utility::if_else( + RuntimeOrigin::signed(1), + call_transfer(2, 11).into(), + call_transfer(2, 11).into() + ), + TokenError::FundsUnavailable + ); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + }) +} + +#[test] +fn if_else_with_nested_if_else_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + + let main_call = call_transfer(2, 11).into(); + let fallback_call = call_transfer(2, 5).into(); + + let nested_if_else_call = RuntimeCall::Utility(UtilityCall::if_else { + main: main_call, + fallback: fallback_call, + }) + .into(); + + // Nested `if_else` call. + assert_ok!(Utility::if_else( + RuntimeOrigin::signed(1), + nested_if_else_call, + call_transfer(2, 7).into() + )); + + // inner if_else fallback is executed. + assert_eq!(Balances::free_balance(1), 5); + assert_eq!(Balances::free_balance(2), 15); + + // Ensure the correct event was triggered for the main call(nested if_else). + System::assert_last_event(utility::Event::IfElseMainSuccess.into()); + }); +} diff --git a/pallets/utility/src/weights.rs b/pallets/utility/src/weights.rs index 502f85a3f1..eb1f036087 100644 --- a/pallets/utility/src/weights.rs +++ b/pallets/utility/src/weights.rs @@ -18,33 +18,37 @@ //! Autogenerated weights for `pallet_utility` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=dev +// --extrinsic=* +// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm +// --pallet=pallet_utility +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/utility/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_utility +// --heap-pages=4096 +// --template=substrate/.maintain/frame-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/utility/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes +// --genesis-builder-policy=none +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage,pallet_election_provider_multi_block,pallet_election_provider_multi_block::signed,pallet_election_provider_multi_block::unsigned,pallet_election_provider_multi_block::verifier #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] +#![allow(dead_code)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; @@ -56,6 +60,8 @@ pub trait WeightInfo { fn batch_all(c: u32, ) -> Weight; fn dispatch_as() -> Weight; fn force_batch(c: u32, ) -> Weight; + fn dispatch_as_fallible() -> Weight; + fn if_else() -> Weight; } /// Weights for `pallet_utility` using the Substrate node and recommended hardware. @@ -68,12 +74,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 5_312_000 picoseconds. - Weight::from_parts(2_694_370, 3997) - // Standard Error: 5_055 - .saturating_add(Weight::from_parts(5_005_941, 0).saturating_mul(c.into())) + // Minimum execution time: 3_972_000 picoseconds. + Weight::from_parts(4_034_000, 3997) + // Standard Error: 2_323 + .saturating_add(Weight::from_parts(4_914_560, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -82,10 +88,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) fn as_derivative() -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 9_263_000 picoseconds. - Weight::from_parts(9_639_000, 3997) + // Minimum execution time: 5_866_000 picoseconds. + Weight::from_parts(6_097_000, 3997) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -95,20 +101,20 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 5_120_000 picoseconds. - Weight::from_parts(12_948_874, 3997) - // Standard Error: 4_643 - .saturating_add(Weight::from_parts(5_162_821, 0).saturating_mul(c.into())) + // Minimum execution time: 3_983_000 picoseconds. + Weight::from_parts(4_075_000, 3997) + // Standard Error: 2_176 + .saturating_add(Weight::from_parts(5_127_263, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_126_000 picoseconds. - Weight::from_parts(7_452_000, 0) + // Minimum execution time: 5_530_000 picoseconds. + Weight::from_parts(5_720_000, 0) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -117,14 +123,33 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 5_254_000 picoseconds. - Weight::from_parts(4_879_712, 3997) - // Standard Error: 4_988 - .saturating_add(Weight::from_parts(4_955_816, 0).saturating_mul(c.into())) + // Minimum execution time: 3_880_000 picoseconds. + Weight::from_parts(4_035_000, 3997) + // Standard Error: 1_682 + .saturating_add(Weight::from_parts(4_902_729, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } + fn dispatch_as_fallible() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_417_000 picoseconds. + Weight::from_parts(5_705_000, 0) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:2 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + fn if_else() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `7004` + // Minimum execution time: 11_273_000 picoseconds. + Weight::from_parts(11_571_000, 7004) + .saturating_add(T::DbWeight::get().reads(3_u64)) + } } // For backwards compatibility and tests. @@ -136,12 +161,12 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 5_312_000 picoseconds. - Weight::from_parts(2_694_370, 3997) - // Standard Error: 5_055 - .saturating_add(Weight::from_parts(5_005_941, 0).saturating_mul(c.into())) + // Minimum execution time: 3_972_000 picoseconds. + Weight::from_parts(4_034_000, 3997) + // Standard Error: 2_323 + .saturating_add(Weight::from_parts(4_914_560, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -150,10 +175,10 @@ impl WeightInfo for () { /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) fn as_derivative() -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 9_263_000 picoseconds. - Weight::from_parts(9_639_000, 3997) + // Minimum execution time: 5_866_000 picoseconds. + Weight::from_parts(6_097_000, 3997) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) @@ -163,20 +188,20 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 5_120_000 picoseconds. - Weight::from_parts(12_948_874, 3997) - // Standard Error: 4_643 - .saturating_add(Weight::from_parts(5_162_821, 0).saturating_mul(c.into())) + // Minimum execution time: 3_983_000 picoseconds. + Weight::from_parts(4_075_000, 3997) + // Standard Error: 2_176 + .saturating_add(Weight::from_parts(5_127_263, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_126_000 picoseconds. - Weight::from_parts(7_452_000, 0) + // Minimum execution time: 5_530_000 picoseconds. + Weight::from_parts(5_720_000, 0) } /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -185,12 +210,31 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 5_254_000 picoseconds. - Weight::from_parts(4_879_712, 3997) - // Standard Error: 4_988 - .saturating_add(Weight::from_parts(4_955_816, 0).saturating_mul(c.into())) + // Minimum execution time: 3_880_000 picoseconds. + Weight::from_parts(4_035_000, 3997) + // Standard Error: 1_682 + .saturating_add(Weight::from_parts(4_902_729, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } + fn dispatch_as_fallible() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_417_000 picoseconds. + Weight::from_parts(5_705_000, 0) + } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:2 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + fn if_else() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `7004` + // Minimum execution time: 11_273_000 picoseconds. + Weight::from_parts(11_571_000, 7004) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index aa87c52a19..f3589f9f0a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -884,6 +884,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = System; } pub struct Proxier;