diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index d7b0556bfdb208e44fe5aa925f9cda432194ab8c..f0bdcef29b962ba3af04f0054cb2142ce46059d5 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -4148,6 +4148,7 @@ dependencies = [ "sp-runtime", "sp-std", "sp-storage", + "substrate-test-utils", ] [[package]] diff --git a/substrate/bin/node/executor/tests/basic.rs b/substrate/bin/node/executor/tests/basic.rs index a8eb1bfe3dd456f5bffe10dfa5b5c04807d9d73c..9350c3546ff8576d6716e9ba455455bbb6486cf1 100644 --- a/substrate/bin/node/executor/tests/basic.rs +++ b/substrate/bin/node/executor/tests/basic.rs @@ -21,12 +21,10 @@ use frame_support::{ traits::Currency, weights::{GetDispatchInfo, DispatchInfo, DispatchClass, constants::ExtrinsicBaseWeight}, }; -use sp_core::{ - NeverNativeValue, map, traits::Externalities, storage::{well_known_keys, Storage}, -}; +use sp_core::{NeverNativeValue, traits::Externalities, storage::well_known_keys}; use sp_runtime::{ ApplyExtrinsicResult, Fixed128, - traits::{Hash as HashT, Convert, BlakeTwo256}, + traits::{Hash as HashT, Convert}, transaction_validity::InvalidTransaction, }; use pallet_contracts::ContractAddressFor; @@ -159,20 +157,13 @@ fn block_with_size(time: u64, nonce: u32, size: usize) -> (Vec<u8>, Hash) { #[test] fn panic_execution_with_foreign_code_gives_error() { - let mut t = TestExternalities::<BlakeTwo256>::new_with_code(BLOATY_CODE, Storage { - top: map![ - <frame_system::Account<Runtime>>::hashed_key_for(alice()) => { - (69u128, 0u8, 0u128, 0u128, 0u128).encode() - }, - <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => { - 69_u128.encode() - }, - <frame_system::BlockHash<Runtime>>::hashed_key_for(0) => { - vec![0u8; 32] - } - ], - children_default: map![], - }); + let mut t = new_test_ext(BLOATY_CODE, false); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(alice()), + (69u128, 0u8, 0u128, 0u128, 0u128).encode() + ); + t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69_u128.encode()); + t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]); let r = executor_call::<NeverNativeValue, fn() -> _>( &mut t, @@ -195,20 +186,13 @@ fn panic_execution_with_foreign_code_gives_error() { #[test] fn bad_extrinsic_with_native_equivalent_code_gives_error() { - let mut t = TestExternalities::<BlakeTwo256>::new_with_code(COMPACT_CODE, Storage { - top: map![ - <frame_system::Account<Runtime>>::hashed_key_for(alice()) => { - (0u32, 0u8, 69u128, 0u128, 0u128, 0u128).encode() - }, - <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => { - 69_u128.encode() - }, - <frame_system::BlockHash<Runtime>>::hashed_key_for(0) => { - vec![0u8; 32] - } - ], - children_default: map![], - }); + let mut t = new_test_ext(COMPACT_CODE, false); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(alice()), + (0u32, 0u8, 69u128, 0u128, 0u128, 0u128).encode() + ); + t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69_u128.encode()); + t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]); let r = executor_call::<NeverNativeValue, fn() -> _>( &mut t, @@ -231,18 +215,20 @@ fn bad_extrinsic_with_native_equivalent_code_gives_error() { #[test] fn successful_execution_with_native_equivalent_code_gives_ok() { - let mut t = TestExternalities::<BlakeTwo256>::new_with_code(COMPACT_CODE, Storage { - top: map![ - <frame_system::Account<Runtime>>::hashed_key_for(alice()) => { - (0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode() - }, - <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => { - (111 * DOLLARS).encode() - }, - <frame_system::BlockHash<Runtime>>::hashed_key_for(0) => vec![0u8; 32] - ], - children_default: map![], - }); + let mut t = new_test_ext(COMPACT_CODE, false); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(alice()), + (0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode() + ); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(bob()), + (0u32, 0u8, 0 * DOLLARS, 0u128, 0u128, 0u128).encode() + ); + t.insert( + <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), + (111 * DOLLARS).encode() + ); + t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]); let r = executor_call::<NeverNativeValue, fn() -> _>( &mut t, @@ -273,18 +259,20 @@ fn successful_execution_with_native_equivalent_code_gives_ok() { #[test] fn successful_execution_with_foreign_code_gives_ok() { - let mut t = TestExternalities::<BlakeTwo256>::new_with_code(BLOATY_CODE, Storage { - top: map![ - <frame_system::Account<Runtime>>::hashed_key_for(alice()) => { - (0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode() - }, - <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => { - (111 * DOLLARS).encode() - }, - <frame_system::BlockHash<Runtime>>::hashed_key_for(0) => vec![0u8; 32] - ], - children_default: map![], - }); + let mut t = new_test_ext(BLOATY_CODE, false); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(alice()), + (0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode() + ); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(bob()), + (0u32, 0u8, 0 * DOLLARS, 0u128, 0u128, 0u128).encode() + ); + t.insert( + <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), + (111 * DOLLARS).encode() + ); + t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]); let r = executor_call::<NeverNativeValue, fn() -> _>( &mut t, @@ -708,15 +696,13 @@ fn native_big_block_import_fails_on_fallback() { #[test] fn panic_execution_gives_error() { - let mut t = TestExternalities::<BlakeTwo256>::new_with_code(BLOATY_CODE, Storage { - top: map![ - <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => { - 0_u128.encode() - }, - <frame_system::BlockHash<Runtime>>::hashed_key_for(0) => vec![0u8; 32] - ], - children_default: map![], - }); + let mut t = new_test_ext(BLOATY_CODE, false); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(alice()), + (0u32, 0u8, 0 * DOLLARS, 0u128, 0u128, 0u128).encode() + ); + t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 0_u128.encode()); + t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]); let r = executor_call::<NeverNativeValue, fn() -> _>( &mut t, @@ -739,18 +725,20 @@ fn panic_execution_gives_error() { #[test] fn successful_execution_gives_ok() { - let mut t = TestExternalities::<BlakeTwo256>::new_with_code(COMPACT_CODE, Storage { - top: map![ - <frame_system::Account<Runtime>>::hashed_key_for(alice()) => { - (0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode() - }, - <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => { - (111 * DOLLARS).encode() - }, - <frame_system::BlockHash<Runtime>>::hashed_key_for(0) => vec![0u8; 32] - ], - children_default: map![], - }); + let mut t = new_test_ext(COMPACT_CODE, false); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(alice()), + (0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode() + ); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(bob()), + (0u32, 0u8, 0 * DOLLARS, 0u128, 0u128, 0u128).encode() + ); + t.insert( + <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), + (111 * DOLLARS).encode() + ); + t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]); let r = executor_call::<NeverNativeValue, fn() -> _>( &mut t, @@ -760,7 +748,12 @@ fn successful_execution_gives_ok() { None, ).0; assert!(r.is_ok()); + t.execute_with(|| { + assert_eq!(Balances::total_balance(&alice()), 111 * DOLLARS); + }); + let fm = t.execute_with(TransactionPayment::next_fee_multiplier); + let r = executor_call::<NeverNativeValue, fn() -> _>( &mut t, "BlockBuilder_apply_extrinsic", diff --git a/substrate/bin/node/executor/tests/fees.rs b/substrate/bin/node/executor/tests/fees.rs index 77db2ca66993c521a238c6380bcf0c3112d1e600..0e55f781e78c20423df0ca1372ed70448c9c7c17 100644 --- a/substrate/bin/node/executor/tests/fees.rs +++ b/substrate/bin/node/executor/tests/fees.rs @@ -21,8 +21,8 @@ use frame_support::{ traits::Currency, weights::{GetDispatchInfo, constants::ExtrinsicBaseWeight}, }; -use sp_core::{NeverNativeValue, map, storage::Storage}; -use sp_runtime::{Fixed128, Perbill, traits::{Convert, BlakeTwo256}}; +use sp_core::NeverNativeValue; +use sp_runtime::{Fixed128, Perbill, traits::Convert}; use node_runtime::{ CheckedExtrinsic, Call, Runtime, Balances, TransactionPayment, TransactionByteFee, WeightFeeCoefficient, @@ -131,21 +131,20 @@ fn transaction_fee_is_correct_ultimate() { // - 1 MILLICENTS in substrate node. // - 1 milli-dot based on current polkadot runtime. // (this baed on assigning 0.1 CENT to the cheapest tx with `weight = 100`) - let mut t = TestExternalities::<BlakeTwo256>::new_with_code(COMPACT_CODE, Storage { - top: map![ - <frame_system::Account<Runtime>>::hashed_key_for(alice()) => { - (0u32, 0u8, 100 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode() - }, - <frame_system::Account<Runtime>>::hashed_key_for(bob()) => { - (0u32, 0u8, 10 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode() - }, - <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => { - (110 * DOLLARS).encode() - }, - <frame_system::BlockHash<Runtime>>::hashed_key_for(0) => vec![0u8; 32] - ], - children_default: map![], - }); + let mut t = new_test_ext(COMPACT_CODE, false); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(alice()), + (0u32, 0u8, 100 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode() + ); + t.insert( + <frame_system::Account<Runtime>>::hashed_key_for(bob()), + (0u32, 0u8, 10 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode() + ); + t.insert( + <pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), + (110 * DOLLARS).encode() + ); + t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]); let tip = 1_000_000; let xt = sign(CheckedExtrinsic { diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index da99617f1c5b377de5233c7a23345fb6fe7cfe23..32ba8c94db09f7ad5cd24b842727c67dedee787e 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -344,6 +344,7 @@ parameter_types! { pub const CooloffPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; // One cent: $10,000 / MB pub const PreimageByteDeposit: Balance = 1 * CENTS; + pub const MaxVotes: u32 = 100; } impl pallet_democracy::Trait for Runtime { @@ -376,6 +377,7 @@ impl pallet_democracy::Trait for Runtime { type PreimageByteDeposit = PreimageByteDeposit; type Slash = Treasury; type Scheduler = Scheduler; + type MaxVotes = MaxVotes; } parameter_types! { diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 9183b48f371748123fb4c8009a980f9e4d568ddf..5c339d2bc511867452c0c34eb85f6d44372d006a 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -26,6 +26,7 @@ sp-core = { version = "2.0.0-dev", path = "../../primitives/core" } pallet-balances = { version = "2.0.0-dev", path = "../balances" } pallet-scheduler = { version = "2.0.0-dev", path = "../scheduler" } sp-storage = { version = "2.0.0-dev", path = "../../primitives/storage" } +substrate-test-utils = { version = "2.0.0-dev", path = "../../test-utils" } hex-literal = "0.2.1" [features] @@ -43,5 +44,6 @@ std = [ runtime-benchmarks = [ "frame-benchmarking", "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] diff --git a/substrate/frame/democracy/src/benchmarking.rs b/substrate/frame/democracy/src/benchmarking.rs index b60250a5a65aa0a811218a3e08138099c74ba581..9fa619a994fa32aca63292b8cffaedc9c79653bb 100644 --- a/substrate/frame/democracy/src/benchmarking.rs +++ b/substrate/frame/democracy/src/benchmarking.rs @@ -34,7 +34,6 @@ const MAX_USERS: u32 = 1000; const MAX_REFERENDUMS: u32 = 100; const MAX_PROPOSALS: u32 = 100; const MAX_SECONDERS: u32 = 100; -const MAX_VETOERS: u32 = 100; const MAX_BYTES: u32 = 16_384; fn assert_last_event<T: Trait>(generic_event: <T as Trait>::Event) { @@ -56,7 +55,11 @@ fn add_proposal<T: Trait>(n: u32) -> Result<T::Hash, &'static str> { let value = T::MinimumDeposit::get(); let proposal_hash: T::Hash = T::Hashing::hash_of(&n); - Democracy::<T>::propose(RawOrigin::Signed(other).into(), proposal_hash, value.into())?; + Democracy::<T>::propose( + RawOrigin::Signed(other).into(), + proposal_hash, + value.into(), + )?; Ok(proposal_hash) } @@ -134,15 +137,15 @@ benchmarks! { // Create s existing "seconds" for i in 0 .. s { let seconder = funded_account::<T>("seconder", i); - Democracy::<T>::second(RawOrigin::Signed(seconder).into(), 0)?; + Democracy::<T>::second(RawOrigin::Signed(seconder).into(), 0, u32::max_value())?; } let deposits = Democracy::<T>::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.1.len(), (s + 1) as usize, "Seconds not recorded"); - }: _(RawOrigin::Signed(caller), 0) + assert_eq!(deposits.0.len(), (s + 1) as usize, "Seconds not recorded"); + }: _(RawOrigin::Signed(caller), 0, u32::max_value()) verify { let deposits = Democracy::<T>::deposit_of(0).ok_or("Proposal not created")?; - assert_eq!(deposits.1.len(), (s + 2) as usize, "`second` benchmark did not work"); + assert_eq!(deposits.0.len(), (s + 2) as usize, "`second` benchmark did not work"); } vote_new { @@ -300,7 +303,7 @@ benchmarks! { // Worst case scenario, we external propose a previously blacklisted proposal external_propose { let p in 1 .. MAX_PROPOSALS; - let v in 1 .. MAX_VETOERS; + let v in 1 .. MAX_VETOERS as u32; let origin = T::ExternalOrigin::successful_origin(); let proposal_hash = T::Hashing::hash_of(&p); @@ -361,7 +364,7 @@ benchmarks! { veto_external { // Existing veto-ers - let v in 0 .. MAX_VETOERS; + let v in 0 .. MAX_VETOERS as u32; let proposal_hash: T::Hash = T::Hashing::hash_of(&v); @@ -685,7 +688,7 @@ benchmarks! { assert!(Preimages::<T>::contains_key(proposal_hash)); let caller = funded_account::<T>("caller", 0); - }: _(RawOrigin::Signed(caller), proposal_hash.clone()) + }: _(RawOrigin::Signed(caller), proposal_hash.clone(), u32::max_value()) verify { let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); assert!(!Preimages::<T>::contains_key(proposal_hash)); diff --git a/substrate/frame/democracy/src/lib.rs b/substrate/frame/democracy/src/lib.rs index 1cf89f84c50b4efe77a5bf914b8ab1e18f3c3646..e0f1ec9b5c707dac4dfde47fbb6ce24386427b42 100644 --- a/substrate/frame/democracy/src/lib.rs +++ b/substrate/frame/democracy/src/lib.rs @@ -169,14 +169,16 @@ use sp_runtime::{ DispatchResult, DispatchError, RuntimeDebug, traits::{Zero, Hash, Dispatchable, Saturating}, }; -use codec::{Encode, Decode}; +use codec::{Encode, Decode, Input}; use frame_support::{ decl_module, decl_storage, decl_event, decl_error, ensure, Parameter, + storage::IterableStorageMap, weights::{Weight, DispatchClass}, traits::{ Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get, OnUnbalanced, BalanceStatus, schedule::Named as ScheduleNamed, EnsureOrigin - } + }, + dispatch::DispatchResultWithPostInfo, }; use frame_system::{self as system, ensure_signed, ensure_root}; @@ -197,6 +199,11 @@ pub mod benchmarking; const DEMOCRACY_ID: LockIdentifier = *b"democrac"; +/// The maximum number of vetoers on a single proposal used to compute Weight. +/// +/// NOTE: This is not enforced by any logic. +pub const MAX_VETOERS: Weight = 100; + /// A proposal index. pub type PropIndex = u32; @@ -265,6 +272,11 @@ pub trait Trait: frame_system::Trait + Sized { type CancellationOrigin: EnsureOrigin<Self::Origin>; /// Origin for anyone able to veto proposals. + /// + /// # Warning + /// + /// The number of Vetoers for a proposal must be small, extrinsics are weighted according to + /// [MAX_VETOERS](./const.MAX_VETOERS.html) type VetoOrigin: EnsureOrigin<Self::Origin, Success=Self::AccountId>; /// Period in blocks where an external proposal may not be re-submitted after being vetoed. @@ -278,6 +290,12 @@ pub trait Trait: frame_system::Trait + Sized { /// The Scheduler. type Scheduler: ScheduleNamed<Self::BlockNumber, Self::Proposal>; + + /// The maximum number of votes for an account. + /// + /// Also used to compute weight, an overly big value can + /// lead to extrinsic with very big weight: see `delegate` for instance. + type MaxVotes: Get<u32>; } #[derive(Clone, Encode, Decode, RuntimeDebug)] @@ -304,6 +322,14 @@ impl<AccountId, Balance, BlockNumber> PreimageStatus<AccountId, Balance, BlockNu } } +// A value placed in storage that represents the current version of the Democracy storage. +// This value is used by the `on_runtime_upgrade` logic to determine whether we run +// storage migration logic. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)] +enum Releases { + V1, +} + decl_storage! { trait Store for Module<T: Trait> as Democracy { // TODO: Refactor public proposal queue into its own pallet. @@ -314,7 +340,7 @@ decl_storage! { pub PublicProps get(fn public_props): Vec<(PropIndex, T::Hash, T::AccountId)>; /// Those who have locked a deposit. pub DepositOf get(fn deposit_of): - map hasher(twox_64_concat) PropIndex => Option<(BalanceOf<T>, Vec<T::AccountId>)>; + map hasher(twox_64_concat) PropIndex => Option<(Vec<T::AccountId>, BalanceOf<T>)>; /// Map of hashes to the proposal preimage, along with who registered it and their deposit. /// The block number is the block at which it was deposited. @@ -368,6 +394,11 @@ decl_storage! { /// Record of all proposals that have been subject to emergency cancellation. pub Cancellations: map hasher(identity) T::Hash => bool; + + /// Storage version of the pallet. + /// + /// New networks start with last version. + StorageVersion build(|_| Some(Releases::V1)): Option<Releases>; } } @@ -492,6 +523,62 @@ decl_error! { InstantNotAllowed, /// Delegation to oneself makes no sense. Nonsense, + /// Invalid upper bound. + WrongUpperBound, + /// Maximum number of votes reached. + MaxVotesReached, + } +} + +/// Functions for calcuating the weight of some dispatchables. +mod weight_for { + use frame_support::{traits::Get, weights::Weight}; + use super::Trait; + + /// Calculate the weight for `delegate`. + /// - Db reads: 2*`VotingOf`, `balances locks` + /// - Db writes: 2*`VotingOf`, `balances locks` + /// - Db reads per votes: `ReferendumInfoOf` + /// - Db writes per votes: `ReferendumInfoOf` + /// - Base Weight: 65.78 + 8.229 * R µs + // NOTE: weight must cover an incorrect voting of origin with 100 votes. + pub(crate) fn delegate<T: Trait>(votes: Weight) -> Weight { + T::DbWeight::get().reads_writes(votes.saturating_add(3), votes.saturating_add(3)) + .saturating_add(66_000_000) + .saturating_add(votes.saturating_mul(8_100_000)) + } + + /// Calculate the weight for `undelegate`. + /// - Db reads: 2*`VotingOf` + /// - Db writes: 2*`VotingOf` + /// - Db reads per votes: `ReferendumInfoOf` + /// - Db writes per votes: `ReferendumInfoOf` + /// - Base Weight: 33.29 + 8.104 * R µs + pub(crate) fn undelegate<T: Trait>(votes: Weight) -> Weight { + T::DbWeight::get().reads_writes(votes.saturating_add(2), votes.saturating_add(2)) + .saturating_add(33_000_000) + .saturating_add(votes.saturating_mul(8_000_000)) + } + + /// Calculate the weight for `proxy_delegate`. + /// same as `delegate with additional: + /// - Db reads: `Proxy`, `proxy account` + /// - Db writes: `proxy account` + /// - Base Weight: 68.61 + 8.039 * R µs + pub(crate) fn proxy_delegate<T: Trait>(votes: Weight) -> Weight { + T::DbWeight::get().reads_writes(votes.saturating_add(5), votes.saturating_add(4)) + .saturating_add(69_000_000) + .saturating_add(votes.saturating_mul(8_000_000)) + } + + /// Calculate the weight for `proxy_undelegate`. + /// same as `undelegate with additional: + /// Db reads: `Proxy` + /// Base Weight: 39 + 7.958 * R µs + pub(crate) fn proxy_undelegate<T: Trait>(votes: Weight) -> Weight { + T::DbWeight::get().reads_writes(votes.saturating_add(3), votes.saturating_add(2)) + .saturating_add(40_000_000) + .saturating_add(votes.saturating_mul(8_000_000)) } } @@ -526,6 +613,22 @@ decl_module! { fn deposit_event() = default; + fn on_runtime_upgrade() -> Weight { + if let None = StorageVersion::get() { + StorageVersion::put(Releases::V1); + + DepositOf::<T>::translate::< + (BalanceOf<T>, Vec<T::AccountId>), _ + >(|_, (balance, accounts)| { + Some((accounts, balance)) + }); + + T::MaximumBlockWeight::get() + } else { + T::DbWeight::get().reads(1) + } + } + /// Propose a sensitive action to be taken. /// /// The dispatch origin of this call must be _Signed_ and the sender must @@ -537,22 +640,22 @@ decl_module! { /// Emits `Proposed`. /// /// # <weight> - /// - `O(P)` - /// - P is the number proposals in the `PublicProps` vec. - /// - Two DB changes, one DB entry. + /// - Complexity: `O(1)` + /// - Db reads: `PublicPropCount`, `PublicProps` + /// - Db writes: `PublicPropCount`, `PublicProps`, `DepositOf` + /// ------------------- + /// Base Weight: 42.58 + .127 * P µs with `P` the number of proposals `PublicProps` /// # </weight> - #[weight = 5_000_000_000] - fn propose(origin, - proposal_hash: T::Hash, - #[compact] value: BalanceOf<T> - ) { + #[weight = 50_000_000 + T::DbWeight::get().reads_writes(2, 3)] + fn propose(origin, proposal_hash: T::Hash, #[compact] value: BalanceOf<T>) { let who = ensure_signed(origin)?; ensure!(value >= T::MinimumDeposit::get(), Error::<T>::ValueLow); + T::Currency::reserve(&who, value)?; let index = Self::public_prop_count(); PublicPropCount::put(index + 1); - <DepositOf<T>>::insert(index, (value, &[&who][..])); + <DepositOf<T>>::insert(index, (&[&who][..], value)); <PublicProps<T>>::append((index, proposal_hash, who)); @@ -565,19 +668,30 @@ decl_module! { /// must have funds to cover the deposit, equal to the original deposit. /// /// - `proposal`: The index of the proposal to second. + /// - `seconds_upper_bound`: an upper bound on the current number of seconds on this + /// proposal. Extrinsic is weighted according to this value with no refund. /// /// # <weight> - /// - `O(S)`. - /// - S is the number of seconds a proposal already has. - /// - One DB entry. + /// - Complexity: `O(S)` where S is the number of seconds a proposal already has. + /// - Db reads: `DepositOf` + /// - Db writes: `DepositOf` + /// --------- + /// - Base Weight: 22.28 + .229 * S µs /// # </weight> - #[weight = 5_000_000_000] - fn second(origin, #[compact] proposal: PropIndex) { + #[weight = 23_000_000 + .saturating_add(230_000.saturating_mul(Weight::from(*seconds_upper_bound))) + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + ] + fn second(origin, #[compact] proposal: PropIndex, #[compact] seconds_upper_bound: u32) { let who = ensure_signed(origin)?; + + let seconds = Self::len_of_deposit_of(proposal) + .ok_or_else(|| Error::<T>::ProposalMissing)?; + ensure!(seconds <= seconds_upper_bound, Error::<T>::WrongUpperBound); let mut deposit = Self::deposit_of(proposal) .ok_or(Error::<T>::ProposalMissing)?; - T::Currency::reserve(&who, deposit.0)?; - deposit.1.push(who); + T::Currency::reserve(&who, deposit.1)?; + deposit.0.push(who); <DepositOf<T>>::insert(proposal, deposit); } @@ -590,11 +704,16 @@ decl_module! { /// - `vote`: The vote configuration. /// /// # <weight> - /// - `O(R)`. - /// - R is the number of referendums the voter has voted on. - /// - One DB change, one DB entry. + /// - Complexity: `O(R)` where R is the number of referendums the voter has voted on. + /// weight is charged as if maximum votes. + /// - Db reads: `ReferendumInfoOf`, `VotingOf`, `balances locks` + /// - Db writes: `ReferendumInfoOf`, `VotingOf`, `balances locks` + /// -------------------- + /// - Base Weight: + /// - Vote New: 49.24 + .333 * R µs + /// - Vote Existing: 49.94 + .343 * R µs /// # </weight> - #[weight = 200_000_000] + #[weight = 50_000_000 + 350_000 * Weight::from(T::MaxVotes::get()) + T::DbWeight::get().reads_writes(3, 3)] fn vote(origin, #[compact] ref_index: ReferendumIndex, vote: AccountVote<BalanceOf<T>>, @@ -612,10 +731,16 @@ decl_module! { /// - `vote`: The vote configuration. /// /// # <weight> - /// - `O(1)`. - /// - One DB change, one DB entry. + /// - Complexity: `O(R)` where R is the number of referendums the proxy has voted on. + /// weight is charged as if maximum votes. + /// - Db reads: `ReferendumInfoOf`, `VotingOf`, `balances locks`, `Proxy`, `proxy account` + /// - Db writes: `ReferendumInfoOf`, `VotingOf`, `balances locks` + /// ------------ + /// - Base Weight: + /// - Proxy Vote New: 54.35 + .344 * R µs + /// - Proxy Vote Existing: 54.35 + .35 * R µs /// # </weight> - #[weight = 200_000_000] + #[weight = 55_000_000 + 350_000 * Weight::from(T::MaxVotes::get()) + T::DbWeight::get().reads_writes(5, 3)] fn proxy_vote(origin, #[compact] ref_index: ReferendumIndex, vote: AccountVote<BalanceOf<T>>, @@ -633,9 +758,13 @@ decl_module! { /// -`ref_index`: The index of the referendum to cancel. /// /// # <weight> - /// - `O(1)`. + /// - Complexity: `O(1)`. + /// - Db reads: `ReferendumInfoOf`, `Cancellations` + /// - Db writes: `ReferendumInfoOf`, `Cancellations` + /// ------------- + /// - Base Weight: 34.25 µs /// # </weight> - #[weight = (500_000_000, DispatchClass::Operational)] + #[weight = (35_000_000 + T::DbWeight::get().reads_writes(2, 2), DispatchClass::Operational)] fn emergency_cancel(origin, ref_index: ReferendumIndex) { T::CancellationOrigin::ensure_origin(origin)?; @@ -655,10 +784,13 @@ decl_module! { /// - `proposal_hash`: The preimage hash of the proposal. /// /// # <weight> - /// - `O(1)`. - /// - One DB change. + /// - Complexity `O(V)` with V number of vetoers in the blacklist of proposal. + /// Decoding vec of length V. Charged as maximum + /// - Db reads: `NextExternal`, `Blacklist` + /// - Db writes: `NextExternal` + /// - Base Weight: 13.8 + .106 * V µs /// # </weight> - #[weight = 5_000_000_000] + #[weight = 15_000_000 + 110_000 * MAX_VETOERS + T::DbWeight::get().reads_writes(2, 1)] fn external_propose(origin, proposal_hash: T::Hash) { T::ExternalOrigin::ensure_origin(origin)?; ensure!(!<NextExternal<T>>::exists(), Error::<T>::DuplicateProposal); @@ -682,10 +814,11 @@ decl_module! { /// pre-scheduled `external_propose` call. /// /// # <weight> - /// - `O(1)`. - /// - One DB change. + /// - Complexity: `O(1)` + /// - Db write: `NextExternal` + /// - Base Weight: 3.065 µs /// # </weight> - #[weight = 5_000_000_000] + #[weight = 3_100_000 + T::DbWeight::get().writes(1)] fn external_propose_majority(origin, proposal_hash: T::Hash) { T::ExternalMajorityOrigin::ensure_origin(origin)?; <NextExternal<T>>::put((proposal_hash, VoteThreshold::SimpleMajority)); @@ -702,10 +835,11 @@ decl_module! { /// pre-scheduled `external_propose` call. /// /// # <weight> - /// - `O(1)`. - /// - One DB change. + /// - Complexity: `O(1)` + /// - Db write: `NextExternal` + /// - Base Weight: 3.087 µs /// # </weight> - #[weight = 5_000_000_000] + #[weight = 3_100_000 + T::DbWeight::get().writes(1)] fn external_propose_default(origin, proposal_hash: T::Hash) { T::ExternalDefaultOrigin::ensure_origin(origin)?; <NextExternal<T>>::put((proposal_hash, VoteThreshold::SuperMajorityAgainst)); @@ -726,11 +860,12 @@ decl_module! { /// Emits `Started`. /// /// # <weight> - /// - One DB clear. - /// - One DB change. - /// - One extra DB entry. + /// - Complexity: `O(1)` + /// - Db reads: `NextExternal`, `ReferendumCount` + /// - Db writes: `NextExternal`, `ReferendumCount`, `ReferendumInfoOf` + /// - Base Weight: 30.1 µs /// # </weight> - #[weight = 200_000_000] + #[weight = 30_000_000 + T::DbWeight::get().reads_writes(2, 3)] fn fast_track(origin, proposal_hash: T::Hash, voting_period: T::BlockNumber, @@ -775,13 +910,13 @@ decl_module! { /// Emits `Vetoed`. /// /// # <weight> - /// - Two DB entries. - /// - One DB clear. - /// - Performs a binary search on `existing_vetoers` which should not - /// be very large. - /// - O(log v), v is number of `existing_vetoers` + /// - Complexity: `O(V + log(V))` where V is number of `existing vetoers` + /// Performs a binary search on `existing_vetoers` which should not be very large. + /// - Db reads: `NextExternal`, `Blacklist` + /// - Db writes: `NextExternal`, `Blacklist` + /// - Base Weight: 29.87 + .188 * V µs /// # </weight> - #[weight = 200_000_000] + #[weight = 30_000_000 + 180_000 * MAX_VETOERS + T::DbWeight::get().reads_writes(2, 2)] fn veto_external(origin, proposal_hash: T::Hash) { let who = T::VetoOrigin::ensure_origin(origin)?; @@ -812,9 +947,11 @@ decl_module! { /// - `ref_index`: The index of the referendum to cancel. /// /// # <weight> - /// - `O(1)`. + /// - Complexity: `O(1)`. + /// - Db writes: `ReferendumInfoOf` + /// - Base Weight: 21.57 µs /// # </weight> - #[weight = (0, DispatchClass::Operational)] + #[weight = (22_000_000 + T::DbWeight::get().writes(1), DispatchClass::Operational)] fn cancel_referendum(origin, #[compact] ref_index: ReferendumIndex) { ensure_root(origin)?; Self::internal_cancel_referendum(ref_index); @@ -827,22 +964,24 @@ decl_module! { /// - `which`: The index of the referendum to cancel. /// /// # <weight> - /// - One DB change. - /// - O(d) where d is the items in the dispatch queue. + /// - `O(D)` where `D` is the items in the dispatch queue. Weighted as `D = 10`. + /// - Db reads: `scheduler lookup`, scheduler agenda` + /// - Db writes: `scheduler lookup`, scheduler agenda` + /// - Base Weight: 36.78 + 3.277 * D µs /// # </weight> - #[weight = (0, DispatchClass::Operational)] + #[weight = (68_000_000 + T::DbWeight::get().reads_writes(2, 2), DispatchClass::Operational)] fn cancel_queued(origin, which: ReferendumIndex) { ensure_root(origin)?; T::Scheduler::cancel_named((DEMOCRACY_ID, which).encode()) .map_err(|_| Error::<T>::ProposalMissing)?; } + /// Weight: see `begin_block` fn on_initialize(n: T::BlockNumber) -> Weight { - if let Err(e) = Self::begin_block(n) { + Self::begin_block(n).unwrap_or_else(|e| { sp_runtime::print(e); - } - - 0 + 0 + }) } /// Specify a proxy that is already open to us. Called by the stash. @@ -854,9 +993,12 @@ decl_module! { /// - `proxy`: The account that will be activated as proxy. /// /// # <weight> - /// - One extra DB entry. + /// - Complexity: `O(1)` + /// - Db reads: `Proxy` + /// - Db writes: `Proxy` + /// - Base Weight: 7.972 µs /// # </weight> - #[weight = 100_000_000] + #[weight = 8_000_000 + T::DbWeight::get().reads_writes(1, 1)] fn activate_proxy(origin, proxy: T::AccountId) { let who = ensure_signed(origin)?; Proxy::<T>::try_mutate(&proxy, |a| match a.take() { @@ -877,9 +1019,12 @@ decl_module! { /// The dispatch origin of this call must be _Signed_. /// /// # <weight> - /// - One DB clear. + /// - Complexity: `O(1)` + /// - Db reads: `Proxy`, `sender account` + /// - Db writes: `Proxy`, `sender account` + /// - Base Weight: 15.41 µs /// # </weight> - #[weight = 100_000_000] + #[weight = 16_000_000 + T::DbWeight::get().reads_writes(1, 1)] fn close_proxy(origin) { let who = ensure_signed(origin)?; Proxy::<T>::mutate(&who, |a| { @@ -901,9 +1046,12 @@ decl_module! { /// - `proxy`: The account that will be deactivated as proxy. /// /// # <weight> - /// - One DB clear. + /// - Complexity: `O(1)` + /// - Db reads: `Proxy` + /// - Db writes: `Proxy` + /// - Base Weight: 8.03 µs /// # </weight> - #[weight = 100_000_000] + #[weight = 8_000_000 + T::DbWeight::get().reads_writes(1, 1)] fn deactivate_proxy(origin, proxy: T::AccountId) { let who = ensure_signed(origin)?; Proxy::<T>::try_mutate(&proxy, |a| match a.take() { @@ -935,11 +1083,26 @@ decl_module! { /// Emits `Delegated`. /// /// # <weight> + /// - Complexity: `O(R)` where R is the number of referendums the voter delegating to has + /// voted on. Weight is charged as if maximum votes. + /// - Db reads: 2*`VotingOf`, `balances locks` + /// - Db writes: 2*`VotingOf`, `balances locks` + /// - Db reads per votes: `ReferendumInfoOf` + /// - Db writes per votes: `ReferendumInfoOf` + /// - Base Weight: 65.78 + 8.229 * R µs + // NOTE: weight must cover an incorrect voting of origin with 100 votes. /// # </weight> - #[weight = 500_000_000] - pub fn delegate(origin, to: T::AccountId, conviction: Conviction, balance: BalanceOf<T>) { + #[weight = weight_for::delegate::<T>(T::MaxVotes::get().into())] + pub fn delegate( + origin, + to: T::AccountId, + conviction: Conviction, + balance: BalanceOf<T> + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::try_delegate(who, to, conviction, balance)?; + let votes = Self::try_delegate(who, to, conviction, balance)?; + + Ok(Some(weight_for::delegate::<T>(votes.into())).into()) } /// Undelegate the voting power of the sending account. @@ -953,12 +1116,20 @@ decl_module! { /// Emits `Undelegated`. /// /// # <weight> - /// - O(1). + /// - Complexity: `O(R)` where R is the number of referendums the voter delegating to has + /// voted on. Weight is charged as if maximum votes. + /// - Db reads: 2*`VotingOf` + /// - Db writes: 2*`VotingOf` + /// - Db reads per votes: `ReferendumInfoOf` + /// - Db writes per votes: `ReferendumInfoOf` + /// - Base Weight: 33.29 + 8.104 * R µs + // NOTE: weight must cover an incorrect voting of origin with 100 votes. /// # </weight> - #[weight = 500_000_000] - fn undelegate(origin) { + #[weight = weight_for::undelegate::<T>(T::MaxVotes::get().into())] + fn undelegate(origin) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::try_undelegate(who)?; + let votes = Self::try_undelegate(who)?; + Ok(Some(weight_for::undelegate::<T>(votes.into())).into()) } /// Clears all public proposals. @@ -967,12 +1138,12 @@ decl_module! { /// /// # <weight> /// - `O(1)`. - /// - One DB clear. + /// - Db writes: `PublicProps` + /// - Base Weight: 2.505 µs /// # </weight> - #[weight = 0] + #[weight = 2_500_000 + T::DbWeight::get().writes(1)] fn clear_public_proposals(origin) { ensure_root(origin)?; - <PublicProps<T>>::kill(); } @@ -986,10 +1157,13 @@ decl_module! { /// Emits `PreimageNoted`. /// /// # <weight> - /// - Dependent on the size of `encoded_proposal` but protected by a - /// required deposit. + /// - Complexity: `O(E)` with E size of `encoded_proposal` (protected by a required deposit). + /// - Db reads: `Preimages` + /// - Db writes: `Preimages` + /// - Base Weight: 37.93 + .004 * b µs /// # </weight> - #[weight = 100_000_000] + #[weight = 38_000_000 + 4_000 * Weight::from(encoded_proposal.len() as u32) + + T::DbWeight::get().reads_writes(1, 1)] fn note_preimage(origin, encoded_proposal: Vec<u8>) { let who = ensure_signed(origin)?; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); @@ -1022,12 +1196,17 @@ decl_module! { /// Emits `PreimageNoted`. /// /// # <weight> - /// - Dependent on the size of `encoded_proposal` and length of dispatch queue. + /// - Complexity: `O(E)` with E size of `encoded_proposal` (protected by a required deposit). + /// - Db reads: `Preimages` + /// - Db writes: `Preimages` + /// - Base Weight: 28.04 + .003 * b µs /// # </weight> - #[weight = 100_000_000] + #[weight = 28_000_000 + 3_000 * Weight::from(encoded_proposal.len() as u32) + + T::DbWeight::get().reads_writes(1, 1)] fn note_imminent_preimage(origin, encoded_proposal: Vec<u8>) { let who = ensure_signed(origin)?; let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); + Self::check_pre_image_is_missing(proposal_hash)?; let status = Preimages::<T>::get(&proposal_hash).ok_or(Error::<T>::NotImminent)?; let expiry = status.to_missing_expiry().ok_or(Error::<T>::DuplicatePreimage)?; @@ -1050,6 +1229,8 @@ decl_module! { /// The dispatch origin of this call must be _Signed_. /// /// - `proposal_hash`: The preimage hash of a proposal. + /// - `proposal_length_upper_bound`: an upper bound on length of the proposal. + /// Extrinsic is weighted according to this value with no refund. /// /// This will only work after `VotingPeriod` blocks from the time that the preimage was /// noted, if it's the same account doing it. If it's a different account, then it'll only @@ -1058,11 +1239,21 @@ decl_module! { /// Emits `PreimageReaped`. /// /// # <weight> - /// - One DB clear. + /// - Complexity: `O(D)` where D is length of proposal. + /// - Db reads: `Preimages` + /// - Db writes: `Preimages` + /// - Base Weight: 39.31 + .003 * b µs /// # </weight> - #[weight = 0] - fn reap_preimage(origin, proposal_hash: T::Hash) { + #[weight = (39_000_000 + T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(3_000.saturating_mul(Weight::from(*proposal_len_upper_bound)))] + fn reap_preimage(origin, proposal_hash: T::Hash, #[compact] proposal_len_upper_bound: u32) { let who = ensure_signed(origin)?; + + ensure!( + Self::pre_image_data_len(proposal_hash)? <= proposal_len_upper_bound, + Error::<T>::WrongUpperBound, + ); + let (provider, deposit, since, expiry) = <Preimages<T>>::get(&proposal_hash) .and_then(|m| match m { PreimageStatus::Available { provider, deposit, since, expiry, .. } @@ -1088,9 +1279,15 @@ decl_module! { /// - `target`: The account to remove the lock on. /// /// # <weight> - /// - `O(1)`. + /// - Complexity `O(R)` with R number of vote of target. + /// - Db reads: `VotingOf`, `balances locks`, `target account` + /// - Db writes: `VotingOf`, `balances locks`, `target account` + /// - Base Weight: + /// - Unlock Remove: 42.96 + .048 * R + /// - Unlock Set: 37.63 + .327 * R /// # </weight> - #[weight = 0] + #[weight = 43_000_000 + 330_000 * Weight::from(T::MaxVotes::get()) + + T::DbWeight::get().reads_writes(3, 3)] fn unlock(origin, target: T::AccountId) { ensure_signed(origin)?; Self::update_lock(&target); @@ -1107,9 +1304,12 @@ decl_module! { /// `close_proxy` must be called before the account can be destroyed. /// /// # <weight> - /// - One extra DB entry. + /// - Complexity: O(1) + /// - Db reads: `Proxy`, `proxy account` + /// - Db writes: `Proxy`, `proxy account` + /// - Base Weight: 14.86 µs /// # </weight> - #[weight = 100_000_000] + #[weight = 15_000_000 + T::DbWeight::get().reads_writes(2, 2)] fn open_proxy(origin, target: T::AccountId) { let who = ensure_signed(origin)?; Proxy::<T>::mutate(&who, |a| { @@ -1147,8 +1347,12 @@ decl_module! { /// /// # <weight> /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + /// - Db reads: `ReferendumInfoOf`, `VotingOf` + /// - Db writes: `ReferendumInfoOf`, `VotingOf` + /// - Base Weight: 21.03 + .359 * R /// # </weight> - #[weight = 0] + #[weight = 21_000_000 + 360_000 * Weight::from(T::MaxVotes::get()) + T::DbWeight::get().reads_writes(2, 2)] fn remove_vote(origin, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; Self::try_remove_vote(&who, index, UnvoteScope::Any) @@ -1169,8 +1373,12 @@ decl_module! { /// /// # <weight> /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + /// - Db reads: `ReferendumInfoOf`, `VotingOf` + /// - Db writes: `ReferendumInfoOf`, `VotingOf` + /// - Base Weight: 19.15 + .372 * R /// # </weight> - #[weight = 0] + #[weight = 19_000_000 + 370_000 * Weight::from(T::MaxVotes::get()) + T::DbWeight::get().reads_writes(2, 2)] fn remove_other_vote(origin, target: T::AccountId, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; @@ -1200,16 +1408,22 @@ decl_module! { /// Emits `Delegated`. /// /// # <weight> + /// same as `delegate with additional: + /// - Db reads: `Proxy`, `proxy account` + /// - Db writes: `proxy account` + /// - Base Weight: 68.61 + 8.039 * R µs /// # </weight> - #[weight = 500_000_000] + #[weight = weight_for::proxy_delegate::<T>(T::MaxVotes::get().into())] pub fn proxy_delegate(origin, to: T::AccountId, conviction: Conviction, balance: BalanceOf<T>, - ) { + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?; - Self::try_delegate(target, to, conviction, balance)?; + let votes = Self::try_delegate(target, to, conviction, balance)?; + + Ok(Some(weight_for::proxy_delegate::<T>(votes.into())).into()) } /// Undelegate the voting power of a proxied account. @@ -1223,13 +1437,17 @@ decl_module! { /// Emits `Undelegated`. /// /// # <weight> - /// - O(1). + /// same as `undelegate with additional: + /// Db reads: `Proxy` + /// Base Weight: 39 + 7.958 * R µs /// # </weight> - #[weight = 500_000_000] - fn proxy_undelegate(origin) { + #[weight = weight_for::proxy_undelegate::<T>(T::MaxVotes::get().into())] + fn proxy_undelegate(origin) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?; - Self::try_undelegate(target)?; + let votes = Self::try_undelegate(target)?; + + Ok(Some(weight_for::proxy_undelegate::<T>(votes.into())).into()) } /// Remove a proxied vote for a referendum. @@ -1244,8 +1462,12 @@ decl_module! { /// /// # <weight> /// - `O(R + log R)` where R is the number of referenda that `target` has voted on. + /// Weight is calculated for the maximum number of vote. + /// - Db reads: `ReferendumInfoOf`, `VotingOf`, `Proxy` + /// - Db writes: `ReferendumInfoOf`, `VotingOf` + /// - Base Weight: 26.35 + .36 * R µs /// # </weight> - #[weight = 0] + #[weight = 26_000_000 + 360_000 * Weight::from(T::MaxVotes::get()) + T::DbWeight::get().reads_writes(2, 3)] fn proxy_remove_vote(origin, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; let target = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?; @@ -1267,7 +1489,7 @@ impl<T: Trait> Module<T> { /// Get the amount locked in support of `proposal`; `None` if proposal isn't a valid proposal /// index. pub fn backing_for(proposal: PropIndex) -> Option<BalanceOf<T>> { - Self::deposit_of(proposal).map(|(d, l)| d * (l.len() as u32).into()) + Self::deposit_of(proposal).map(|(l, d)| d * (l.len() as u32).into()) } /// Get all referenda ready for tally at block `n`. @@ -1276,7 +1498,14 @@ impl<T: Trait> Module<T> { ) -> Vec<(ReferendumIndex, ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>)> { let next = Self::lowest_unbaked(); let last = Self::referendum_count(); - (next..last).into_iter() + Self::maturing_referenda_at_inner(n, next..last) + } + + fn maturing_referenda_at_inner( + n: T::BlockNumber, + range: core::ops::Range<PropIndex>, + ) -> Vec<(ReferendumIndex, ReferendumStatus<T::BlockNumber, T::Hash, BalanceOf<T>>)> { + range.into_iter() .map(|i| (i, Self::referendum_info(i))) .filter_map(|(i, maybe_info)| match maybe_info { Some(ReferendumInfo::Ongoing(status)) => Some((i, status)), @@ -1353,7 +1582,10 @@ impl<T: Trait> Module<T> { } votes[i].1 = vote; } - Err(i) => votes.insert(i, (ref_index, vote)), + Err(i) => { + ensure!(votes.len() as u32 <= T::MaxVotes::get(), Error::<T>::MaxVotesReached); + votes.insert(i, (ref_index, vote)); + } } // Shouldn't be possible to fail, but we handle it gracefully. status.tally.add(vote).ok_or(Error::<T>::Overflow)?; @@ -1416,11 +1648,14 @@ impl<T: Trait> Module<T> { Ok(()) } - fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) { + /// Return the number of votes for `who` + fn increase_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) -> u32 { VotingOf::<T>::mutate(who, |voting| match voting { - Voting::Delegating { delegations, .. } => + Voting::Delegating { delegations, .. } => { // We don't support second level delegating, so we don't need to do anything more. - *delegations = delegations.saturating_add(amount), + *delegations = delegations.saturating_add(amount); + 1 + }, Voting::Direct { votes, delegations, .. } => { *delegations = delegations.saturating_add(amount); for &(ref_index, account_vote) in votes.iter() { @@ -1432,15 +1667,19 @@ impl<T: Trait> Module<T> { ); } } + votes.len() as u32 } }) } - fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) { + /// Return the number of votes for `who` + fn reduce_upstream_delegation(who: &T::AccountId, amount: Delegations<BalanceOf<T>>) -> u32 { VotingOf::<T>::mutate(who, |voting| match voting { - Voting::Delegating { delegations, .. } => - // We don't support second level delegating, so we don't need to do anything more. - *delegations = delegations.saturating_sub(amount), + Voting::Delegating { delegations, .. } => { + // We don't support second level delegating, so we don't need to do anything more. + *delegations = delegations.saturating_sub(amount); + 1 + } Voting::Direct { votes, delegations, .. } => { *delegations = delegations.saturating_sub(amount); for &(ref_index, account_vote) in votes.iter() { @@ -1452,20 +1691,23 @@ impl<T: Trait> Module<T> { ); } } + votes.len() as u32 } }) } /// Attempt to delegate `balance` times `conviction` of voting power from `who` to `target`. + /// + /// Return the upstream number of votes. fn try_delegate( who: T::AccountId, target: T::AccountId, conviction: Conviction, balance: BalanceOf<T>, - ) -> DispatchResult { + ) -> Result<u32, DispatchError> { ensure!(who != target, Error::<T>::Nonsense); ensure!(balance <= T::Currency::free_balance(&who), Error::<T>::InsufficientFunds); - VotingOf::<T>::try_mutate(&who, |voting| -> DispatchResult { + let votes = VotingOf::<T>::try_mutate(&who, |voting| -> Result<u32, DispatchError> { let mut old = Voting::Delegating { balance, target: target.clone(), @@ -1486,7 +1728,7 @@ impl<T: Trait> Module<T> { voting.set_common(delegations, prior); } } - Self::increase_upstream_delegation(&target, conviction.votes(balance)); + let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance)); // Extend the lock to `balance` (rather than setting it) since we don't know what other // votes are in place. T::Currency::extend_lock( @@ -1495,15 +1737,17 @@ impl<T: Trait> Module<T> { balance, WithdrawReason::Transfer.into() ); - Ok(()) + Ok(votes) })?; Self::deposit_event(Event::<T>::Delegated(who, target)); - Ok(()) + Ok(votes) } /// Attempt to end the current delegation. - fn try_undelegate(who: T::AccountId) -> DispatchResult { - VotingOf::<T>::try_mutate(&who, |voting| -> DispatchResult { + /// + /// Return the number of votes of upstream. + fn try_undelegate(who: T::AccountId) -> Result<u32, DispatchError> { + let votes = VotingOf::<T>::try_mutate(&who, |voting| -> Result<u32, DispatchError> { let mut old = Voting::default(); sp_std::mem::swap(&mut old, voting); match old { @@ -1515,20 +1759,21 @@ impl<T: Trait> Module<T> { mut prior, } => { // remove any delegation votes to our current target. - Self::reduce_upstream_delegation(&target, conviction.votes(balance)); + let votes = Self::reduce_upstream_delegation(&target, conviction.votes(balance)); let now = system::Module::<T>::block_number(); let lock_periods = conviction.lock_periods().into(); prior.accumulate(now + T::EnactmentPeriod::get() * lock_periods, balance); voting.set_common(delegations, prior); + + Ok(votes) } Voting::Direct { .. } => { - return Err(Error::<T>::NotDelegating.into()) + Err(Error::<T>::NotDelegating.into()) } } - Ok(()) })?; Self::deposit_event(Event::<T>::Undelegated(who)); - Ok(()) + Ok(votes) } /// Rejig the lock on an account. It will never get more stringent (since that would indicate @@ -1598,7 +1843,7 @@ impl<T: Trait> Module<T> { let (prop_index, proposal, _) = public_props.swap_remove(winner_index); <PublicProps<T>>::put(public_props); - if let Some((deposit, depositors)) = <DepositOf<T>>::take(prop_index) { + if let Some((depositors, deposit)) = <DepositOf<T>>::take(prop_index) { // refund depositors for d in &depositors { T::Currency::unreserve(d, deposit); @@ -1677,19 +1922,132 @@ impl<T: Trait> Module<T> { } /// Current era is ending; we should finish up any proposals. - fn begin_block(now: T::BlockNumber) -> DispatchResult { + /// + /// + /// # <weight> + /// If a referendum is launched or maturing take full block weight. Otherwise: + /// - Complexity: `O(R)` where `R` is the number of unbaked referenda. + /// - Db reads: `LastTabledWasExternal`, `NextExternal`, `PublicProps`, `account`, + /// `ReferendumCount`, `LowestUnbaked` + /// - Db writes: `PublicProps`, `account`, `ReferendumCount`, `DepositOf`, `ReferendumInfoOf` + /// - Db reads per R: `DepositOf`, `ReferendumInfoOf` + /// - Base Weight: 58.58 + 10.9 * R µs + /// # </weight> + fn begin_block(now: T::BlockNumber) -> Result<Weight, DispatchError> { + let mut weight = 60_000_000 + T::DbWeight::get().reads_writes(6, 5); + // pick out another public referendum if it's time. if (now % T::LaunchPeriod::get()).is_zero() { // Errors come from the queue being empty. we don't really care about that, and even if // we did, there is nothing we can do here. let _ = Self::launch_next(now); + weight = T::MaximumBlockWeight::get(); } // tally up votes for any expiring referenda. - for (index, info) in Self::maturing_referenda_at(now).into_iter() { + let next = Self::lowest_unbaked(); + let last = Self::referendum_count(); + let r = Weight::from(last.saturating_sub(next)); + weight += 11_000_000 * r + T::DbWeight::get().reads(2 * r); + for (index, info) in Self::maturing_referenda_at_inner(now, next..last).into_iter() { let approved = Self::bake_referendum(now, index, info)?; ReferendumInfoOf::<T>::insert(index, ReferendumInfo::Finished { end: now, approved }); + weight = T::MaximumBlockWeight::get(); + } + + Ok(weight) + } + + /// Reads the length of account in DepositOf without getting the complete value in the runtime. + /// + /// Return 0 if no deposit for this proposal. + fn len_of_deposit_of(proposal: PropIndex) -> Option<u32> { + // DepositOf first tuple element is a vec, decoding its len is equivalent to decode a + // `Compact<u32>`. + decode_compact_u32_at(&<DepositOf<T>>::hashed_key_for(proposal)) + } + + /// Check that pre image exists and its value is variant `PreimageStatus::Missing`. + /// + /// This check is done without getting the complete value in the runtime to avoid copying a big + /// value in the runtime. + fn check_pre_image_is_missing(proposal_hash: T::Hash) -> DispatchResult { + // To decode the enum variant we only need the first byte. + let mut buf = [0u8; 1]; + let key = <Preimages<T>>::hashed_key_for(proposal_hash); + let bytes = match sp_io::storage::read(&key, &mut buf, 0) { + Some(bytes) => bytes, + None => return Err(Error::<T>::NotImminent.into()), + }; + // The value may be smaller that 1 byte. + let mut input = &buf[0..buf.len().min(bytes as usize)]; + + match input.read_byte() { + Ok(0) => Ok(()), // PreimageStatus::Missing is variant 0 + Ok(1) => Err(Error::<T>::DuplicatePreimage.into()), + _ => { + sp_runtime::print("Failed to decode `PreimageStatus` variant"); + Err(Error::<T>::NotImminent.into()) + } + } + } + + /// Check that pre image exists, its value is variant `PreimageStatus::Available` and decode + /// the length of `data: Vec<u8>` fields. + /// + /// This check is done without getting the complete value in the runtime to avoid copying a big + /// value in the runtime. + /// + /// If the pre image is missing variant or doesn't exist then the error `PreimageMissing` is + /// returned. + fn pre_image_data_len(proposal_hash: T::Hash) -> Result<u32, DispatchError> { + // To decode the `data` field of Available variant we need: + // * one byte for the variant + // * at most 5 bytes to decode a `Compact<u32>` + let mut buf = [0u8; 6]; + let key = <Preimages<T>>::hashed_key_for(proposal_hash); + let bytes = match sp_io::storage::read(&key, &mut buf, 0) { + Some(bytes) => bytes, + None => return Err(Error::<T>::PreimageMissing.into()), + }; + // The value may be smaller that 6 bytes. + let mut input = &buf[0..buf.len().min(bytes as usize)]; + + match input.read_byte() { + Ok(1) => (), // Check that input exists and is second variant. + Ok(0) => return Err(Error::<T>::PreimageMissing.into()), + _ => { + sp_runtime::print("Failed to decode `PreimageStatus` variant"); + return Err(Error::<T>::PreimageMissing.into()); + } + } + + // Decode the length of the vector. + let len = codec::Compact::<u32>::decode(&mut input).map_err(|_| { + sp_runtime::print("Failed to decode `PreimageStatus` variant"); + DispatchError::from(Error::<T>::PreimageMissing) + })?.0; + + Ok(len) + } +} + +/// Decode `Compact<u32>` from the trie at given key. +fn decode_compact_u32_at(key: &[u8]) -> Option<u32> { + // `Compact<u32>` takes at most 5 bytes. + let mut buf = [0u8; 5]; + let bytes = match sp_io::storage::read(&key, &mut buf, 0) { + Some(bytes) => bytes, + None => return None, + }; + // The value may be smaller than 5 bytes. + let mut input = &buf[0..buf.len().min(bytes as usize)]; + match codec::Compact::<u32>::decode(&mut input) { + Ok(c) => Some(c.0), + Err(_) => { + sp_runtime::print("Failed to decode compact u32 at:"); + sp_runtime::print(key); + None } - Ok(()) } } diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 2fa9bb232839aa7a4f04cc04efb5d4b5b312779e..d46214a1699c88c3a0fc25a6c5f84c5162966154 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -42,6 +42,8 @@ mod proxying; mod public_proposals; mod scheduling; mod voting; +mod migration; +mod decoders; const AYE: Vote = Vote { aye: true, conviction: Conviction::None }; const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; @@ -132,6 +134,7 @@ parameter_types! { pub const MinimumDeposit: u64 = 1; pub const EnactmentPeriod: u64 = 2; pub const CooloffPeriod: u64 = 2; + pub const MaxVotes: u32 = 100; } ord_parameter_types! { pub const One: u64 = 1; @@ -182,6 +185,7 @@ impl super::Trait for Test { type InstantOrigin = EnsureSignedBy<Six, u64>; type InstantAllowed = InstantAllowed; type Scheduler = Scheduler; + type MaxVotes = MaxVotes; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -232,7 +236,7 @@ fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { Democracy::propose( Origin::signed(who), set_balance_proposal_hash(value), - delay + delay, ) } @@ -240,14 +244,14 @@ fn propose_set_balance_and_note(who: u64, value: u64, delay: u64) -> DispatchRes Democracy::propose( Origin::signed(who), set_balance_proposal_hash_and_note(value), - delay + delay, ) } fn next_block() { System::set_block_number(System::block_number() + 1); Scheduler::on_initialize(System::block_number()); - assert_eq!(Democracy::begin_block(System::block_number()), Ok(())); + assert!(Democracy::begin_block(System::block_number()).is_ok()); } fn fast_forward_to(n: u64) { diff --git a/substrate/frame/democracy/src/tests/decoders.rs b/substrate/frame/democracy/src/tests/decoders.rs new file mode 100644 index 0000000000000000000000000000000000000000..6b8e661ca9fd9715e9dea882eb3c1e580535eb77 --- /dev/null +++ b/substrate/frame/democracy/src/tests/decoders.rs @@ -0,0 +1,81 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see <http://www.gnu.org/licenses/>. + +//! The for various partial storage decoders + +use super::*; +use frame_support::storage::{migration, StorageMap, unhashed}; + +#[test] +fn test_decode_compact_u32_at() { + new_test_ext().execute_with(|| { + let v = codec::Compact(u64::max_value()); + migration::put_storage_value(b"test", b"", &[], v); + assert_eq!(decode_compact_u32_at(b"test"), None); + + for v in vec![0, 10, u32::max_value()] { + let compact_v = codec::Compact(v); + unhashed::put(b"test", &compact_v); + assert_eq!(decode_compact_u32_at(b"test"), Some(v)); + } + + unhashed::kill(b"test"); + assert_eq!(decode_compact_u32_at(b"test"), None); + }) +} + +#[test] +fn len_of_deposit_of() { + new_test_ext().execute_with(|| { + for l in vec![0, 1, 200, 1000] { + let value: (Vec<u64>, u64) = ((0..l).map(|_| Default::default()).collect(), 3u64); + DepositOf::<Test>::insert(2, value); + assert_eq!(Democracy::len_of_deposit_of(2), Some(l)); + } + + DepositOf::<Test>::remove(2); + assert_eq!(Democracy::len_of_deposit_of(2), None); + }) +} + +#[test] +fn pre_image() { + new_test_ext().execute_with(|| { + let key = Default::default(); + let missing = PreimageStatus::Missing(0); + Preimages::<Test>::insert(key, missing); + assert!(Democracy::pre_image_data_len(key).is_err()); + assert_eq!(Democracy::check_pre_image_is_missing(key), Ok(())); + + Preimages::<Test>::remove(key); + assert!(Democracy::pre_image_data_len(key).is_err()); + assert!(Democracy::check_pre_image_is_missing(key).is_err()); + + for l in vec![0, 10, 100, 1000u32] { + let available = PreimageStatus::Available{ + data: (0..l).map(|i| i as u8).collect(), + provider: 0, + deposit: 0, + since: 0, + expiry: None, + }; + + Preimages::<Test>::insert(key, available); + assert_eq!(Democracy::pre_image_data_len(key), Ok(l)); + assert!(Democracy::check_pre_image_is_missing(key).is_err()); + } + }) +} diff --git a/substrate/frame/democracy/src/tests/migration.rs b/substrate/frame/democracy/src/tests/migration.rs new file mode 100644 index 0000000000000000000000000000000000000000..cab8f7f5c936c433237d4aaa7e952ada6933da4c --- /dev/null +++ b/substrate/frame/democracy/src/tests/migration.rs @@ -0,0 +1,45 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see <http://www.gnu.org/licenses/>. + +//! The tests for migration. + +use super::*; +use frame_support::{storage::migration, Hashable, traits::OnRuntimeUpgrade}; +use substrate_test_utils::assert_eq_uvec; + +#[test] +fn migration() { + new_test_ext().execute_with(|| { + for i in 0..3 { + let k = i.twox_64_concat(); + let v: (BalanceOf<Test>, Vec<u64>) = (i * 1000, vec![i]); + migration::put_storage_value(b"Democracy", b"DepositOf", &k, v); + } + StorageVersion::kill(); + + Democracy::on_runtime_upgrade(); + + assert_eq!(StorageVersion::get(), Some(Releases::V1)); + assert_eq_uvec!( + DepositOf::<Test>::iter().collect::<Vec<_>>(), + vec![ + (0, (vec![0u64], <BalanceOf<Test>>::from(0u32))), + (1, (vec![1u64], <BalanceOf<Test>>::from(1000u32))), + (2, (vec![2u64], <BalanceOf<Test>>::from(2000u32))), + ] + ); + }) +} diff --git a/substrate/frame/democracy/src/tests/preimage.rs b/substrate/frame/democracy/src/tests/preimage.rs index 218a0cc25c86e64ead754d11af08c9844dc0c240..094cde86d0b7b492c6562b052ce7c79272bfd439 100644 --- a/substrate/frame/democracy/src/tests/preimage.rs +++ b/substrate/frame/democracy/src/tests/preimage.rs @@ -77,11 +77,11 @@ fn preimage_deposit_should_be_reapable_earlier_by_owner() { next_block(); assert_noop!( - Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2)), + Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::max_value()), Error::<Test>::TooEarly ); next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2))); + assert_ok!(Democracy::reap_preimage(Origin::signed(6), set_balance_proposal_hash(2), u32::max_value())); assert_eq!(Balances::free_balance(6), 60); assert_eq!(Balances::reserved_balance(6), 0); @@ -92,7 +92,7 @@ fn preimage_deposit_should_be_reapable_earlier_by_owner() { fn preimage_deposit_should_be_reapable() { new_test_ext().execute_with(|| { assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::max_value()), Error::<Test>::PreimageMissing ); @@ -104,12 +104,12 @@ fn preimage_deposit_should_be_reapable() { next_block(); next_block(); assert_noop!( - Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2)), + Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::max_value()), Error::<Test>::TooEarly ); next_block(); - assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2))); + assert_ok!(Democracy::reap_preimage(Origin::signed(5), set_balance_proposal_hash(2), u32::max_value())); assert_eq!(Balances::reserved_balance(6), 0); assert_eq!(Balances::free_balance(6), 48); assert_eq!(Balances::free_balance(5), 62); @@ -153,6 +153,6 @@ fn reaping_imminent_preimage_should_fail() { assert_ok!(Democracy::vote(Origin::signed(1), r, aye(1))); next_block(); next_block(); - assert_noop!(Democracy::reap_preimage(Origin::signed(6), h), Error::<Test>::Imminent); + assert_noop!(Democracy::reap_preimage(Origin::signed(6), h, u32::max_value()), Error::<Test>::Imminent); }); } diff --git a/substrate/frame/democracy/src/tests/public_proposals.rs b/substrate/frame/democracy/src/tests/public_proposals.rs index c2103fd9480a8cdcfe8d551de456cf6472457331..68ec790baae8610591c8cf768e66644824f4c09a 100644 --- a/substrate/frame/democracy/src/tests/public_proposals.rs +++ b/substrate/frame/democracy/src/tests/public_proposals.rs @@ -35,10 +35,10 @@ fn backing_for_should_work() { fn deposit_for_proposals_should_be_taken() { new_test_ext().execute_with(|| { assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); + assert_ok!(Democracy::second(Origin::signed(2), 0, u32::max_value())); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::max_value())); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::max_value())); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::max_value())); assert_eq!(Balances::free_balance(1), 5); assert_eq!(Balances::free_balance(2), 15); assert_eq!(Balances::free_balance(5), 35); @@ -49,10 +49,10 @@ fn deposit_for_proposals_should_be_taken() { fn deposit_for_proposals_should_be_returned() { new_test_ext().execute_with(|| { assert_ok!(propose_set_balance_and_note(1, 2, 5)); - assert_ok!(Democracy::second(Origin::signed(2), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); - assert_ok!(Democracy::second(Origin::signed(5), 0)); + assert_ok!(Democracy::second(Origin::signed(2), 0, u32::max_value())); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::max_value())); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::max_value())); + assert_ok!(Democracy::second(Origin::signed(5), 0, u32::max_value())); fast_forward_to(3); assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 20); @@ -78,7 +78,21 @@ fn poor_proposer_should_not_work() { fn poor_seconder_should_not_work() { new_test_ext().execute_with(|| { assert_ok!(propose_set_balance_and_note(2, 2, 11)); - assert_noop!(Democracy::second(Origin::signed(1), 0), BalancesError::<Test, _>::InsufficientBalance); + assert_noop!( + Democracy::second(Origin::signed(1), 0, u32::max_value()), + BalancesError::<Test, _>::InsufficientBalance + ); + }); +} + +#[test] +fn invalid_seconds_upper_bound_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(propose_set_balance_and_note(1, 2, 5)); + assert_noop!( + Democracy::second(Origin::signed(2), 0, 0), + Error::<Test>::WrongUpperBound + ); }); }